Bug 1440195 Add a per-context mix-in value for our Timer Precision Reduction Functions r?baku draft
authorTom Ritter <tom@mozilla.com>
Fri, 09 Mar 2018 16:22:37 -0600
changeset 767470 c1611ee0df8cbd4e63e02ce515184f809851ec5a
parent 767469 ad0c149441dc56c137d4897993a0edfe82b3ef08
child 767471 9f69e3802148b0af56b99c68ce91ecc8f3003496
push id102611
push userbmo:tom@mozilla.com
push dateWed, 14 Mar 2018 17:48:37 +0000
reviewersbaku
bugs1440195
milestone60.0a1
Bug 1440195 Add a per-context mix-in value for our Timer Precision Reduction Functions r?baku We need to include a seed for each context (origin, iframe, worker, etc) we reduce the time precision of. This prevents a replay attack of the random midpoint sequence. MozReview-Commit-ID: EFoHev1SrTM
toolkit/components/resistfingerprinting/RelativeTimeline.cpp
toolkit/components/resistfingerprinting/RelativeTimeline.h
toolkit/components/resistfingerprinting/moz.build
toolkit/components/resistfingerprinting/nsRFPService.cpp
toolkit/components/resistfingerprinting/nsRFPService.h
toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/RelativeTimeline.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIRandomGenerator.h"
+#include "nsServiceManagerUtils.h"
+
+#include "RelativeTimeline.h"
+
+namespace mozilla {
+
+int64_t RelativeTimeline::GetRandomTimelineSeed()
+{
+  if (mRandomTimelineSeed == 0)
+  {
+    nsresult rv;
+    nsCOMPtr<nsIRandomGenerator> randomGenerator =
+      do_GetService("@mozilla.org/security/random-generator;1", &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mRandomTimelineSeed = rand();
+      return mRandomTimelineSeed;
+    }
+
+    uint8_t* buffer = nullptr;
+    rv = randomGenerator->GenerateRandomBytes(sizeof(mRandomTimelineSeed), &buffer);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mRandomTimelineSeed = rand();
+      return mRandomTimelineSeed;
+    }
+
+    memcpy(&mRandomTimelineSeed, buffer, sizeof(mRandomTimelineSeed));
+    MOZ_ASSERT(buffer);
+    free(buffer);
+  }
+  return mRandomTimelineSeed;
+}
+
+} // mozilla namespace
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/RelativeTimeline.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __RelativeTimeline_h__
+#define __RelativeTimeline_h__
+
+namespace mozilla {
+
+class RelativeTimeline
+{
+public:
+  RelativeTimeline()
+    : mRandomTimelineSeed(0) { }
+
+  int64_t GetRandomTimelineSeed();
+private:
+  uint64_t mRandomTimelineSeed;
+
+};
+
+} // mozilla namespace
+
+#endif /* __RelativeTimeline_h__ */
\ No newline at end of file
--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -3,19 +3,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 TEST_DIRS += ['tests']
 
 UNIFIED_SOURCES += [
     'nsRFPService.cpp',
+    'RelativeTimeline.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 EXPORTS += [
-    'nsRFPService.h',
+    'nsRFPService.h'
+]
+EXPORTS.mozilla += [
+    'RelativeTimeline.h',
 ]
 
 EXTRA_JS_MODULES += [
     'LanguagePrompt.jsm',
 ]
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -240,18 +240,18 @@ static StaticRefPtr<LRUCache> sCache;
  *                              random midpoint (e.g. 360)
  *
  * If our actual timestamp (e.g. 325) is below the midpoint, we keep it clamped
  * downwards. If it were equal to or above the midpoint (e.g. 365) we would
  * round it upwards to the largest clamped value (in this example: 400).
  *
  * The question is: does time go backwards?
  *
