/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "chre/pal/util/wifi_scan_cache.h" #include #include #include #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 { /************************************************ * Prototypes ***********************************************/ void chreWifiScanResponseCallback(bool pending, uint8_t errorCode); void chreWifiScanEventCallback(struct chreWifiScanEvent *event); struct WifiScanResponse { bool pending; uint8_t errorCode; }; /************************************************ * Global variables ***********************************************/ const chrePalWifiCallbacks gChreWifiPalCallbacks = { .scanResponseCallback = chreWifiScanResponseCallback, .scanEventCallback = chreWifiScanEventCallback, }; using InputVec = std::vector; using ResultVec = chre::FixedSizeVector; chre::Optional gWifiScanResponse; ResultVec gWifiScanResultList; chre::Optional gExpectedWifiScanEvent; bool gWifiScanEventCompleted; /************************************************ * Test class ***********************************************/ class WifiScanCacheTests : public ::testing::Test { protected: void SetUp() override { clearTestState(); EXPECT_TRUE(chreWifiScanCacheInit(&chre::gChrePalSystemApi, &gChreWifiPalCallbacks)); } void TearDown() override { chreWifiScanCacheDeinit(); clearMonotonicTimeOverride(); } void clearTestState() { gExpectedWifiScanEvent.reset(); gWifiScanResponse.reset(); while (!gWifiScanResultList.empty()) { gWifiScanResultList.pop_back(); } } }; /************************************************ * Private functions ***********************************************/ void chreWifiScanResponseCallback(bool pending, uint8_t errorCode) { WifiScanResponse response = { .pending = pending, .errorCode = errorCode, }; gWifiScanResponse = response; } void chreWifiScanEventCallback(struct chreWifiScanEvent *event) { ASSERT_TRUE(gExpectedWifiScanEvent.has_value()); EXPECT_EQ(event->version, gExpectedWifiScanEvent->version); EXPECT_EQ(event->scanType, gExpectedWifiScanEvent->scanType); EXPECT_EQ(event->ssidSetSize, gExpectedWifiScanEvent->ssidSetSize); ASSERT_EQ(event->scannedFreqListLen, gExpectedWifiScanEvent->scannedFreqListLen); if (event->scannedFreqListLen > 0) { ASSERT_NE(event->scannedFreqList, nullptr); EXPECT_EQ( memcmp(gExpectedWifiScanEvent->scannedFreqList, event->scannedFreqList, event->scannedFreqListLen * sizeof(uint32_t)), 0); } EXPECT_EQ(event->radioChainPref, gExpectedWifiScanEvent->radioChainPref); EXPECT_EQ(event->eventIndex, gExpectedWifiScanEvent->eventIndex); gExpectedWifiScanEvent->eventIndex++; for (uint8_t i = 0; i < event->resultCount; i++) { const chreWifiScanResult &result = event->results[i]; gWifiScanResultList.push_back(result); } if (gWifiScanResultList.size() == event->resultTotal) { gWifiScanEventCompleted = true; } chreWifiScanCacheReleaseScanEvent(event); } void beginDefaultWifiCache(const uint32_t *scannedFreqList, uint16_t scannedFreqListLen, bool scanRequestedByChre = true) { chreWifiScanEvent event; memset(&event, 0, sizeof(chreWifiScanEvent)); event.version = CHRE_WIFI_SCAN_EVENT_VERSION; event.scanType = CHRE_WIFI_SCAN_TYPE_ACTIVE; event.scannedFreqList = scannedFreqList; event.scannedFreqListLen = scannedFreqListLen; event.radioChainPref = CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT; gExpectedWifiScanEvent = event; chreWifiScanCacheScanEventBegin( static_cast(gExpectedWifiScanEvent->scanType), gExpectedWifiScanEvent->ssidSetSize, gExpectedWifiScanEvent->scannedFreqList, gExpectedWifiScanEvent->scannedFreqListLen, gExpectedWifiScanEvent->radioChainPref, scanRequestedByChre); } void resultSpecifiedWifiCacheTest(size_t numEvents, InputVec &inputResults, ResultVec &expectedResults, const uint32_t *scannedFreqList, uint16_t scannedFreqListLen, bool scanRequestedByChre = true, bool scanMonitoringEnabled = false) { gWifiScanEventCompleted = false; beginDefaultWifiCache(scannedFreqList, scannedFreqListLen, scanRequestedByChre); for (size_t i = 0; i < numEvents; i++) { chreWifiScanCacheScanEventAdd(&inputResults[i]); } chreWifiScanCacheScanEventEnd(CHRE_ERROR_NONE); if (scanRequestedByChre) { EXPECT_TRUE(gWifiScanResponse.has_value()); EXPECT_EQ(gWifiScanResponse->pending, true); ASSERT_EQ(gWifiScanResponse->errorCode, CHRE_ERROR_NONE); } else { EXPECT_FALSE(gWifiScanResponse.has_value()); } size_t numEventsExpected = 0; if (scanRequestedByChre || scanMonitoringEnabled) { numEventsExpected = std::min( numEvents, static_cast(CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY)); ASSERT_TRUE(gWifiScanEventCompleted); } ASSERT_EQ(gWifiScanResultList.size(), numEventsExpected); for (size_t i = 0; i < gWifiScanResultList.size(); i++) { // ageMs is not known apriori expectedResults[i].ageMs = gWifiScanResultList[i].ageMs; EXPECT_EQ(memcmp(&gWifiScanResultList[i], &expectedResults[i], sizeof(chreWifiScanResult)), 0); } } void cacheDefaultWifiCacheTest(size_t numEvents, const uint32_t *scannedFreqList, uint16_t scannedFreqListLen, bool scanRequestedByChre = true, bool scanMonitoringEnabled = false) { InputVec inputResults; ResultVec expectedResults; // Generate a default set of input and expected results if not specified chreWifiScanResult result = {}; for (uint64_t i = 0; i < numEvents; i++) { result.rssi = static_cast(i); memcpy(result.bssid, &i, sizeof(result.bssid)); inputResults.push_back(result); if (!expectedResults.full()) { expectedResults.push_back(result); } else { int8_t minRssi = result.rssi; int minIdx = -1; for (uint64_t idx = 0; idx < expectedResults.size(); idx++) { if (expectedResults[idx].rssi < minRssi) { minRssi = expectedResults[idx].rssi; minIdx = idx; } } if (minIdx != -1) { expectedResults[minIdx] = result; } } } resultSpecifiedWifiCacheTest(numEvents, inputResults, expectedResults, scannedFreqList, scannedFreqListLen, scanRequestedByChre, scanMonitoringEnabled); } void testCacheDispatch(size_t numEvents, uint32_t maxScanAgeMs, bool expectSuccess) { cacheDefaultWifiCacheTest(numEvents, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); gExpectedWifiScanEvent->eventIndex = 0; gWifiScanResponse.reset(); while (!gWifiScanResultList.empty()) { gWifiScanResultList.pop_back(); } struct chreWifiScanParams params = { .scanType = CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE, .maxScanAgeMs = maxScanAgeMs, .frequencyListLen = 0, .frequencyList = nullptr, .ssidListLen = 0, .ssidList = nullptr, .radioChainPref = CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT, .channelSet = CHRE_WIFI_CHANNEL_SET_NON_DFS, }; EXPECT_EQ(chreWifiScanCacheDispatchFromCache(¶ms), expectSuccess); EXPECT_EQ(gWifiScanResponse.has_value(), expectSuccess); if (expectSuccess) { EXPECT_TRUE(gWifiScanResponse->pending); EXPECT_EQ(gWifiScanResponse->errorCode, CHRE_ERROR_NONE); } EXPECT_EQ(gWifiScanResultList.size(), expectSuccess ? numEvents : 0); } } // anonymous namespace /************************************************ * Tests ***********************************************/ TEST_F(WifiScanCacheTests, SingleWifiResultTest) { cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, MultiWifiResultTest) { cacheDefaultWifiCacheTest( CHRE_PAL_WIFI_SCAN_CACHE_MAX_RESULT_COUNT + 1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, WifiResultOverflowTest) { cacheDefaultWifiCacheTest( CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY + 42 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, WeakestRssiNotAddedToFullCacheTest) { size_t numEvents = CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY + 1; InputVec inputResults; ResultVec expectedResults; chreWifiScanResult result = {}; result.rssi = -20; uint64_t i; for (i = 0; i < CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY; i++) { memcpy(result.bssid, &i, sizeof(result.bssid)); inputResults.push_back(result); expectedResults.push_back(result); } result.rssi = -21; memcpy(result.bssid, &i, sizeof(result.bssid)); inputResults.push_back(result); resultSpecifiedWifiCacheTest(numEvents, inputResults, expectedResults, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, WeakestRssiReplacedAtEndOfFullCacheTest) { size_t numEvents = CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY + 1; InputVec inputResults; ResultVec expectedResults; chreWifiScanResult result = {}; result.rssi = -20; uint64_t i; for (i = 0; i < CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY - 1; i++) { memcpy(result.bssid, &i, sizeof(result.bssid)); inputResults.push_back(result); expectedResults.push_back(result); } result.rssi = -21; memcpy(result.bssid, &i, sizeof(result.bssid)); i++; inputResults.push_back(result); result.rssi = -19; memcpy(result.bssid, &i, sizeof(result.bssid)); i++; inputResults.push_back(result); expectedResults.push_back(result); resultSpecifiedWifiCacheTest(numEvents, inputResults, expectedResults, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, EmptyWifiResultTest) { cacheDefaultWifiCacheTest(0 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, FailedWifiCacheTest) { beginDefaultWifiCache(nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); chreWifiScanCacheScanEventEnd(CHRE_ERROR); EXPECT_TRUE(gWifiScanResponse.has_value()); EXPECT_FALSE(gWifiScanResponse->pending); EXPECT_EQ(gWifiScanResponse->errorCode, CHRE_ERROR); EXPECT_EQ(gWifiScanResultList.size(), 0); } TEST_F(WifiScanCacheTests, FrequencyListTest) { const uint32_t freqList[2] = {5210, 5240}; cacheDefaultWifiCacheTest(1 /* numEvents */, freqList, ARRAY_SIZE(freqList)); } TEST_F(WifiScanCacheTests, InvalidFrequencyListTest) { beginDefaultWifiCache(nullptr /* scannedFreqList */, 1 /* scannedFreqListLen */); EXPECT_TRUE(gWifiScanResponse.has_value()); EXPECT_FALSE(gWifiScanResponse->pending); EXPECT_EQ(gWifiScanResponse->errorCode, CHRE_ERROR_INVALID_ARGUMENT); EXPECT_EQ(gWifiScanResultList.size(), 0); } TEST_F(WifiScanCacheTests, SequentialWifiResultTest) { cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); clearTestState(); cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */); } TEST_F(WifiScanCacheTests, ScanMonitorDisabledTest) { cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */, false /* scanRequestedByChre */, false /* scanMonitoringEnabled */); } TEST_F(WifiScanCacheTests, ScanMonitorEnabledTest) { chreWifiScanCacheConfigureScanMonitor(true /* enable */); cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */, false /* scanRequestedByChre */, true /* scanMonitoringEnabled */); } TEST_F(WifiScanCacheTests, ScanMonitorEnableDisableTest) { chreWifiScanCacheConfigureScanMonitor(true /* enable */); cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */, false /* scanRequestedByChre */, true /* scanMonitoringEnabled */); clearTestState(); chreWifiScanCacheConfigureScanMonitor(false /* enable */); cacheDefaultWifiCacheTest(1 /* numEvents */, nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */, false /* scanRequestedByChre */, false /* scanMonitoringEnabled */); } TEST_F(WifiScanCacheTests, CacheDispatchTest) { testCacheDispatch(1 /* numEvents */, 5000 /* maxScanAgeMs */, true /* expectSuccess */); } TEST_F(WifiScanCacheTests, ZeroMaxScanAgeCacheDispatchTest) { testCacheDispatch(1 /* numEvents */, 0 /* maxScanAgeMs */, false /* expectSuccess */); } TEST_F(WifiScanCacheTests, DuplicateScanResultTest) { beginDefaultWifiCache(nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */, true /* scanRequestedByChre */); chreWifiScanResult result = {}; result.rssi = -98; result.primaryChannel = 5270; std::string sampleSsid = "Test ssid"; memcpy(result.ssid, sampleSsid.c_str(), sampleSsid.length()); result.ssidLen = sampleSsid.length(); std::string sampleBssid = "12:34:56:78:9a:bc"; memcpy(result.bssid, sampleBssid.c_str(), sampleBssid.length()); chreWifiScanResult result2 = {}; result2.rssi = -98; result2.primaryChannel = 5270; std::string sampleSsid2 = "Test ssid 2"; memcpy(result2.ssid, sampleSsid2.c_str(), sampleSsid2.length()); result2.ssidLen = sampleSsid2.length(); std::string sampleBssid2 = "34:56:78:9a:bc:de"; memcpy(result2.bssid, sampleBssid2.c_str(), sampleBssid2.length()); chreWifiScanCacheScanEventAdd(&result); chreWifiScanCacheScanEventAdd(&result2); chreWifiScanCacheScanEventAdd(&result); chreWifiScanCacheScanEventEnd(CHRE_ERROR_NONE); EXPECT_TRUE(gWifiScanResponse.has_value()); EXPECT_EQ(gWifiScanResponse->pending, true); ASSERT_EQ(gWifiScanResponse->errorCode, CHRE_ERROR_NONE); ASSERT_EQ(gWifiScanResultList.size(), 2); result.ageMs = gWifiScanResultList[0].ageMs; EXPECT_EQ( memcmp(&gWifiScanResultList[0], &result, sizeof(chreWifiScanResult)), 0); result2.ageMs = gWifiScanResultList[1].ageMs; EXPECT_EQ( memcmp(&gWifiScanResultList[1], &result2, sizeof(chreWifiScanResult)), 0); } TEST_F(WifiScanCacheTests, IncomingRequestDuringCachePopulationTest) { beginDefaultWifiCache(nullptr /* scannedFreqList */, 0 /* scannedFreqListLen */, false /* scanRequestedByChre */); chreWifiScanResult result = {}; chreWifiScanCacheScanEventAdd(&result); // An incoming request should upgrade the cache to a CHRE requested scan // event. chreWifiScanParams params = { .scanType = CHRE_WIFI_SCAN_TYPE_ACTIVE, .maxScanAgeMs = 5000, .frequencyListLen = 0, .frequencyList = nullptr, .ssidListLen = 0, .ssidList = nullptr, .radioChainPref = CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT, .channelSet = CHRE_WIFI_CHANNEL_SET_NON_DFS, }; EXPECT_TRUE(chreWifiScanCacheDispatchFromCache(¶ms)); // We shouldn't get the scan response until cache population is complete. EXPECT_FALSE(gWifiScanResponse.has_value()); result.bssid[0] = 1; chreWifiScanCacheScanEventAdd(&result); chreWifiScanCacheScanEventEnd(CHRE_ERROR_NONE); // CHRE should get the full cache result. EXPECT_TRUE(gWifiScanResponse.has_value()); EXPECT_EQ(gWifiScanResponse->pending, true); 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()); }