/* * Copyright (C) 2017 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. */ package com.android.server.wifi; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.wifi.ScanResult; import android.net.wifi.SecurityParams; import android.net.wifi.WifiConfiguration; import com.android.server.wifi.util.ScanResultUtil; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Class to store the info needed to match a scan result to the provided network configuration. */ public class ScanResultMatchInfo { /** * SSID of the network. */ public String networkSsid; /** * Security params list. */ public List securityParamsList = new ArrayList<>(); /** * True if created from a scan result */ private boolean mFromScanResult = false; /** * Get the ScanResultMatchInfo for the given WifiConfiguration */ public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) { ScanResultMatchInfo info = new ScanResultMatchInfo(); info.networkSsid = config.SSID; info.securityParamsList = config.getSecurityParamsList(); return info; } /** * Get the ScanResultMatchInfo for the given ScanResult */ public static ScanResultMatchInfo fromScanResult(ScanResult scanResult) { ScanResultMatchInfo info = new ScanResultMatchInfo(); // Scan result ssid's are not quoted, hence add quotes. // TODO: This matching algo works only if the scan result contains a string SSID. // However, according to our public documentation ths {@link WifiConfiguration#SSID} can // either have a hex string or quoted ASCII string SSID. info.networkSsid = ScanResultUtil.createQuotedSSID(scanResult.SSID); info.securityParamsList = ScanResultUtil.generateSecurityParamsListFromScanResult(scanResult); info.mFromScanResult = true; return info; } /** * Check if an auto-upgraded security parameters configuration is allowed by the overlay * configurations for WPA3-Personal (SAE) and Enhanced Open (OWE). * * @param securityParams Security parameters object * @return true if allowed, false if not allowed */ private static boolean isAutoUpgradeSecurityParamsAllowed(SecurityParams securityParams) { WifiGlobals wifiGlobals = WifiInjector.getInstance().getWifiGlobals(); // In mixed security network environments, we need to filter out APs with the stronger // security type when the current network supports the weaker security type, and the // stronger security type was added by auto-upgrade, and // auto-upgrade feature is disabled. if (securityParams.getSecurityType() == WifiConfiguration.SECURITY_TYPE_SAE && securityParams.isAddedByAutoUpgrade() && !wifiGlobals.isWpa3SaeUpgradeEnabled()) { return false; } if (securityParams.getSecurityType() == WifiConfiguration.SECURITY_TYPE_OWE && securityParams.isAddedByAutoUpgrade() && !wifiGlobals.isOweUpgradeEnabled()) { return false; } return true; } /** * The matching algorithm is that the type with a bigger index in the allowed * params list has the higher priority. We try to match each type from the end of * the allowed params list against the params in the scan result params list. * * There are three cases which will skip the match: * 1. the security type is different. * 2. the params is disabled, ex. disabled by Transition Disable Indication. * 3. The params is added by the auto-upgrade mechanism, but the corresponding * feature is not enabled. */ private static @Nullable SecurityParams findBestMatchingSecurityParams( List allowedParamsList, List scanResultParamsList) { if (null == allowedParamsList) return null; if (null == scanResultParamsList) return null; for (int i = allowedParamsList.size() - 1; i >= 0; i--) { SecurityParams allowedParams = allowedParamsList.get(i); if (!WifiConfigurationUtil.isSecurityParamsValid(allowedParams) || !isAutoUpgradeSecurityParamsAllowed(allowedParams)) { continue; } for (SecurityParams scanResultParams: scanResultParamsList) { if (!allowedParams.isSecurityType(scanResultParams.getSecurityType())) { continue; } return allowedParams; } } return null; } /** * Get the best-matching security type between ScanResult and WifiConifiguration. */ public static @Nullable SecurityParams getBestMatchingSecurityParams( WifiConfiguration config, ScanResult scanResult) { if (null == config || null == scanResult) return null; return findBestMatchingSecurityParams( config.getSecurityParamsList(), ScanResultUtil.generateSecurityParamsListFromScanResult(scanResult)); } /** * Get the best-matching security type between ScanResult and WifiConifiguration. */ public static @Nullable SecurityParams getBestMatchingSecurityParams( WifiConfiguration config, List scanResultParamsList) { if (null == config || null == scanResultParamsList) return null; return findBestMatchingSecurityParams( config.getSecurityParamsList(), scanResultParamsList); } public @Nullable SecurityParams getDefaultSecurityParams() { return securityParamsList.isEmpty() ? null : securityParamsList.get(0); } public @Nullable SecurityParams getFirstAvailableSecurityParams() { return securityParamsList.stream() .filter(WifiConfigurationUtil::isSecurityParamsValid) .findFirst() .orElse(null); } /** * Checks for equality of network type. */ public boolean networkTypeEquals(@NonNull ScanResultMatchInfo other) { if (null == securityParamsList || null == other.securityParamsList) return false; // If both are from the same sources, do normal comparison. if (mFromScanResult == other.mFromScanResult) { return securityParamsList.equals(other.securityParamsList); } final List allowedParamsList = mFromScanResult ? other.securityParamsList : securityParamsList; final List scanResultParamsList = mFromScanResult ? securityParamsList : other.securityParamsList; return null != findBestMatchingSecurityParams( allowedParamsList, scanResultParamsList); } @Override public boolean equals(Object otherObj) { if (this == otherObj) { return true; } else if (!(otherObj instanceof ScanResultMatchInfo)) { return false; } ScanResultMatchInfo other = (ScanResultMatchInfo) otherObj; if (mFromScanResult == other.mFromScanResult) { return Objects.equals(networkSsid, other.networkSsid) && securityParamsList.equals(other.securityParamsList); } return null != matchForNetworkSelection(other); } /** * Match two ScanResultMatchInfo objects while considering configuration in overlays * * @param other Other object to compare against * @return return best matching security params, null if no matching one. */ public SecurityParams matchForNetworkSelection(ScanResultMatchInfo other) { if (!Objects.equals(networkSsid, other.networkSsid)) return null; if (null == securityParamsList) return null; if (null == other.securityParamsList) return null; final List allowedParamsList = mFromScanResult ? other.securityParamsList : securityParamsList; final List scanResultParamsList = mFromScanResult ? securityParamsList : other.securityParamsList; return findBestMatchingSecurityParams( allowedParamsList, scanResultParamsList); } /** Check whether this matchinfo contains the type or not. */ public boolean isSecurityType(@WifiConfiguration.SecurityType int securityType) { return securityParamsList.stream().anyMatch(p -> p.isSecurityType(securityType)); } @Override public int hashCode() { return Objects.hash(networkSsid); } @Override public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append("ScanResultMatchInfo: SSID: ").append(networkSsid); sbuf.append(", from scan result: ").append(mFromScanResult); sbuf.append(", SecurityParams List:"); securityParamsList.stream() .forEach(params -> sbuf.append(params.toString())); return sbuf.toString(); } }