--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -120,81 +120,105 @@ nsRFPService::IsResistFingerprintingEnab
bool
nsRFPService::IsTimerPrecisionReductionEnabled(TimerPrecisionType aType)
{
if (aType == TimerPrecisionType::RFPOnly) {
return IsResistFingerprintingEnabled();
}
return (sPrivacyTimerPrecisionReduction || IsResistFingerprintingEnabled()) &&
- TimerResolution() != 0;
+ TimerResolution() > 0;
}
-/*
- DOC TODO
-*/
+/**
+ * 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
+ * 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.
+ * @return If clamping is appropriate, the clamped value of the input, otherwise the input.
+ */
/* static */
double
nsRFPService::ReduceTimePrecisionImpl(
double aTime,
- double aResolutionUS,
- double aResolutionScaleCorrection,
+ TimeScale aTimeScale,
+ double aResolutionUSec,
TimerPrecisionType aType)
{
- if (!IsTimerPrecisionReductionEnabled(aType)) {
+ if (!IsTimerPrecisionReductionEnabled(aType) || aResolutionUSec <= 0) {
return aTime;
}
- if (aResolutionScaleCorrection != 1 &&
- aResolutionScaleCorrection != 1000 &&
- aResolutionScaleCorrection != 1000000) {
- MOZ_ASSERT(false, "Only scale corrections of 1, 1000, and 1000000 are supported.");
- return aTime;
- }
- double ret;
- const double reducedResolution = aResolutionUS / aResolutionScaleCorrection;
- if (aResolutionScaleCorrection >= 1000000 && aResolutionUS < 1000000) {
- // The resolution is so small we need to use the reciprocal to avoid floating point error.
- const double resolutionReciprocal = 1000000.0 / reducedResolution;
- ret = floor(aTime * resolutionReciprocal) / resolutionReciprocal;
-#if defined(DEBUG)
- MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
- ("Given: %.*f, Reciprocal Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
- DBL_DIG-1, aTime, DBL_DIG-1, resolutionReciprocal, DBL_DIG-1, floor(aTime * resolutionReciprocal), DBL_DIG-1, ret));
-#endif
- } else {
- ret = floor(aTime / reducedResolution) * reducedResolution;
+ // 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;
+ // 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
+ // closer to zero (a ceil).
+ // The impact of this is that comparing two clamped values that should be related by a
+ // constant (e.g. 10s) that are across the zero barrier will no longer work. We need to
+ // round consistently towards positive infinity or negative infinity (we chose negative.)
+ // This can't be done with a truncation, it must be done with floor.
+ long long clamped = floor(double(timeAsInt) / resolutionAsInt) * resolutionAsInt;
+ // Cast it back to a double and reduce it to the correct units.
+ double ret = double(clamped) / (1000000.0 / aTimeScale);
+
#if defined(DEBUG)
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
- ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
- DBL_DIG-1, aTime, DBL_DIG-1, reducedResolution, DBL_DIG-1, floor(aTime / reducedResolution), DBL_DIG-1, ret));
+ ("Given: (%.*f, Scaled: %.*f, Converted: %lli), Rounding with (%lli, Originally %.*f), Intermediate: (%lli), Got: (%lli Converted: %.*f)",
+ DBL_DIG-1, aTime, DBL_DIG-1, timeScaled, timeAsInt, resolutionAsInt, DBL_DIG-1, aResolutionUSec,
+ (long long)floor(double(timeAsInt) / resolutionAsInt), clamped, DBL_DIG-1, ret));
#endif
- }
+
return ret;
}
/* static */
double
nsRFPService::ReduceTimePrecisionAsUSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
{
- return nsRFPService::ReduceTimePrecisionImpl(aTime, TimerResolution(), 1, aType);
+ return nsRFPService::ReduceTimePrecisionImpl(aTime, MicroSeconds, TimerResolution(), aType);
}
/* static */
double
nsRFPService::ReduceTimePrecisionAsMSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
{
- return nsRFPService::ReduceTimePrecisionImpl(aTime, TimerResolution(), 1000, aType);
+ return nsRFPService::ReduceTimePrecisionImpl(aTime, MilliSeconds, TimerResolution(), aType);
}
/* static */
double
nsRFPService::ReduceTimePrecisionAsSecs(double aTime, TimerPrecisionType aType /* = TimerPrecisionType::All */)
{
- return nsRFPService::ReduceTimePrecisionImpl(aTime, TimerResolution(), 1000000, aType);
+ return nsRFPService::ReduceTimePrecisionImpl(aTime, Seconds, TimerResolution(), aType);
}
/* static */
uint32_t
nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality)
{
return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
}
--- a/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
+++ b/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
@@ -37,116 +37,138 @@ using namespace mozilla;
Look at the last two values:
Got: 2064.83383999999978
Got: 2064.83381999999983
They're supposed to be equal. They're not. But they both round to 2064.83.
*/
-void process(double clock, double precision, double precisionUnits) {
- double reduced1 = nsRFPService::ReduceTimePrecisionImpl(clock, precision, precisionUnits, TimerPrecisionType::All);
- double reduced2 = nsRFPService::ReduceTimePrecisionImpl(reduced1, precision, precisionUnits, TimerPrecisionType::All);
+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);
ASSERT_EQ(reduced1, reduced2);
}
TEST(ResistFingerprinting, ReducePrecision_Assumptions) {
ASSERT_EQ(FLT_RADIX, 2);
ASSERT_EQ(DBL_MANT_DIG, 53);
}
TEST(ResistFingerprinting, ReducePrecision_Reciprocal) {
// This one has a rounding error in the Reciprocal case:
- process(2064.8338460, 20, 1000000);
+ process(2064.8338460, nsRFPService::TimeScale::MicroSeconds, 20);
// These are just big values
- process(1516305819, 20, 1000000);
- process(69053.12, 20, 1000000);
+ process(1516305819, nsRFPService::TimeScale::MicroSeconds, 20);
+ process(69053.12, nsRFPService::TimeScale::MicroSeconds, 20);
}
TEST(ResistFingerprinting, ReducePrecision_KnownGood) {
- process(2064.8338460, 20, 1000);
- process(69027.62, 20, 1000);
- process(69053.12, 20, 1000);
+ process(2064.8338460, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(69027.62, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(69053.12, nsRFPService::TimeScale::MilliSeconds, 20);
}
TEST(ResistFingerprinting, ReducePrecision_KnownBad) {
- process(1054.842405, 20, 1000);
- process(273.53038600000002, 20, 1000);
- process(628.66686500000003, 20, 1000);
- process(521.28919100000007, 20, 1000);
+ process(1054.842405, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(273.53038600000002, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(628.66686500000003, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(521.28919100000007, nsRFPService::TimeScale::MilliSeconds, 20);
}
TEST(ResistFingerprinting, ReducePrecision_Edge) {
- process(2611.14, 20, 1000);
- process(2611.16, 20, 1000);
- process(2612.16, 20, 1000);
- process(2601.64, 20, 1000);
- process(2595.16, 20, 1000);
- process(2578.66, 20, 1000);
+ process(2611.14, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(2611.16, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(2612.16, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(2601.64, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(2595.16, nsRFPService::TimeScale::MilliSeconds, 20);
+ process(2578.66, nsRFPService::TimeScale::MilliSeconds, 20);
}
TEST(ResistFingerprinting, ReducePrecision_Expectations) {
double result;
- result = nsRFPService::ReduceTimePrecisionImpl(2611.14, 20, 1000, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(2611.14, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2611.14);
+ result = nsRFPService::ReduceTimePrecisionImpl(2611.145, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
ASSERT_EQ(result, 2611.14);
- result = nsRFPService::ReduceTimePrecisionImpl(2611.145, 20, 1000, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(2611.141, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
ASSERT_EQ(result, 2611.14);
- result = nsRFPService::ReduceTimePrecisionImpl(2611.141, 20, 1000, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(2611.15999, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2611.14);
+ result = nsRFPService::ReduceTimePrecisionImpl(2611.15, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
ASSERT_EQ(result, 2611.14);
- result = nsRFPService::ReduceTimePrecisionImpl(2611.15999, 20, 1000, TimerPrecisionType::All);
- ASSERT_EQ(result, 2611.14);
- result = nsRFPService::ReduceTimePrecisionImpl(2611.15, 20, 1000, TimerPrecisionType::All);
- ASSERT_EQ(result, 2611.14);
- result = nsRFPService::ReduceTimePrecisionImpl(2611.13, 20, 1000, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(2611.13, nsRFPService::TimeScale::MilliSeconds, 20, TimerPrecisionType::All);
ASSERT_EQ(result, 2611.12);
}
+TEST(ResistFingerprinting, ReducePrecision_ExpectedLossOfPrecision) {
+ double result;
+ // We lose integer precision at 9007199254740992 - let's confirm that.
+ result = nsRFPService::ReduceTimePrecisionImpl(9007199254740992.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
+ ASSERT_EQ(result, 9007199254740990.0);
+ // 9007199254740995 is approximated to 9007199254740996
+ result = nsRFPService::ReduceTimePrecisionImpl(9007199254740995.0, nsRFPService::TimeScale::MicroSeconds, 5, TimerPrecisionType::All);
+ ASSERT_EQ(result, 9007199254740996);
+ // 9007199254740999 is approximated as 9007199254741000
+ result = nsRFPService::ReduceTimePrecisionImpl(9007199254740999.0, nsRFPService::TimeScale::MicroSeconds, 5, 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);
+ ASSERT_EQ(result, 9007199254743564.0);
+}
+
// Use an ugly but simple hack to turn an integer-based rand()
-// function to a double-based one
+// function to a double-based one.
#define RAND_DOUBLE (rand() * (rand() / (double)rand()))
+// If you're doing logging, you really don't want to run this test.
+#define RUN_AGGRESSIVE false
+
TEST(ResistFingerprinting, ReducePrecision_Aggressive) {
+ if(!RUN_AGGRESSIVE) {
+ return;
+ }
+
for (int i=0; i<10000; i++) {
// Test three different time magnitudes, with decimals.
// Note that we need separate variables for the different units, as scaling
- // them after calculating them will erase effects of approximation
- // A magnitude in the seconds since epoch range
- double time1_s = fmod(RAND_DOUBLE, 1516305819);
- double time1_ms = fmod(RAND_DOUBLE, 1516305819000);
- double time1_us = fmod(RAND_DOUBLE, 1516305819000000);
- // A magnitude in the 'couple of minutes worth of milliseconds' range
- double time2_s = fmod(RAND_DOUBLE, (60 * 60 * 5));
- double time2_ms = fmod(RAND_DOUBLE, (1000 * 60 * 60 * 5));
- double time2_us = fmod(RAND_DOUBLE, (1000000 * 60 * 60 * 5));
+ // them after calculating them will erase effects of approximation.
+ // A magnitude in the seconds since epoch range.
+ double time1_s = fmod(RAND_DOUBLE, 1516305819.0);
+ double time1_ms = fmod(RAND_DOUBLE, 1516305819000.0);
+ double time1_us = fmod(RAND_DOUBLE, 1516305819000000.0);
+ // A magnitude in the 'couple of minutes worth of milliseconds' range.
+ double time2_s = fmod(RAND_DOUBLE, (60.0 * 60 * 5));
+ double time2_ms = fmod(RAND_DOUBLE, (1000.0 * 60 * 60 * 5));
+ double time2_us = fmod(RAND_DOUBLE, (1000000.0 * 60 * 60 * 5));
// A magnitude in the small range
double time3_s = fmod(RAND_DOUBLE, 10);
double time3_ms = fmod(RAND_DOUBLE, 10000);
double time3_us = fmod(RAND_DOUBLE, 10000000);
- // Test two precision magnitudes, no decimals
- // A magnitude in the high milliseconds
+ // Test two precision magnitudes, no decimals.
+ // A magnitude in the high milliseconds.
double precision1 = rand() % 250000;
- // a magnitude in the low microseconds
+ // a magnitude in the low microseconds.
double precision2 = rand() % 200;
- //printf("%.*f, %.*f, %.*f\n", DBL_DIG-1, time1, DBL_DIG-1, time2, DBL_DIG-1, time3);
- process(time1_s, precision1, 1000000);
- process(time1_s, precision2, 1000000);
- process(time2_s, precision1, 1000000);
- process(time2_s, precision2, 1000000);
- process(time3_s, precision1, 1000000);
- process(time3_s, precision2, 1000000);
+ process(time1_s, nsRFPService::TimeScale::Seconds, precision1);
+ process(time1_s, nsRFPService::TimeScale::Seconds, precision2);
+ process(time2_s, nsRFPService::TimeScale::Seconds, precision1);
+ process(time2_s, nsRFPService::TimeScale::Seconds, precision2);
+ process(time3_s, nsRFPService::TimeScale::Seconds, precision1);
+ process(time3_s, nsRFPService::TimeScale::Seconds, precision2);
- process(time1_ms, precision1, 1000);
- process(time1_ms, precision2, 1000);
- process(time2_ms, precision1, 1000);
- process(time2_ms, precision2, 1000);
- process(time3_ms, precision1, 1000);
- process(time3_ms, precision2, 1000);
+ process(time1_ms, nsRFPService::TimeScale::MilliSeconds, precision1);
+ process(time1_ms, nsRFPService::TimeScale::MilliSeconds, precision2);
+ process(time2_ms, nsRFPService::TimeScale::MilliSeconds, precision1);
+ process(time2_ms, nsRFPService::TimeScale::MilliSeconds, precision2);
+ process(time3_ms, nsRFPService::TimeScale::MilliSeconds, precision1);
+ process(time3_ms, nsRFPService::TimeScale::MilliSeconds, precision2);
- process(time1_us, precision1, 1);
- process(time1_us, precision2, 1);
- process(time2_us, precision1, 1);
- process(time2_us, precision2, 1);
- process(time3_us, precision1, 1);
- process(time3_us, precision2, 1);
+ process(time1_us, nsRFPService::TimeScale::MicroSeconds, precision1);
+ process(time1_us, nsRFPService::TimeScale::MicroSeconds, precision2);
+ process(time2_us, nsRFPService::TimeScale::MicroSeconds, precision1);
+ process(time2_us, nsRFPService::TimeScale::MicroSeconds, precision2);
+ process(time3_us, nsRFPService::TimeScale::MicroSeconds, precision1);
+ process(time3_us, nsRFPService::TimeScale::MicroSeconds, precision2);
}
}