| /* |
| * 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 <inttypes.h> |
| |
| #include "chre/util/macros.h" |
| |
| /************************************************ |
| * Prototypes |
| ***********************************************/ |
| |
| struct chreWifiScanCacheState { |
| //! true if the scan cache has started, i.e. chreWifiScanCacheScanEventBegin |
| //! was invoked and has not yet ended. |
| bool started; |
| |
| //! true if the current scan cache is a result of a CHRE active scan request. |
| bool activeScanResult; |
| |
| //! The number of chreWifiScanResults dropped due to OOM. |
| uint16_t numWifiScanResultsDropped; |
| |
| //! Stores the WiFi cache elements |
| struct chreWifiScanEvent event; |
| struct chreWifiScanResult resultList[CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY]; |
| |
| //! The number of chreWifiScanEvent data pending release via |
| //! chreWifiScanCacheReleaseScanEvent(). |
| uint8_t numWifiEventsPendingRelease; |
| |
| bool scanMonitoringEnabled; |
| |
| uint32_t scannedFreqList[CHRE_WIFI_FREQUENCY_LIST_MAX_LEN]; |
| }; |
| |
| /************************************************ |
| * Global variables |
| ***********************************************/ |
| static const struct chrePalSystemApi *gSystemApi = NULL; |
| static const struct chrePalWifiCallbacks *gCallbacks = NULL; |
| |
| static struct chreWifiScanCacheState gWifiCacheState; |
| |
| //! true if scan monitoring is enabled via |
| //! chreWifiScanCacheConfigureScanMonitor(). |
| static bool gScanMonitoringEnabled; |
| |
| static const uint64_t kOneMillisecondInNanoseconds = UINT64_C(1000000); |
| |
| /************************************************ |
| * Private functions |
| ***********************************************/ |
| static bool chreWifiScanCacheIsInitialized(void) { |
| return (gSystemApi != NULL && gCallbacks != NULL); |
| } |
| |
| static bool areAllScanEventsReleased(void) { |
| return gWifiCacheState.numWifiEventsPendingRelease == 0; |
| } |
| |
| static bool isFrequencyListValid(const uint32_t *frequencyList, |
| uint16_t frequencyListLen) { |
| return (frequencyListLen == 0) || (frequencyList != NULL); |
| } |
| |
| static bool paramsMatchScanCache(const struct chreWifiScanParams *params) { |
| uint64_t timeNs = gWifiCacheState.event.referenceTime; |
| bool scan_within_age = |
| (timeNs >= gSystemApi->getCurrentTime() - |
| (params->maxScanAgeMs * kOneMillisecondInNanoseconds)); |
| |
| // Perform a conservative check for the params and scan cache. |
| // TODO(b/174510035): Consider optimizing for the case for channelSet == |
| // CHRE_WIFI_CHANNEL_SET_ALL. |
| bool params_non_dfs = |
| (params->scanType == CHRE_WIFI_SCAN_TYPE_ACTIVE) || |
| ((params->scanType == CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE) && |
| (params->channelSet == CHRE_WIFI_CHANNEL_SET_NON_DFS)); |
| bool cache_non_dfs = |
| (gWifiCacheState.event.scanType == CHRE_WIFI_SCAN_TYPE_ACTIVE) || |
| (gWifiCacheState.event.scanType == CHRE_WIFI_SCAN_TYPE_PASSIVE); |
| |
| bool cache_all_freq = (gWifiCacheState.event.scannedFreqListLen == 0); |
| bool cache_all_ssid = (gWifiCacheState.event.ssidSetSize == 0); |
| |
| return scan_within_age && (params_non_dfs || !cache_non_dfs) && |
| cache_all_freq && cache_all_ssid; |
| } |
| |
| static bool isWifiScanCacheBusy(bool logOnBusy) { |
| bool busy = true; |
| if (gWifiCacheState.started) { |
| if (logOnBusy) { |
| gSystemApi->log(CHRE_LOG_ERROR, "Scan cache already started"); |
| } |
| } else if (!areAllScanEventsReleased()) { |
| if (logOnBusy) { |
| gSystemApi->log(CHRE_LOG_ERROR, "Scan cache events pending release"); |
| } |
| } else { |
| busy = false; |
| } |
| |
| return busy; |
| } |
| |
| static void chreWifiScanCacheDispatchAll(void) { |
| gSystemApi->log(CHRE_LOG_DEBUG, "Dispatching %" PRIu8 " events", |
| gWifiCacheState.event.resultTotal); |
| if (gWifiCacheState.event.resultTotal == 0) { |
| gWifiCacheState.event.eventIndex = 0; |
| gWifiCacheState.event.resultCount = 0; |
| gWifiCacheState.event.results = NULL; |
| gCallbacks->scanEventCallback(&gWifiCacheState.event); |
| } else { |
| uint8_t eventIndex = 0; |
| for (uint16_t i = 0; i < gWifiCacheState.event.resultTotal; |
| i += CHRE_PAL_WIFI_SCAN_CACHE_MAX_RESULT_COUNT) { |
| gWifiCacheState.event.resultCount = |
| MIN(CHRE_PAL_WIFI_SCAN_CACHE_MAX_RESULT_COUNT, |
| (uint8_t)(gWifiCacheState.event.resultTotal - i)); |
| gWifiCacheState.event.eventIndex = eventIndex++; |
| gWifiCacheState.event.results = &gWifiCacheState.resultList[i]; |
| |
| // TODO(b/174511061): The current approach only works for situations where |
| // the event is released immediately. Add a way to handle this scenario |
| // (e.g. an array of chreWifiScanEvent's). |
| gWifiCacheState.numWifiEventsPendingRelease++; |
| gCallbacks->scanEventCallback(&gWifiCacheState.event); |
| } |
| } |
| } |
| |
| static bool isWifiScanResultInCache(const struct chreWifiScanResult *result, |
| size_t *index) { |
| for (uint8_t i = 0; i < gWifiCacheState.event.resultTotal; i++) { |
| const struct chreWifiScanResult *cacheResult = |
| &gWifiCacheState.resultList[i]; |
| // Filtering based on BSSID + SSID + frequency based on Linux cfg80211. |
| // https://github.com/torvalds/linux/blob/master/net/wireless/scan.c |
| if ((result->primaryChannel == cacheResult->primaryChannel) && |
| (memcmp(result->bssid, cacheResult->bssid, CHRE_WIFI_BSSID_LEN) == 0) && |
| (result->ssidLen == cacheResult->ssidLen) && |
| (memcmp(result->ssid, cacheResult->ssid, result->ssidLen) == 0)) { |
| *index = i; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /************************************************ |
| * Public functions |
| ***********************************************/ |
| bool chreWifiScanCacheInit(const struct chrePalSystemApi *systemApi, |
| const struct chrePalWifiCallbacks *callbacks) { |
| if (systemApi == NULL || callbacks == NULL) { |
| return false; |
| } |
| |
| gSystemApi = systemApi; |
| gCallbacks = callbacks; |
| memset(&gWifiCacheState, 0, sizeof(gWifiCacheState)); |
| gScanMonitoringEnabled = false; |
| |
| return true; |
| } |
| |
| void chreWifiScanCacheDeinit(void) { |
| gSystemApi = NULL; |
| gCallbacks = NULL; |
| } |
| |
| bool chreWifiScanCacheScanEventBegin(enum chreWifiScanType scanType, |
| uint8_t ssidSetSize, |
| const uint32_t *scannedFreqList, |
| uint16_t scannedFreqListLength, |
| uint8_t radioChainPref, |
| bool activeScanResult) { |
| bool success = false; |
| if (chreWifiScanCacheIsInitialized()) { |
| enum chreError error = CHRE_ERROR_NONE; |
| if (!isFrequencyListValid(scannedFreqList, scannedFreqListLength)) { |
| gSystemApi->log(CHRE_LOG_ERROR, "Invalid frequency argument"); |
| error = CHRE_ERROR_INVALID_ARGUMENT; |
| } else if (isWifiScanCacheBusy(true /* logOnBusy */)) { |
| error = CHRE_ERROR_BUSY; |
| } else { |
| success = true; |
| memset(&gWifiCacheState, 0, sizeof(gWifiCacheState)); |
| |
| gWifiCacheState.event.version = CHRE_WIFI_SCAN_EVENT_VERSION; |
| gWifiCacheState.event.scanType = scanType; |
| gWifiCacheState.event.ssidSetSize = ssidSetSize; |
| |
| scannedFreqListLength = |
| MIN(scannedFreqListLength, CHRE_WIFI_FREQUENCY_LIST_MAX_LEN); |
| if (scannedFreqList != NULL) { |
| memcpy(gWifiCacheState.scannedFreqList, scannedFreqList, |
| scannedFreqListLength * sizeof(uint32_t)); |
| } |
| gWifiCacheState.event.scannedFreqListLen = scannedFreqListLength; |
| gWifiCacheState.event.radioChainPref = radioChainPref; |
| |
| gWifiCacheState.activeScanResult = activeScanResult; |
| gWifiCacheState.started = true; |
| } |
| |
| if (activeScanResult && !success) { |
| gCallbacks->scanResponseCallback(false /* pending */, error); |
| } |
| } |
| |
| return success; |
| } |
| |
| void chreWifiScanCacheScanEventAdd(const struct chreWifiScanResult *result) { |
| if (!gWifiCacheState.started) { |
| gSystemApi->log(CHRE_LOG_ERROR, "Cannot add to cache before starting it"); |
| } else { |
| size_t index; |
| bool exists = isWifiScanResultInCache(result, &index); |
| if (!exists && gWifiCacheState.event.resultTotal >= |
| CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY) { |
| // TODO(b/174510884): Filter based on e.g. RSSI if full |
| gWifiCacheState.numWifiScanResultsDropped++; |
| } else { |
| if (!exists) { |
| // Only add a new entry if the result was not already cached. |
| index = gWifiCacheState.event.resultTotal; |
| gWifiCacheState.event.resultTotal++; |
| } |
| |
| memcpy(&gWifiCacheState.resultList[index], result, |
| sizeof(const struct chreWifiScanResult)); |
| |
| // ageMs will be properly populated in chreWifiScanCacheScanEventEnd |
| gWifiCacheState.resultList[index].ageMs = (uint32_t)( |
| gSystemApi->getCurrentTime() / kOneMillisecondInNanoseconds); |
| } |
| } |
| } |
| |
| void chreWifiScanCacheScanEventEnd(enum chreError errorCode) { |
| if (gWifiCacheState.started) { |
| if (gWifiCacheState.numWifiScanResultsDropped > 0) { |
| gSystemApi->log(CHRE_LOG_WARN, |
| "Dropped total of %" PRIu32 " access points", |
| gWifiCacheState.numWifiScanResultsDropped); |
| } |
| if (gWifiCacheState.activeScanResult) { |
| gCallbacks->scanResponseCallback( |
| errorCode == CHRE_ERROR_NONE /* pending */, errorCode); |
| } |
| |
| if (errorCode == CHRE_ERROR_NONE && |
| (gWifiCacheState.activeScanResult || gScanMonitoringEnabled)) { |
| gWifiCacheState.event.referenceTime = gSystemApi->getCurrentTime(); |
| gWifiCacheState.event.scannedFreqList = gWifiCacheState.scannedFreqList; |
| |
| uint32_t referenceTimeMs = (uint32_t)( |
| gWifiCacheState.event.referenceTime / kOneMillisecondInNanoseconds); |
| for (uint16_t i = 0; i < gWifiCacheState.event.resultTotal; i++) { |
| gWifiCacheState.resultList[i].ageMs = |
| referenceTimeMs - gWifiCacheState.resultList[i].ageMs; |
| } |
| |
| chreWifiScanCacheDispatchAll(); |
| } |
| |
| gWifiCacheState.started = false; |
| gWifiCacheState.activeScanResult = false; |
| } |
| } |
| |
| bool chreWifiScanCacheDispatchFromCache( |
| const struct chreWifiScanParams *params) { |
| if (!chreWifiScanCacheIsInitialized()) { |
| return false; |
| } |
| |
| if (paramsMatchScanCache(params) && |
| !isWifiScanCacheBusy(false /* logOnBusy */)) { |
| // TODO(b/174511061): Handle scenario where cache is working on delivering |
| // a scan event. Ideally the library will wait until it is complete to |
| // dispatch from the cache if it meets the criteria, rather than scheduling |
| // a fresh scan. |
| gCallbacks->scanResponseCallback(true /* pending */, CHRE_ERROR_NONE); |
| chreWifiScanCacheDispatchAll(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| void chreWifiScanCacheReleaseScanEvent(struct chreWifiScanEvent *event) { |
| if (!chreWifiScanCacheIsInitialized()) { |
| return; |
| } |
| |
| if (event != &gWifiCacheState.event) { |
| gSystemApi->log(CHRE_LOG_ERROR, "Invalid event pointer %p", event); |
| } else if (gWifiCacheState.numWifiEventsPendingRelease > 0) { |
| gWifiCacheState.numWifiEventsPendingRelease--; |
| } |
| } |
| |
| void chreWifiScanCacheConfigureScanMonitor(bool enable) { |
| if (!chreWifiScanCacheIsInitialized()) { |
| return; |
| } |
| |
| gScanMonitoringEnabled = enable; |
| } |