- * The midpoint is deterministicly random
- * and generated from two components: a secret seed and a clamped time.
+ * The midpoint is deterministicly random and generated from three components:
+ * a secret seed, a per-timeline (context) 'mix-in', and a clamped time.
  *
  * When comparing times across different seed values: time may go backwards.
  * For a clamped time of 300, one seed may generate a midpoint of 305 and another
  * 395. So comparing an (actual) timestamp of 325 and 351 could see the 325 clamped
  * up to 400 and the 351 clamped down to 300. The seed is per-process, so this case
  * occurs when one can compare timestamps cross-process. This is uncommon (because
  * we don't have site isolation.) The circumstances this could occur are
  * BroadcastChannel, Storage Notification, and in theory (but not yet implemented)
@@ -263,37 +263,46 @@ static StaticRefPtr<LRUCache> sCache;
  * Assume a page and a worker. If one calls performance.now() in the page and then
  * triggers a call to performance.now() in the worker, the following invariant should
  * hold true:
  *             page.performance.timeOrigin + page.performance.now() <
  *                        worker.performance.timeOrigin + worker.performance.now()
  *
  * We break this invariant.
  *
+ * The 'Context Mix-in' is a securely generated random seed that is unique for each
+ * timeline that starts over at zero. It is needed to ensure that the sequence of
+ * midpoints (as calculated by the secret seed and clamped time) does not repeat.
+ * In RelativeTimeline.h, we define a 'RelativeTimeline' class that can be inherited by
+ * any object that has a relative timeline. The most obvious examples are Documents
+ * and Workers. An attacker could let time go forward and observe (roughly) where
+ * the random midpoints fall. Then they create a new object, time starts back over at
+ * zero, and they know (approximately) where the random midpoints are.
  *
- * TODO: The above comment is going to need to be entirely rewritten when we mix in
- * a per-context shared secret. Context is 'Any new object that gets a time origin
- * starting from zero'. The most obvious example is Documents and Workers. An attacker
- * could let time go forward and observe (roughly) where the random midpoints fall.
- * Then they create a new object, time starts back ovr at zero, and they know
- * (approximately) where the random midpoints are.
+ * When the timestamp given is a non-relative timestamp (e.g. it is relative to the
+ * unix epoch) it is not possible to replay a sequence of random values. Thus,
+ * providing a zero context pointer is an indicator that the timestamp given is
+ * absolute and does not need any additional randomness.
  *
  * @param aClampedTimeUSec [in]  The clamped input time in microseconds.
  * @param aResolutionUSec  [in]  The current resolution for clamping in microseconds.
  * @param aMidpointOut     [out] The midpoint, in microseconds, between [0, aResolutionUSec].
+ * @param aContextMixin    [in]  An opaque random value for relative timestamps. 0 for
+ *                               absolute timestamps
  * @param aSecretSeed      [in]  TESTING ONLY. When provided, the current seed will be
  *                               replaced with this value.
  * @return                 A nsresult indicating success of failure. If the function failed,
  *                         nothing is written to aMidpointOut
  */
 
 /* static */
 nsresult
 nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
                              long long aResolutionUSec,
+                             int64_t aContextMixin,
                              long long* aMidpointOut,
                              uint8_t * aSecretSeed /* = nullptr */)
 {
   nsresult rv;
   const int kSeedSize = 16;
   const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
   static uint8_t * sSecretMidpointSeed = nullptr;
 
@@ -424,47 +433,66 @@ nsRFPService::RandomMidpoint(long long a
  * Given a precision value, this function will reduce a given input time to the nearest
  * multiple of that precision.
  *
  * It will check if it is appropriate to clamp the input time according to the values
  * of the privacy.resistFingerprinting and privacy.reduceTimerPrecision preferences.
  * Note that while it will check these prefs, it will use whatever precision is given to
  * it, so if one desires a minimum precision for Resist Fingerprinting, it is the
  * caller's responsibility to provide the correct value. This means you should pass
- * TimerPrecision(), which enforces a minimum vale on the precision based on
+ * TimerResolution(), which enforces a minimum vale on the precision based on
  * preferences.
  *
  * It ensures the given precision value is greater than zero, if it is not it returns
  * the input time.
  *
  * @param aTime           [in] The input time to be clamped.
  * @param aTimeScale      [in] The units the input time is in (Seconds, Milliseconds, or Microseconds).
  * @param aResolutionUSec [in] The precision (in microseconds) to clamp to.
+ * @param aContextMixin   [in] An opaque random value for relative timestamps. 0 for absolute timestamps
  * @return                 If clamping is appropriate, the clamped value of the input, otherwise the input.
  */
 /* static */
 double
 nsRFPService::ReduceTimePrecisionImpl(
   double aTime,
   TimeScale aTimeScale,
   double aResolutionUSec,
+  int64_t aContextMixin,
   TimerPrecisionType aType)
  {
    if (!IsTimerPrecisionReductionEnabled(aType) || aResolutionUSec <= 0) {
      return aTime;
    }
 
   // Increase the time as needed until it is in microseconds.
   // Note that a double can hold up to 2**53 with integer precision. This gives us
   // only until June 5, 2255 in time-since-the-epoch with integer precision.
   // So we will be losing microseconds precision after that date.
   // We think this is okay, and we codify it in some tests.
   double timeScaled = aTime * (1000000 / aTimeScale);
   // Cut off anything less than a microsecond.
   long long timeAsInt = timeScaled;
+
+  // If we have a blank context mixin, this indicates we (should) have an absolute timestamp.
+  // We check the time, and if it less than a unix timestamp about 10 years in the past, we
+  // output to the log and, in debug builds, assert. This is an error case we want to
+  // understand and fix: we must have given a relative timestamp with a mixin of 0 which is
+  // incorrect.
+  // Anyone running a debug build _probably_ has an accurate clock, and if they don't, they'll
+  // hopefully find this message and understand why things are crashing.
+  if (aContextMixin == 0 && aType == TimerPrecisionType::All && timeAsInt < 1204233985000) {
+    MOZ_LOG(gResistFingerprintingLog, LogLevel::Error,
+      ("About to assert. aTime=%lli<1204233985000 aContextMixin=%" PRId64 " aType=%s",
+        timeAsInt, aContextMixin, (aType == TimerPrecisionType::RFPOnly ? "RFPOnly" : "All")));
+    MOZ_ASSERT(false, "ReduceTimePrecisionImpl was given a relative time "
+                      "with an empty context mix-in (or your clock is 10+ years off.) "
+                      "Run this with MOZ_LOG=nsResistFingerprinting:1 to get more details.");
+}
+
   // Cast the resolution (in microseconds) to an int.
   long long resolutionAsInt = aResolutionUSec;
   // Perform the clamping.
   // We do a cast back to double to perform the division with doubles, then floor the result
   // and the rest occurs with integer precision.
   // This is because it gives consistency above and below zero. Above zero, performing the
   // division in integers truncates decimals, taking the result closer to zero (a floor).
   // Below zero, performing the division in integers truncates decimals, taking the result
@@ -480,92 +508,125 @@ nsRFPService::ReduceTimePrecisionImpl(
             clampedAndJittered = clamped;
   // RandomMidpoint uses crypto functions from NSS. But we wind up in this code _very_ early
   // on in and we don't want to initialize NSS earlier than it would be initialized naturally.
   // Doing so caused nearly every xpcshell test to fail, as well as Marionette.
   // This is safe, because we're not going to be doing any web context stuff before NSS is
   // initialized, so anything that winds up here won't be exposed to content so we don't
   // really need to worry about fuzzing its value.
   if (sJitter && NSS_IsInitialized()) {
-    if(!NS_FAILED(RandomMidpoint(clamped, resolutionAsInt, &midpoint)) &&
+    if(!NS_FAILED(RandomMidpoint(clamped, resolutionAsInt, aContextMixin, &midpoint)) &&
        timeAsInt >= clamped + midpoint) {
       clampedAndJittered += resolutionAsInt;
     }
   }
 
   // Cast it back to a double and reduce it to the correct units.
   double ret = double(clampedAndJittered) / (1000000.0 / aTimeScale);
 
   bool tmp_jitter = sJitter;
   MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
     ("Given: (%.*f, Scaled: %.*f, Converted: %lli), Rounding with (%lli, Originally %.*f), "
-     "Intermediate: (%lli), Clamped: (%lli) Jitter: (%i Midpoint: %lli) Final: (%lli Converted: %.*f)",
+    "Intermediate: (%lli), Clamped: (%lli) Jitter: (%i Context: %" PRId64 " Midpoint: %lli) "
+    "Final: (%lli Converted: %.*f)",
      DBL_DIG-1, aTime, DBL_DIG-1, timeScaled, timeAsInt, resolutionAsInt, DBL_DIG-1, aResolutionUSec,
-     (long long)floor(double(timeAsInt) / resolutionAsInt), clamped, tmp_jitter, midpoint, clampedAndJittered, DBL_DIG-1, ret));
+    (long long)floor(double(timeAsInt) / resolutionAsInt), clamped, tmp_jitter, aContextMixin, midpoint,
+    clampedAndJittered, DBL_DIG-1, ret));
 
   return ret;
 }
 
 /* static */
 double
-nsRFPService::ReduceTimePrecisionAsUSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
+nsRFPService::ReduceTimePrecisionAsUSecs(
+  double aTime,
+  int64_t aContextMixin,
+  TimerPrecisionType aType /* = TimerPrecisionType::All */)
 {
-  return nsRFPService::ReduceTimePrecisionImpl(aTime, MicroSeconds, TimerResolution(), aType);
+  return nsRFPService::ReduceTimePrecisionImpl(
+    aTime,
+    MicroSeconds,
+    TimerResolution(),
+    aContextMixin,
+    aType);
 }
 
 /* static */
 double
 nsRFPService::ReduceTimePrecisionAsUSecsWrapper(double aTime)
 {
-  return nsRFPService::ReduceTimePrecisionImpl(aTime, MicroSeconds, TimerResolution(), TimerPrecisionType::All);
+  return nsRFPService::ReduceTimePrecisionImpl(
+    aTime,
+    MicroSeconds,
+    TimerResolution(),
+    0,
+    TimerPrecisionType::All);
 }
 
 /* static */
 double
-nsRFPService::ReduceTimePrecisionAsMSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
+nsRFPService::ReduceTimePrecisionAsMSecs(
+  double aTime,
+  int64_t aContextMixin,
+  TimerPrecisionType aType /* = TimerPrecisionType::All */)
 {
-  return nsRFPService::ReduceTimePrecisionImpl(aTime, MilliSeconds, TimerResolution(), aType);
+  return nsRFPService::ReduceTimePrecisionImpl(
+    aTime,
+    MilliSeconds,
+    TimerResolution(),
+    aContextMixin,
+    aType);
 }
 
 /* static */
 double
-nsRFPService::ReduceTimePrecisionAsSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
+nsRFPService::ReduceTimePrecisionAsSecs(
+  double aTime,
+  int64_t aContextMixin,
+  TimerPrecisionType aType /* = TimerPrecisionType::All */)
 {
-  return nsRFPService::ReduceTimePrecisionImpl(aTime, Seconds, TimerResolution(), aType);
+  return nsRFPService::ReduceTimePrecisionImpl(
+    aTime,
+    Seconds,
+    TimerResolution(),
+    aContextMixin,
+    aType);
 }
 
 /* static */
 uint32_t
 nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality)
 {
   return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
 }
 
 /* static */
 uint32_t
 nsRFPService::GetSpoofedTotalFrames(double aTime)
 {
-  double time = ReduceTimePrecisionAsSecs(aTime);
+  double precision = TimerResolution() / 1000 / 1000;
+  double time = floor(aTime / precision) * precision;
 
   return NSToIntFloor(time * sVideoFramesPerSec);
 }
 
 /* static */
 uint32_t
 nsRFPService::GetSpoofedDroppedFrames(double aTime, uint32_t aWidth, uint32_t aHeight)
 {
   uint32_t targetRes = CalculateTargetVideoResolution(sTargetVideoRes);
 
   // The video resolution is less than or equal to the target resolution, we
   // report a zero dropped rate for this case.
   if (targetRes >= aWidth * aHeight) {
     return 0;
   }
 
-  double time = ReduceTimePrecisionAsSecs(aTime);
+  double precision = TimerResolution() / 1000 / 1000;
+  double time = floor(aTime / precision) * precision;
   // Bound the dropped ratio from 0 to 100.
   uint32_t boundedDroppedRatio = min(sVideoDroppedRatio, 100u);
 
   return NSToIntFloor(time * sVideoFramesPerSec * (boundedDroppedRatio / 100.0));
 }
 
 /* static */
 uint32_t
