--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -14,8 +14,12 @@ FINAL_LIBRARY = 'xul'
EXPORTS += [
'nsRFPService.h',
]
EXTRA_JS_MODULES += [
'LanguagePrompt.jsm',
]
+
+LOCAL_INCLUDES += [
+ '/security/pkix/include',
+]
\ No newline at end of file
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -19,17 +19,19 @@
#include "nsCOMPtr.h"
#include "nsCoord.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#include "nsPrintfCString.h"
-#include "nsICryptoHash.h"
+#include "ScopedNSSTypes.h"
+#include "nsNSSComponent.h"
+
#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIRandomGenerator.h"
#include "nsIXULAppInfo.h"
#include "nsIXULRuntime.h"
#include "nsJSUtils.h"
@@ -129,75 +131,78 @@ nsRFPService::IsTimerPrecisionReductionE
if (aType == TimerPrecisionType::RFPOnly) {
return IsResistFingerprintingEnabled();
}
return (sPrivacyTimerPrecisionReduction || IsResistFingerprintingEnabled()) &&
TimerResolution() > 0;
}
-#define HASH_DIGEST_SIZE_BITS (256)
-#define HASH_DIGEST_SIZE_BYTES (HASH_DIGEST_SIZE_BITS / 8)
+#define AES_BLOCK_SIZE_BITS (128)
+#define AES_BLOCK_SIZE_BYTES (AES_BLOCK_SIZE_BITS / 8)
// TODO: Fix Race Conditions
class LRUCache
{
public:
LRUCache() {
- for (int i=0; i<kCacheSize; i++) {
- this->cache[i].accessTime = 0;
- this->cache[i].key = 0xFFFFFFFFFFFFFFFF;
- this->cache[i].data = nullptr;
+ for (auto & cacheEntry : this->cache) {
+ cacheEntry.accessTime = 0;
+ cacheEntry.key = 0xFFFFFFFFFFFFFFFF;
+ cacheEntry.data = nullptr;
}
}
- shared_ptr<nsAutoCStringN<HASH_DIGEST_SIZE_BYTES>> get(long long aKey) {
- for (int i=0; i<kCacheSize; i++) {
- if (this->cache[i].key == aKey) {
- this->cache[i].accessTime = PR_Now();
+ shared_ptr<uint8_t> get(long long aKey) {
+ for (auto & cacheEntry : this->cache) {
+ if (cacheEntry.key == aKey) {
+ cacheEntry.accessTime = PR_Now();
#if defined(DEBUG)
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
- ("LRU Cache HIT with %lli == %lli index %i",
- aKey, this->cache[i].key, i));
+ ("LRU Cache HIT with %lli == %lli",
+ aKey, cacheEntry.key));
#endif
- return this->cache[i].data;
+ return cacheEntry.data;
}
}
return nullptr;
}
- void store(long long aKey, nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> aValue) {
+ shared_ptr<uint8_t> store(long long aKey, uint8_t* data) {
PRTime lowestTime;
memset(&lowestTime, 0xFF, sizeof(PRTime));
lowestTime &= ~(1ULL << ((sizeof(PRTime) * 8) - 1));
int lowestKey = 0;
for (int i=0; i<kCacheSize; i++) {
if (this->cache[i].accessTime < lowestTime) {
lowestTime = this->cache[i].accessTime;
lowestKey = i;
}
}
this->cache[lowestKey].key = aKey;
- this->cache[lowestKey].data = make_shared<nsAutoCStringN<HASH_DIGEST_SIZE_BYTES>>(aValue);
+ this->cache[lowestKey].data = std::shared_ptr<uint8_t>(new uint8_t[AES_BLOCK_SIZE_BYTES], std::default_delete<uint8_t[]>());
+ memcpy(this->cache[lowestKey].data.get(), data, AES_BLOCK_SIZE_BYTES);
this->cache[lowestKey].accessTime = PR_Now();
#if defined(DEBUG)
MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
("LRU Cache STORE with %lli index %i",
aKey, lowestKey));
#endif
+
+ return this->cache[lowestKey].data;
}
private:
struct cache_entry {
long long key;
PRTime accessTime = 0;
- shared_ptr<nsAutoCStringN<HASH_DIGEST_SIZE_BYTES>> data;
+ shared_ptr<uint8_t> data;
};
static const int kCacheSize = 45;
cache_entry cache[kCacheSize];
};
/*
* The purpose of this function is to deterministicly generate a random midpoint
@@ -258,125 +263,175 @@ nsresult
nsRFPService::RandomMidpoint(long long aClampedTime,
long long aResolutionUSec,
long long* aMidpoint,
void* aContextPointer,
uint8_t * aSecretSeed /* = nullptr */)
{
nsresult rv;
const int kSeedSize = 16;
- const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
+ const int kClampTimesPerDigest = AES_BLOCK_SIZE_BITS / 32;
static uint8_t * secretSeed = nullptr;
+ static bool doubleCheckNSS = false;
+ static UniquePK11SlotInfo slot;
+ static UniquePLArenaPool arena;
+ static UniquePK11SymKey symKey;
+
if (!sCache) {
sCache = new LRUCache();
}
/*
- * Below, we will call a cryptographic hash function. That's expensive. We look for ways to
- * make it more efficient.
+ * Below, we will use AES in CTR Mode as a DRBG.
+ *
+ * This is a non-stand use of crypto, so it seems dangerous. The primary security guarentee
+ * we want to make is that if an attacker observes prior outputs, they cannot predict future
+ * ones. (Technically the attacker cannot directly observe outputs, they would need to
+ * calculate the output from a jittered clock, but we'll assume they can do this even
+ * though it seems quit difficult.)
+ *
+ * CTR works by XORing the 'Intermediate Value' (result of encrypting a counter) with the
+ * Plaintext. We need to assign the AES Key, the IV, and the Plaintext from amoung our
+ * variables of a random seed, the context pointer (currently 0!), and the clamped time.
+ *
+ * Clearly the AES Key needs to be completely random; we make it the random seed.
*
- * We only need as much output from the hash function as the maximum resolution we will
- * ever support, because we will reduce the output modulo that value. The maximum resolution
+ * For now, in this iteration before we have a real context pointer, we choose to make the
+ * IV the Clamped Time, and encrypt an all-zero plaintext. After observing an output, an
+ * attacker will learn the plaintext and resulting intermediate value (and ciphertext), but
+ * will be unable to predict future values (which would amount to a Known-plaintext attack
+ * on AES.) In the future, when we have a context pointer and know what its value will be
+ * (i.e. a random vaue or a pointer or something else) we will revisit.
+ *
+ * Besides the security concerns, it's expensive. We look for ways to make it more efficient.
+ *
+ * The first is to understand how we can share the results of one encryption among other
+ * calls. This comes to a choice of what values we make the Key, IV, and Plaintext - but
+ * we don't want to skimp on the security there. Right now, encrypting the clamped time
+ * with no context pointer means we can reuse the value across different contexts.
+ *
+ * We only need as much output from the DRBG as the maximum time precision we will
+ * ever support, because we will reduce the output modulo that value. The maximum precision
* we think is likely is in the low seconds value, or about 1-10 million microseconds.
* 2**24 is 16 million, so we only need 24 bits of output. Practically speaking though,
* it's way easier to work with 32 bits.
*
- * So we're using 32 bits of output and throwing away the other DIGEST_SIZE - 32 (in the case of
- * SHA-256, DIGEST_SIZE is 256.) That's a lot of waste.
+ * So we're using 32 bits of output and throwing away the other BLOCK_SIZE - 32 (in the case of
+ * AES-CTR, BLOCK_SIZE is 128.) That's 96 bits of waste.
*
- * Instead of throwing it away, we're going to use all of it. We can handle DIGEST_SIZE / 32
- * Clamped Time's per hash function - call that , so we reduce aClampedTime to a multiple of
+ * Instead of throwing it away, we're going to use all of it. We can handle BLOCK_SIZE / 32
+ * Clamped Time's per DRBG function-call. So we reduce aClampedTime to a multiple of
* kClampTimesPerDigest (just like we reduced the real time value to aClampedTime!)
*
- * Then we hash _that_ value (assuming it's not in the cache) and index into the digest result
- * the appropriate bit offset.
+ * Then we use _that_ value as input to the DRBG (assuming it's not in the cache) and
+ * index into the output result the appropriate bit offset.
*/
long long reducedResolution = aResolutionUSec * kClampTimesPerDigest;
long long extraClampedTime = (aClampedTime / reducedResolution) * reducedResolution;
- // While this code compiled and runs I am really unsure I am doing the right thing
- // with shared_ptr and string types.
- nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> fred;
- shared_ptr<nsAutoCStringN<HASH_DIGEST_SIZE_BYTES>> bob = sCache->get(extraClampedTime);
+ shared_ptr<uint8_t> deterministiclyRandomBytes = sCache->get(extraClampedTime);
- if(!bob) { // Cache Miss =(
+ if(deterministiclyRandomBytes == nullptr) { // Cache Miss =(
// If someone has pased in the testing-only parameter, replace our seed with it
if (aSecretSeed != nullptr) {
if (secretSeed) {
+ // If we had an existing seed, we need to wipe it and the AES key derived from it
free(secretSeed);
+ PK11_FreeSymKey(symKey.release());
}
secretSeed = new uint8_t[kSeedSize];
memcpy(secretSeed, aSecretSeed, kSeedSize);
}
// If we don't have a seed, we need to get one
if(!secretSeed) {
nsCOMPtr<nsIRandomGenerator> randomGenerator =
do_GetService("@mozilla.org/security/random-generator;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = randomGenerator->GenerateRandomBytes(kSeedSize, &secretSeed);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
- /*
- * Use a cryptographicly secure hash function, but do _not_ use an HMAC.
- * Obviously we're not using this data for authentication purposes, but
- * even still an HMAC is a perfect fit here, as we're hashing a value
- * using a seed that never changes, and an input that does. So why not
- * use one?
- *
- * Basically - we don't need to, it's two invocations of the hash function,
- * and speed really counts here.
- *
- * With authentication off the table, the properties we would get by
- * using an HMAC here would be:
- * - Resistence to length extension
- * - Resistence to collision attacks on the underlying hash function
- * - Resistence to chosen prefix attacks
- *
- * There is no threat of length extension here. Nor is there any real
- * practical threat of collision: not only are we using a good hash
- * function (you may mock me in 10 years if it is broken) but we don't
- * provide the attacker much control over the input. Nor do we let them
- * have the prefix.
- */
+ // It's possible that NSS hasn't been initialized, observed when running gtests
+ // This will double check that it has been initialized. It will assert if not done
+ // in the parent process, which doesn't make any sense to me.
+ // REVIEWER: This is certainly wrong, but I'm not sure what it is I'm supposed to do.
+ // REVIEWER: gtest complains:
+ // WARNING: NSS_Shutdown failed - some NSS resources are still in use (see bugs 1417680 and 1230312): file xpcom/build/XPCOMInit.cpp, line 1019
+ // Assertion failed: (GLOBAL(int, mutexIsInit)), function sqlite3MutexAlloc, file db/sqlite3/src/sqlite3.c, line 23769.
+ if(!doubleCheckNSS && XRE_IsParentProcess()) {
+ nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
+ MOZ_ASSERT(component);
+ doubleCheckNSS = true;
+ }
- // Then hash extraClampedTime and store it in the cache
- nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
- NS_ENSURE_SUCCESS(rv, rv);
-
- rv = hasher->Init(nsICryptoHash::SHA256);
- NS_ENSURE_SUCCESS(rv, rv);
-
- rv = hasher->Update(secretSeed, kSeedSize);
- NS_ENSURE_SUCCESS(rv, rv);
+ // TODO: Make this threadsafe
+ // If we don't have an AES key set up, set that up
+ if (!slot) {
+ slot = UniquePK11SlotInfo(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+ }
+ if (!arena) {
+ arena = UniquePLArenaPool(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ MOZ_ASSERT(arena);
+ }
+ if (!symKey || !symKey.get()) {
+ SECItem keyItem = { siBuffer, nullptr, 0 };
+ if (!::SECITEM_AllocItem(arena.get(), &keyItem, (128 / 8))) {
+ return NS_ERROR_FAILURE;
+ }
+ memcpy(keyItem.data, secretSeed, (128 / 8));
+
+ symKey = UniquePK11SymKey(PK11_ImportSymKey(slot.get(), CKM_AES_CTR,
+ PK11_OriginUnwrap, CKA_ENCRYPT,
+ &keyItem, nullptr));
+ }
+ MOZ_ASSERT(symKey.get());
- rv = hasher->Update((const uint8_t *)&extraClampedTime, sizeof(extraClampedTime));
- NS_ENSURE_SUCCESS(rv, rv);
-
- nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> derivedSecret;
- rv = hasher->Finish(false, derivedSecret);
- NS_ENSURE_SUCCESS(rv, rv);
+ // Set up CTR Parameters
+ SECItem param = { siBuffer, nullptr, 0 };
+ CK_AES_CTR_PARAMS ctrParams;
+ // REVIEWER: This is the length of the entire IV right? Encompassing nonce || counter
+ ctrParams.ulCounterBits = 128;
+ // This is the value of the entire IV, normally encompassing 64 bits of nonce and 64
+ // bits of counter. We set the IV to be the clamped time
+ memset(&ctrParams.cb, 0x00, 16);
+ memcpy(&ctrParams.cb, &aClampedTime, sizeof(aClampedTime));
+ param.data = (unsigned char*) &ctrParams;
+ param.len = sizeof(ctrParams);
- // Finally, store it in the cache
- sCache->store(extraClampedTime, derivedSecret);
- fred = derivedSecret;
- }
- else { // Cache Hit!
- fred = *bob;
+ // Plaintext
+ // We set the plaintext to be all zeros
+ uint8_t plaintext[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ // Where to store the result
+ uint8_t ciphertext[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ // Perform the encryption/decryption
+ // REVIEWER: Is this thread-safe? Can we call this multiple times at the same time with the same key object?
+ uint32_t outLen = 0;
+ rv = MapSECStatus(PK11_Encrypt(symKey.get(), CKM_AES_CTR, ¶m,
+ ciphertext, &outLen, sizeof(ciphertext),
+ plaintext, sizeof(plaintext)));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ deterministiclyRandomBytes = sCache->store(extraClampedTime, ciphertext);
}
- // Offset the appropriate index into the hash output, and then turn it into a random midpoint
+ // Offset the appropriate index into the DRBG output, and then turn it into a random midpoint
// between 0 and aResolutionUSec
int byteOffset = ((aClampedTime - extraClampedTime) / aResolutionUSec) * 4;
- uint32_t deterministiclyRandomValue = *BitwiseCast<uint32_t*>(PromiseFlatCString(fred).get() + byteOffset);
- deterministiclyRandomValue %= aResolutionUSec;
- *aMidpoint = deterministiclyRandomValue;
+ uint32_t deterministiclyRandomUint = *BitwiseCast<uint32_t*>(deterministiclyRandomBytes.get() + byteOffset);
+ deterministiclyRandomUint %= aResolutionUSec;
+ *aMidpoint = deterministiclyRandomUint;
return NS_OK;
}
/**
* Given a precision value, this function will reduce a given input time to the nearest
* multiple of that precision.
@@ -445,17 +500,17 @@ nsRFPService::ReduceTimePrecisionImpl(
double ret = double(clampedAndJittered) / (1000000.0 / aTimeScale);
#if defined(DEBUG)
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)",
DBL_DIG-1, aTime, DBL_DIG-1, timeScaled, timeAsInt, resolutionAsInt, DBL_DIG-1, aResolutionUSec,
- floor(double(timeAsInt) / resolutionAsInt), clamped, tmp_jitter, midpoint, clampedAndJittered, DBL_DIG-1, ret));
+ (long long)floor(double(timeAsInt) / resolutionAsInt), clamped, tmp_jitter, midpoint, clampedAndJittered, DBL_DIG-1, ret));
#endif
return ret;
}
/* static */
--- a/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
+++ b/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
@@ -214,93 +214,90 @@ TEST(ResistFingerprinting, ReducePrecisi
bool jitterEnabled = setupJitter(true);
/*
* Here's our test vector. First we set the secret to the 16 byte value
* 0x000102030405060708 0x101112131415161718
*
* Then we work with a resolution of 500 us which will bucket things as such:
* Per-Clamp Buckets: [0, 500], [500, 1000], ...
- * Per-Hash Buckets: [0, 4000], [4000, 8000], ...
+ * Per-Hash Buckets: [2000, 2500], [2500, 3000], ...
*
- * The first two hash values should be
- * 0: SHA-256(0x000102030405060708 || 0x101112131415161718 || 0x0000000000000000)
- * 32ca0459 bdb518be c72096dc 2667cd7a a76f94e4 c33fa679 9a1bd499 bfa4ec57
- * 4000: SHA-256(0x000102030405060708 || 0x101112131415161718 || 0xa00f000000000000)
- * bd0bf282 120fd8c2 459c4d05 0170179c 25136f6f 70db5c82 5807558d 148c7745
+ * The first two AES-CTR cihertext values should be
+ * 0: AES-CTR k=0x00010203040506071011121314151617 iv=0x00000000000000000000000000000000 E(0x00000000000000000000000000000000)
+ * e7e9a79f faadd53e fea23818 e9dfd7e8
+ * 2000: AES-CTR k=0x00010203040506071011121314151617 iv=0xD0070000000000000000000000000000 E(0x00000000000000000000000000000000)
+ * af524854 dd3829dd ab57fc33 97aa46b9
*
- * The midpoints are:
- * 0 : 32ca0459 % 500 = 130
- * 500 : bdb518be % 500 = 429
- * 1500: c72096dc % 500 = 311
- * 2000: 2667cd7a % 500 = 138
- * 2500: a76f94e4 % 500 = 159
- * 3000: c33fa679 % 500 = 435
- * 3500: 9a1bd499 % 500 = 246
- * 4000: bfa4ec57 % 500 = 463
- * 4500: bd0bf282 % 500 = 297
- * 5000: 120fd8c2 % 500 = 38
- * 5500: 459c4d05 % 500 = 357
+ * The midpoints are: (one much switch endianness of the hexadecimal values)
+ * 0 : e7e9a79f % 500 = 235
+ * 500 : faadd53e % 500 = 98
+ * 1500: fea23818 % 500 = 426
+ * 2000: e9dfd7e8 % 500 = 173
+ * 2500: af524854 % 500 = 403
+ * 3000: dd3829dd % 500 = 269
+ * 3500: ab57fc33 % 500 = 31
+ * 4000: 97aa46b9 % 500 = 151
*/
// 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, nullptr, hardcodedSecret);
// Run the test vectors
double result;
result = nsRFPService::ReduceTimePrecisionImpl(1, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 0);
- result = nsRFPService::ReduceTimePrecisionImpl(129, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(234, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 0);
- result = nsRFPService::ReduceTimePrecisionImpl(130, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(235, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
- result = nsRFPService::ReduceTimePrecisionImpl(131, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(236, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(499, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
result = nsRFPService::ReduceTimePrecisionImpl(500, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
- result = nsRFPService::ReduceTimePrecisionImpl(600, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(550, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
- result = nsRFPService::ReduceTimePrecisionImpl(928, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(597, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 500);
- result = nsRFPService::ReduceTimePrecisionImpl(929, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(598, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 1000);
- result = nsRFPService::ReduceTimePrecisionImpl(930, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(599, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 1000);
- result = nsRFPService::ReduceTimePrecisionImpl(1255, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ result = nsRFPService::ReduceTimePrecisionImpl(1425, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
ASSERT_EQ(result, 1000);
- result = nsRFPService::ReduceTimePrecisionImpl(4000, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4000);
- result = nsRFPService::ReduceTimePrecisionImpl(4295, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4000);
- result = nsRFPService::ReduceTimePrecisionImpl(4296, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4000);
- result = nsRFPService::ReduceTimePrecisionImpl(4297, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4500);
- result = nsRFPService::ReduceTimePrecisionImpl(4298, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4500);
- result = nsRFPService::ReduceTimePrecisionImpl(4499, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4500);
+ result = nsRFPService::ReduceTimePrecisionImpl(2000, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2000);
+ result = nsRFPService::ReduceTimePrecisionImpl(2402, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2000);
+ result = nsRFPService::ReduceTimePrecisionImpl(2402.9, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2000);
+ result = nsRFPService::ReduceTimePrecisionImpl(2403, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2500);
+ result = nsRFPService::ReduceTimePrecisionImpl(2404, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2500);
+ result = nsRFPService::ReduceTimePrecisionImpl(2499, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2500);
- result = nsRFPService::ReduceTimePrecisionImpl(4500, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4500);
- result = nsRFPService::ReduceTimePrecisionImpl(4536, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4500);
- result = nsRFPService::ReduceTimePrecisionImpl(4537, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 4500);
- result = nsRFPService::ReduceTimePrecisionImpl(4538, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 5000);
- result = nsRFPService::ReduceTimePrecisionImpl(4539, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 5000);
- result = nsRFPService::ReduceTimePrecisionImpl(5106, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
- ASSERT_EQ(result, 5000);
+ result = nsRFPService::ReduceTimePrecisionImpl(2500, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2500);
+ result = nsRFPService::ReduceTimePrecisionImpl(2768.7, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2500);
+ result = nsRFPService::ReduceTimePrecisionImpl(2768.9, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 2500);
+ result = nsRFPService::ReduceTimePrecisionImpl(2769, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 3000);
+ result = nsRFPService::ReduceTimePrecisionImpl(2769.1, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 3000);
+ result = nsRFPService::ReduceTimePrecisionImpl(3030, nsRFPService::TimeScale::MicroSeconds, 500, nullptr, TimerPrecisionType::All);
+ ASSERT_EQ(result, 3000);
cleanupJitter(jitterEnabled);
}