wifi_scan_cache: Correctly calculate ageMs
Prior code could produce very large ageMs values due to improper early
truncation of the 64-bit nanosecond timestamp. The updated version still
avoids 64-bit integer division, but produces correct results and also
guards against providing unreasonably large values even if the provided
time is invalid.
Bug: 382566782
Test: atest chre_unit_tests before & after patch
Change-Id: I8811255906c2bc97dab11fc75d3cecae54e44aad
diff --git a/pal/util/tests/wifi_scan_cache_test.cc b/pal/util/tests/wifi_scan_cache_test.cc
index 5c0dcec..ab55305 100644
--- a/pal/util/tests/wifi_scan_cache_test.cc
+++ b/pal/util/tests/wifi_scan_cache_test.cc
@@ -20,15 +20,23 @@
#include <cinttypes>
#include <cstring>
+#include "chre/platform/linux/system_time.h"
#include "chre/platform/log.h"
#include "chre/platform/shared/pal_system_api.h"
#include "chre/util/fixed_size_vector.h"
#include "chre/util/macros.h"
#include "chre/util/nanoapp/wifi.h"
#include "chre/util/optional.h"
+#include "chre/util/time.h"
#include "chre_api/chre/common.h"
#include "gtest/gtest.h"
+using chre::Milliseconds;
+using chre::Nanoseconds;
+using chre::Seconds;
+using chre::platform_linux::clearMonotonicTimeOverride;
+using chre::platform_linux::overrideMonotonicTime;
+
namespace {
/************************************************
@@ -72,6 +80,7 @@
void TearDown() override {
chreWifiScanCacheDeinit();
+ clearMonotonicTimeOverride();
}
void clearTestState() {
@@ -489,3 +498,56 @@
EXPECT_EQ(gWifiScanResponse->errorCode, CHRE_ERROR_NONE);
EXPECT_EQ(gWifiScanResultList.size(), 2);
}
+
+TEST_F(WifiScanCacheTests, AgeCalculatedCorrectly) {
+ constexpr auto kStartTime = Seconds(4);
+ overrideMonotonicTime(kStartTime);
+ beginDefaultWifiCache(nullptr /* scannedFreqList */,
+ 0 /* scannedFreqListLen */);
+
+ overrideMonotonicTime(kStartTime + Milliseconds(100));
+ chreWifiScanResult result = {};
+ chreWifiScanCacheScanEventAdd(&result);
+
+ overrideMonotonicTime(kStartTime + Milliseconds(500));
+ chreWifiScanCacheScanEventEnd(CHRE_ERROR_NONE);
+
+ ASSERT_EQ(gWifiScanResultList.size(), 1);
+ EXPECT_EQ(gWifiScanResultList[0].ageMs, 500 - 100);
+}
+
+TEST_F(WifiScanCacheTests, AgeLongUptime) {
+ constexpr auto kStartTime = Seconds(60 * 60 * 24 * 50); // 50 days
+ overrideMonotonicTime(kStartTime);
+ beginDefaultWifiCache(nullptr /* scannedFreqList */,
+ 0 /* scannedFreqListLen */);
+
+ overrideMonotonicTime(kStartTime + Milliseconds(500));
+ chreWifiScanResult result = {};
+ chreWifiScanCacheScanEventAdd(&result);
+
+ overrideMonotonicTime(kStartTime + Milliseconds(4000));
+ chreWifiScanCacheScanEventEnd(CHRE_ERROR_NONE);
+
+ ASSERT_EQ(gWifiScanResultList.size(), 1);
+ EXPECT_EQ(gWifiScanResultList[0].ageMs, 4000 - 500);
+}
+
+TEST_F(WifiScanCacheTests, AgeAvoidsUnderflow) {
+ constexpr auto kStartTime = Seconds(30);
+ constexpr auto kEndTime = kStartTime + Seconds(5);
+ overrideMonotonicTime(kStartTime);
+ beginDefaultWifiCache(nullptr /* scannedFreqList */,
+ 0 /* scannedFreqListLen */);
+
+ overrideMonotonicTime(Nanoseconds(0));
+ chreWifiScanResult result = {};
+ chreWifiScanCacheScanEventAdd(&result);
+
+ overrideMonotonicTime(kEndTime);
+ chreWifiScanCacheScanEventEnd(CHRE_ERROR_NONE);
+
+ ASSERT_EQ(gWifiScanResultList.size(), 1);
+ EXPECT_LT(gWifiScanResultList[0].ageMs,
+ Milliseconds(kEndTime - kStartTime).getMilliseconds());
+}
diff --git a/pal/util/wifi_scan_cache.c b/pal/util/wifi_scan_cache.c
index a927bd2..4a44071 100644
--- a/pal/util/wifi_scan_cache.c
+++ b/pal/util/wifi_scan_cache.c
@@ -21,6 +21,17 @@
#include "chre/util/macros.h"
/************************************************
+ * Constants
+ ***********************************************/
+
+// Constants used in chreWifiScanCacheInitialAgeMsValue() and
+// chreWifiScanCacheFinalizeAgeMs()
+// These values are selected because msec = nsec / 1000000 and
+// 1000000 = 64 * 15625 = (1 << 6) * 15625
+#define CHRE_WIFI_SCAN_CACHE_AGE_MS_SHIFT (6)
+#define CHRE_WIFI_SCAN_CACHE_AGE_MS_DIVISOR (15625)
+
+/************************************************
* Prototypes
***********************************************/
@@ -46,6 +57,8 @@
bool scanMonitoringEnabled;
uint32_t scannedFreqList[CHRE_WIFI_FREQUENCY_LIST_MAX_LEN];
+
+ uint64_t scanStartTimeNs;
};
/************************************************
@@ -187,6 +200,59 @@
return foundWeakerResult;
}
+static uint32_t chreWifiScanCacheInitialAgeMsValue() {
+ // ageMs will be finalized via chreWifiScanCacheFinalizeAgeMs() once the scan
+ // finishes, because it is relative to the scan end time that we can't know
+ // yet. Before the end of the scan, populate ageMs with the time since the
+ // start of the scan.
+ //
+ // We avoid 64-bit integer division by:
+ // - Only considering the delta between this result and the start of the
+ // scan, which constrains the range of expected values to what should be
+ // only a few seconds
+ // - Instead of directly dividing by 1000000, we first divide by 64 (right
+ // shift by 6), then truncate to 32 bits, then later we'll do integer
+ // division by 15625 to get milliseconds
+ // - This works because x/1000000 = x/(64 * 15625) = (x/64)/15625
+ // - The largest delta we can fit here is 2^32/15625 ms = 274877 ms or
+ // about 4.5 minutes
+ uint64_t timeSinceScanStartNs =
+ (gSystemApi->getCurrentTime() - gWifiCacheState.scanStartTimeNs);
+ return (timeSinceScanStartNs >> CHRE_WIFI_SCAN_CACHE_AGE_MS_SHIFT);
+}
+
+static void chreWifiScanCacheFinalizeAgeMs() {
+ // Convert ageMs from the chreWifiScanCacheInitialAgeMsValue() to its final,
+ // correct value using the formula derived from these steps:
+ // ageMs = (referenceTimeNs - absoluteScanResultTimeNs) / 1000000
+ // = (referenceTimeNs - (scanStartTimeNs + scanOffsetNs)) / 1000000
+ // = ((referenceTimeNs - scanStartTimeNs) - scanOffsetNs) / 1000000
+ // = (scanDuration / 64 - scanOffsetNs / 64) / 15625
+ // ageMs = (scanDurationShifted - currentAgeMsValue) / 15625
+ uint64_t referenceTimeNs = gWifiCacheState.event.referenceTime;
+ uint64_t scanStartTimeNs = gWifiCacheState.scanStartTimeNs;
+ uint32_t scanDurationShifted =
+ (referenceTimeNs - scanStartTimeNs) >> CHRE_WIFI_SCAN_CACHE_AGE_MS_SHIFT;
+ if (referenceTimeNs < scanStartTimeNs) {
+ gSystemApi->log(CHRE_LOG_ERROR, "Invalid scan timestamp, clamping");
+ // Use a smaller number to avoid very large ageMs values
+ scanDurationShifted = 78125000; // 5 seconds --> 5*10e9 / 64
+ }
+
+ for (uint16_t i = 0; i < gWifiCacheState.event.resultTotal; i++) {
+ if (scanDurationShifted < gWifiCacheState.resultList[i].ageMs) {
+ gSystemApi->log(CHRE_LOG_ERROR,
+ "Invalid result timestamp %" PRIu32 " vs. %" PRIu32,
+ gWifiCacheState.resultList[i].ageMs, scanDurationShifted);
+ gWifiCacheState.resultList[i].ageMs = 0;
+ } else {
+ gWifiCacheState.resultList[i].ageMs =
+ (scanDurationShifted - gWifiCacheState.resultList[i].ageMs) /
+ CHRE_WIFI_SCAN_CACHE_AGE_MS_DIVISOR;
+ }
+ }
+}
+
/************************************************
* Public functions
***********************************************/
@@ -242,6 +308,7 @@
gWifiCacheState.scanRequestedByChre = scanRequestedByChre;
gWifiCacheState.started = true;
+ gWifiCacheState.scanStartTimeNs = gSystemApi->getCurrentTime();
}
if (scanRequestedByChre && !success) {
@@ -277,10 +344,8 @@
memcpy(&gWifiCacheState.resultList[index], result,
sizeof(const struct chreWifiScanResult));
- // ageMs will be properly populated in chreWifiScanCacheScanEventEnd
gWifiCacheState.resultList[index].ageMs =
- (uint32_t)gSystemApi->getCurrentTime() /
- (uint32_t)kOneMillisecondInNanoseconds;
+ chreWifiScanCacheInitialAgeMsValue();
}
void chreWifiScanCacheScanEventEnd(enum chreError errorCode) {
@@ -299,14 +364,7 @@
(gWifiCacheState.scanRequestedByChre || gScanMonitoringEnabled)) {
gWifiCacheState.event.referenceTime = gSystemApi->getCurrentTime();
gWifiCacheState.event.scannedFreqList = gWifiCacheState.scannedFreqList;
-
- uint32_t referenceTimeMs = (uint32_t)gWifiCacheState.event.referenceTime /
- (uint32_t)kOneMillisecondInNanoseconds;
- for (uint16_t i = 0; i < gWifiCacheState.event.resultTotal; i++) {
- gWifiCacheState.resultList[i].ageMs =
- referenceTimeMs - gWifiCacheState.resultList[i].ageMs;
- }
-
+ chreWifiScanCacheFinalizeAgeMs();
chreWifiScanCacheDispatchAll();
}