@@ -574,17 +635,18 @@ nsRFPService::GetSpoofedPresentedFrames(
   uint32_t targetRes = CalculateTargetVideoResolution(sTargetVideoRes);
 
   // The target resolution is greater than the current resolution. For this case,
   // there will be no dropped frames, so we report total frames directly.
   if (targetRes >= aWidth * aHeight) {
     return GetSpoofedTotalFrames(aTime);
   }
 
-  double time = ReduceTimePrecisionAsSecs(aTime);
+  double precision = TimerResolution() / 1000 / 1000;
+  double time = floor(aTime / precision) * precision;
   // Bound the dropped ratio from 0 to 100.
   uint32_t boundedDroppedRatio = min(sVideoDroppedRatio, 100u);
 
   return NSToIntFloor(time * sVideoFramesPerSec * ((100 - boundedDroppedRatio) / 100.0));
 }
 
 /* static */
 nsresult
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -167,36 +167,41 @@ public:
     Seconds      = 1,
     MilliSeconds = 1000,
     MicroSeconds = 1000000
   };
 
   // The following Reduce methods can be called off main thread.
   static double ReduceTimePrecisionAsUSecs(
     double aTime,
+    int64_t aContextMixin,
     TimerPrecisionType aType = TimerPrecisionType::All);
   static double ReduceTimePrecisionAsMSecs(
     double aTime,
+    int64_t aContextMixin,
     TimerPrecisionType aType = TimerPrecisionType::All);
   static double ReduceTimePrecisionAsSecs(
     double aTime,
+    int64_t aContextMixin,
     TimerPrecisionType aType = TimerPrecisionType::All);
 
   // Used by the JS Engine, as it doesn't know about the TimerPrecisionType enum
   static double ReduceTimePrecisionAsUSecsWrapper(
     double aTime);
 
   // Public only for testing purposes
   static double ReduceTimePrecisionImpl(
     double aTime,
     TimeScale aTimeScale,
     double aResolutionUSec,
+    int64_t aContextMixin,
     TimerPrecisionType aType);
   static nsresult RandomMidpoint(long long aClampedTimeUSec,
                                  long long aResolutionUSec,
+                                 int64_t aContextMixin,
                                  long long* aMidpointOut,
                                  uint8_t * aSecretSeed = nullptr);
 
   // This method calculates the video resolution (i.e. height x width) based
   // on the video quality (480p, 720p, etc).
   static uint32_t CalculateTargetVideoResolution(uint32_t aVideoQuality);
 
   // Methods for getting spoofed media statistics and the return value will
--- a/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
+++ b/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
@@ -59,18 +59,18 @@ bool setupJitter(bool enabled) {
 void cleanupJitter(bool jitterWasEnabled) {
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->SetBoolPref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", jitterWasEnabled);
   }
 }
 
 void process(double clock, nsRFPService::TimeScale clockUnits, double precision) {
-  double reduced1 = nsRFPService::ReduceTimePrecisionImpl(clock, clockUnits, precision, TimerPrecisionType::All);
-  double reduced2 = nsRFPService::ReduceTimePrecisionImpl(reduced1, clockUnits, precision, TimerPrecisionType::All);
+  double reduced1 = nsRFPService::ReduceTimePrecisionImpl(clock, clockUnits, precision, -1, TimerPrecisionType::All);
+  double reduced2 = nsRFPService::ReduceTimePrecisionImpl(reduced1, clockUnits, precision, -1, TimerPrecisionType::All);
   ASSERT_EQ(reduced1, reduced2);
 }
 
 TEST(ResistFingerprinting, ReducePrecision_Assumptions) {
   ASSERT_EQ(FLT_RADIX, 2);
   ASSERT_EQ(DBL_MANT_DIG, 53);
 }
 
@@ -110,45 +110,45 @@ TEST(ResistFingerprinting, ReducePrecisi
   process(2595.16, nsRFPService::TimeScale::MilliSeconds, 20);
   process(2578.66, nsRFPService::TimeScale::MilliSeconds, 20);
   cleanupJitter(jitterEnabled);
 }
 
 TEST(ResistFingerprinting, ReducePrecision_Expectations) {
   bool jitterEnabled = setupJitter(false);
   double result;
-  result = nsRFPService::ReduceTimePrecisionImpl(2611.14, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(2611.14, nsRFPService::TimeScale::MilliSeconds, 20, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 2611.14);
-  result = nsRFPService::ReduceTimePrecisionImpl(2611.145, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(2611.145, nsRFPService::TimeScale::MilliSeconds, 20, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 2611.14);
-  result = nsRFPService::ReduceTimePrecisionImpl(2611.141, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(2611.141, nsRFPService::TimeScale::MilliSeconds, 20, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 2611.14);
-  result = nsRFPService::ReduceTimePrecisionImpl(2611.15999, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(2611.15999, nsRFPService::TimeScale::MilliSeconds, 20, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 2611.14);
-  result = nsRFPService::ReduceTimePrecisionImpl(2611.15, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(2611.15, nsRFPService::TimeScale::MilliSeconds, 20, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 2611.14);
-  result = nsRFPService::ReduceTimePrecisionImpl(2611.13, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(2611.13, nsRFPService::TimeScale::MilliSeconds, 20, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 2611.12);
   cleanupJitter(jitterEnabled);
 }
 
 TEST(ResistFingerprinting, ReducePrecision_ExpectedLossOfPrecision) {
   bool jitterEnabled = setupJitter(false);
   double result;
   // We lose integer precision at 9007199254740992 - let's confirm that.
-  result = nsRFPService::ReduceTimePrecisionImpl(9007199254740992.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(9007199254740992.0, nsRFPService::TimeScale::MicroSeconds, 5, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 9007199254740990.0);
   // 9007199254740995 is approximated to 9007199254740996
-  result = nsRFPService::ReduceTimePrecisionImpl(9007199254740995.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(9007199254740995.0, nsRFPService::TimeScale::MicroSeconds, 5, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 9007199254740996);
   // 9007199254740999 is approximated as 9007199254741000
-  result = nsRFPService::ReduceTimePrecisionImpl(9007199254740999.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(9007199254740999.0, nsRFPService::TimeScale::MicroSeconds, 5, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 9007199254741000.0);
   // 9007199254743568 can be represented exactly, but will be clamped to 9007199254743564
-  result = nsRFPService::ReduceTimePrecisionImpl(9007199254743568.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(9007199254743568.0, nsRFPService::TimeScale::MicroSeconds, 5, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 9007199254743564.0);
   cleanupJitter(jitterEnabled);
 }
 
 // Use an ugly but simple hack to turn an integer-based rand()
 // function to a double-based one.
 #define RAND_DOUBLE (rand() * (rand() / (double)rand()))
 
@@ -242,65 +242,65 @@ TEST(ResistFingerprinting, ReducePrecisi
    */
 
   // Set the secret
   long long throwAway;
   uint8_t hardcodedSecret[16] = {
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 };
 
-  nsRFPService::RandomMidpoint(0, 500, &throwAway, hardcodedSecret);
+  nsRFPService::RandomMidpoint(0, 500, -1, &throwAway, hardcodedSecret);
 
   // Run the test vectors
   double result;
 
-  result = nsRFPService::ReduceTimePrecisionImpl(1, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(1, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 0);
-  result = nsRFPService::ReduceTimePrecisionImpl(129, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(129, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 0);
-  result = nsRFPService::ReduceTimePrecisionImpl(130, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(130, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 500);
-  result = nsRFPService::ReduceTimePrecisionImpl(131, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(131, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 500);
-  result = nsRFPService::ReduceTimePrecisionImpl(499, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(499, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 500);
 
-  result = nsRFPService::ReduceTimePrecisionImpl(500, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(500, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 500);
-  result = nsRFPService::ReduceTimePrecisionImpl(600, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(600, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 500);
-  result = nsRFPService::ReduceTimePrecisionImpl(928, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(928, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 500);
-  result = nsRFPService::ReduceTimePrecisionImpl(929, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(929, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 1000);
-  result = nsRFPService::ReduceTimePrecisionImpl(930, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(930, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 1000);
-  result = nsRFPService::ReduceTimePrecisionImpl(1255, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(1255, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 1000);
 
-  result = nsRFPService::ReduceTimePrecisionImpl(4000, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4000, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4000);
-  result = nsRFPService::ReduceTimePrecisionImpl(4295, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4295, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4000);
-  result = nsRFPService::ReduceTimePrecisionImpl(4296, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4296, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4000);
-  result = nsRFPService::ReduceTimePrecisionImpl(4297, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4297, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4500);
-  result = nsRFPService::ReduceTimePrecisionImpl(4298, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4298, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4500);
-  result = nsRFPService::ReduceTimePrecisionImpl(4499, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4499, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4500);
 
-  result = nsRFPService::ReduceTimePrecisionImpl(4500, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4500, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4500);
-  result = nsRFPService::ReduceTimePrecisionImpl(4536, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4536, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4500);
-  result = nsRFPService::ReduceTimePrecisionImpl(4537, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4537, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 4500);
-  result = nsRFPService::ReduceTimePrecisionImpl(4538, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4538, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 5000);
-  result = nsRFPService::ReduceTimePrecisionImpl(4539, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(4539, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 5000);
-  result = nsRFPService::ReduceTimePrecisionImpl(5106, nsRFPService::TimeScale::MicroSeconds, 500, TimerPrecisionType::All);
+  result = nsRFPService::ReduceTimePrecisionImpl(5106, nsRFPService::TimeScale::MicroSeconds, 500, -1, TimerPrecisionType::All);
   ASSERT_EQ(result, 5000);
 
   cleanupJitter(jitterEnabled);
 }