| /* |
| * Copyright (C) 2019 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 static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQP3GPPNetwork; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPDomName; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPIPAddrAvailability; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPNAIRealm; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPRoamingConsortium; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueName; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueUrl; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSConnCapability; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSFriendlyName; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSOSUProviders; |
| import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSWANMetrics; |
| |
| import android.annotation.NonNull; |
| import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback; |
| import android.net.wifi.SecurityParams; |
| import android.net.wifi.SupplicantState; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiSsid; |
| import android.util.Log; |
| |
| import com.android.server.wifi.hotspot2.AnqpEvent; |
| import com.android.server.wifi.hotspot2.IconEvent; |
| import com.android.server.wifi.hotspot2.WnmData; |
| import com.android.server.wifi.hotspot2.anqp.ANQPElement; |
| import com.android.server.wifi.hotspot2.anqp.ANQPParser; |
| import com.android.server.wifi.hotspot2.anqp.Constants; |
| import com.android.server.wifi.util.NativeUtil; |
| |
| import java.io.IOException; |
| import java.nio.BufferUnderflowException; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| abstract class SupplicantStaIfaceCallbackImpl extends ISupplicantStaIfaceCallback.Stub { |
| private static final String TAG = SupplicantStaIfaceCallbackImpl.class.getSimpleName(); |
| private final SupplicantStaIfaceHal mStaIfaceHal; |
| private final String mIfaceName; |
| private final Object mLock; |
| private final WifiMonitor mWifiMonitor; |
| // Used to help check for PSK password mismatch & EAP connection failure. |
| private int mStateBeforeDisconnect = State.INACTIVE; |
| private String mCurrentSsid = null; |
| |
| SupplicantStaIfaceCallbackImpl(@NonNull SupplicantStaIfaceHal staIfaceHal, |
| @NonNull String ifaceName, |
| @NonNull Object lock, |
| @NonNull WifiMonitor wifiMonitor) { |
| mStaIfaceHal = staIfaceHal; |
| mIfaceName = ifaceName; |
| mLock = lock; |
| mWifiMonitor = wifiMonitor; |
| } |
| |
| /** |
| * Converts the supplicant state received from HIDL to the equivalent framework state. |
| */ |
| protected static SupplicantState supplicantHidlStateToFrameworkState(int state) { |
| switch (state) { |
| case ISupplicantStaIfaceCallback.State.DISCONNECTED: |
| return SupplicantState.DISCONNECTED; |
| case ISupplicantStaIfaceCallback.State.IFACE_DISABLED: |
| return SupplicantState.INTERFACE_DISABLED; |
| case ISupplicantStaIfaceCallback.State.INACTIVE: |
| return SupplicantState.INACTIVE; |
| case ISupplicantStaIfaceCallback.State.SCANNING: |
| return SupplicantState.SCANNING; |
| case ISupplicantStaIfaceCallback.State.AUTHENTICATING: |
| return SupplicantState.AUTHENTICATING; |
| case ISupplicantStaIfaceCallback.State.ASSOCIATING: |
| return SupplicantState.ASSOCIATING; |
| case ISupplicantStaIfaceCallback.State.ASSOCIATED: |
| return SupplicantState.ASSOCIATED; |
| case ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE: |
| return SupplicantState.FOUR_WAY_HANDSHAKE; |
| case ISupplicantStaIfaceCallback.State.GROUP_HANDSHAKE: |
| return SupplicantState.GROUP_HANDSHAKE; |
| case ISupplicantStaIfaceCallback.State.COMPLETED: |
| return SupplicantState.COMPLETED; |
| default: |
| throw new IllegalArgumentException("Invalid state: " + state); |
| } |
| } |
| |
| |
| /** |
| * Parses the provided payload into an ANQP element. |
| * |
| * @param infoID Element type. |
| * @param payload Raw payload bytes. |
| * @return AnqpElement instance on success, null on failure. |
| */ |
| private ANQPElement parseAnqpElement(Constants.ANQPElementType infoID, |
| ArrayList<Byte> payload) { |
| synchronized (mLock) { |
| try { |
| return Constants.getANQPElementID(infoID) != null |
| ? ANQPParser.parseElement( |
| infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload))) |
| : ANQPParser.parseHS20Element( |
| infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload))); |
| } catch (IOException | BufferUnderflowException e) { |
| Log.e(TAG, "Failed parsing ANQP element payload: " + infoID, e); |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Parse the ANQP element data and add to the provided elements map if successful. |
| * |
| * @param elementsMap Map to add the parsed out element to. |
| * @param infoID Element type. |
| * @param payload Raw payload bytes. |
| */ |
| private void addAnqpElementToMap(Map<Constants.ANQPElementType, ANQPElement> elementsMap, |
| Constants.ANQPElementType infoID, |
| ArrayList<Byte> payload) { |
| synchronized (mLock) { |
| if (payload == null || payload.isEmpty()) return; |
| ANQPElement element = parseAnqpElement(infoID, payload); |
| if (element != null) { |
| elementsMap.put(infoID, element); |
| } |
| } |
| } |
| |
| @Override |
| public void onNetworkAdded(int id) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onNetworkAdded id=" + id); |
| } |
| } |
| |
| @Override |
| public void onNetworkRemoved(int id) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onNetworkRemoved id=" + id); |
| // Reset state since network has been removed. |
| mStateBeforeDisconnect = State.INACTIVE; |
| } |
| } |
| |
| /** |
| * Added to plumb the new {@code filsHlpSent} param from the V1.3 callback version. |
| */ |
| public void onStateChanged(int newState, byte[/* 6 */] bssid, int id, ArrayList<Byte> ssid, |
| boolean filsHlpSent) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onStateChanged"); |
| SupplicantState newSupplicantState = |
| supplicantHidlStateToFrameworkState(newState); |
| WifiSsid wifiSsid = |
| WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(ssid)); |
| String bssidStr = NativeUtil.macAddressFromByteArray(bssid); |
| if (newState != State.DISCONNECTED) { |
| // onStateChanged(DISCONNECTED) may come before onDisconnected(), so add this |
| // cache to track the state before the disconnect. |
| mStateBeforeDisconnect = newState; |
| } |
| |
| if (newState == State.ASSOCIATING || newState == State.ASSOCIATED |
| || newState == State.COMPLETED) { |
| mStaIfaceHal.updateOnLinkedNetworkRoaming(mIfaceName, id); |
| } |
| |
| if (newState == State.COMPLETED) { |
| mWifiMonitor.broadcastNetworkConnectionEvent( |
| mIfaceName, mStaIfaceHal.getCurrentNetworkId(mIfaceName), filsHlpSent, |
| wifiSsid, bssidStr); |
| } else if (newState == State.ASSOCIATING) { |
| mCurrentSsid = NativeUtil.encodeSsid(ssid); |
| } |
| mWifiMonitor.broadcastSupplicantStateChangeEvent( |
| mIfaceName, mStaIfaceHal.getCurrentNetworkId(mIfaceName), wifiSsid, |
| bssidStr, newSupplicantState); |
| } |
| } |
| |
| @Override |
| public void onStateChanged(int newState, byte[/* 6 */] bssid, int id, ArrayList<Byte> ssid) { |
| onStateChanged(newState, bssid, id, ssid, false); |
| } |
| |
| public void onAnqpQueryDone(byte[/* 6 */] bssid, |
| ISupplicantStaIfaceCallback.AnqpData data, |
| ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data, |
| android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AnqpData dataV14) { |
| Map<Constants.ANQPElementType, ANQPElement> elementsMap = new HashMap<>(); |
| addAnqpElementToMap(elementsMap, ANQPVenueName, data.venueName); |
| addAnqpElementToMap(elementsMap, ANQPRoamingConsortium, data.roamingConsortium); |
| addAnqpElementToMap( |
| elementsMap, ANQPIPAddrAvailability, data.ipAddrTypeAvailability); |
| addAnqpElementToMap(elementsMap, ANQPNAIRealm, data.naiRealm); |
| addAnqpElementToMap(elementsMap, ANQP3GPPNetwork, data.anqp3gppCellularNetwork); |
| addAnqpElementToMap(elementsMap, ANQPDomName, data.domainName); |
| if (dataV14 != null) { |
| addAnqpElementToMap(elementsMap, ANQPVenueUrl, dataV14.venueUrl); |
| } |
| addAnqpElementToMap(elementsMap, HSFriendlyName, hs20Data.operatorFriendlyName); |
| addAnqpElementToMap(elementsMap, HSWANMetrics, hs20Data.wanMetrics); |
| addAnqpElementToMap(elementsMap, HSConnCapability, hs20Data.connectionCapability); |
| addAnqpElementToMap(elementsMap, HSOSUProviders, hs20Data.osuProvidersList); |
| mWifiMonitor.broadcastAnqpDoneEvent( |
| mIfaceName, new AnqpEvent(NativeUtil.macAddressToLong(bssid), elementsMap)); |
| } |
| @Override |
| public void onAnqpQueryDone(byte[/* 6 */] bssid, |
| ISupplicantStaIfaceCallback.AnqpData data, |
| ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onAnqpQueryDone"); |
| onAnqpQueryDone(bssid, data, hs20Data, null /* v1.4 element */); |
| } |
| } |
| |
| @Override |
| public void onHs20IconQueryDone(byte[/* 6 */] bssid, String fileName, |
| ArrayList<Byte> data) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onHs20IconQueryDone"); |
| mWifiMonitor.broadcastIconDoneEvent( |
| mIfaceName, |
| new IconEvent(NativeUtil.macAddressToLong(bssid), fileName, data.size(), |
| NativeUtil.byteArrayFromArrayList(data))); |
| } |
| } |
| |
| @Override |
| public void onHs20SubscriptionRemediation(byte[/* 6 */] bssid, byte osuMethod, String url) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onHs20SubscriptionRemediation"); |
| mWifiMonitor.broadcastWnmEvent( |
| mIfaceName, |
| WnmData.createRemediationEvent(NativeUtil.macAddressToLong(bssid), url, |
| osuMethod)); |
| } |
| } |
| |
| @Override |
| public void onHs20DeauthImminentNotice(byte[/* 6 */] bssid, int reasonCode, |
| int reAuthDelayInSec, String url) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onHs20DeauthImminentNotice"); |
| mWifiMonitor.broadcastWnmEvent( |
| mIfaceName, |
| WnmData.createDeauthImminentEvent(NativeUtil.macAddressToLong(bssid), url, |
| reasonCode == WnmData.ESS, reAuthDelayInSec)); |
| } |
| } |
| |
| @Override |
| public void onDisconnected(byte[/* 6 */] bssid, boolean locallyGenerated, int reasonCode) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onDisconnected"); |
| if (mStaIfaceHal.isVerboseLoggingEnabled()) { |
| Log.e(TAG, "onDisconnected state=" + mStateBeforeDisconnect |
| + " locallyGenerated=" + locallyGenerated |
| + " reasonCode=" + reasonCode); |
| } |
| WifiConfiguration curConfiguration = |
| mStaIfaceHal.getCurrentNetworkLocalConfig(mIfaceName); |
| if (curConfiguration != null) { |
| if (mStateBeforeDisconnect == State.FOURWAY_HANDSHAKE |
| && WifiConfigurationUtil.isConfigForPskNetwork(curConfiguration) |
| && (!locallyGenerated || reasonCode != ReasonCode.IE_IN_4WAY_DIFFERS)) { |
| mWifiMonitor.broadcastAuthenticationFailureEvent( |
| mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1); |
| } else if (mStateBeforeDisconnect == State.ASSOCIATED |
| && WifiConfigurationUtil.isConfigForEapNetwork(curConfiguration)) { |
| mWifiMonitor.broadcastAuthenticationFailureEvent( |
| mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, -1); |
| } |
| } |
| mWifiMonitor.broadcastNetworkDisconnectionEvent( |
| mIfaceName, locallyGenerated, reasonCode, mCurrentSsid, |
| NativeUtil.macAddressFromByteArray(bssid)); |
| } |
| } |
| |
| private void handleAssocRejectEvent(AssocRejectEventInfo assocRejectInfo) { |
| boolean isWrongPwd = false; |
| WifiConfiguration curConfiguration = |
| mStaIfaceHal.getCurrentNetworkLocalConfig(mIfaceName); |
| if (curConfiguration != null) { |
| if (!assocRejectInfo.timedOut) { |
| Log.d(TAG, "flush PMK cache due to association rejection for config id " |
| + curConfiguration.networkId + "."); |
| mStaIfaceHal.removePmkCacheEntry(curConfiguration.networkId); |
| } |
| // Special handling for WPA3-Personal networks. If the password is |
| // incorrect, the AP will send association rejection, with status code 1 |
| // (unspecified failure). In SAE networks, the password authentication |
| // is not related to the 4-way handshake. In this case, we will send an |
| // authentication failure event up. |
| if (assocRejectInfo.statusCode == StatusCode.UNSPECIFIED_FAILURE) { |
| // Network Selection status is guaranteed to be initialized |
| SecurityParams params = curConfiguration.getNetworkSelectionStatus() |
| .getCandidateSecurityParams(); |
| if (params != null |
| && params.getSecurityType() == WifiConfiguration.SECURITY_TYPE_SAE) { |
| mStaIfaceHal.logCallback("SAE incorrect password"); |
| isWrongPwd = true; |
| } |
| } else if (assocRejectInfo.statusCode == StatusCode.CHALLENGE_FAIL |
| && WifiConfigurationUtil.isConfigForWepNetwork(curConfiguration)) { |
| mStaIfaceHal.logCallback("WEP incorrect password"); |
| isWrongPwd = true; |
| } |
| } |
| |
| if (isWrongPwd) { |
| mWifiMonitor.broadcastAuthenticationFailureEvent( |
| mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1); |
| } |
| mWifiMonitor.broadcastAssociationRejectionEvent(mIfaceName, assocRejectInfo); |
| mStateBeforeDisconnect = State.INACTIVE; |
| } |
| |
| public void onAssociationRejected(android.hardware.wifi.supplicant.V1_4 |
| .ISupplicantStaIfaceCallback.AssociationRejectionData assocRejectData) { |
| AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo(assocRejectData); |
| handleAssocRejectEvent(assocRejectInfo); |
| } |
| |
| @Override |
| public void onAssociationRejected(byte[/* 6 */] bssid, int statusCode, boolean timedOut) { |
| synchronized (mLock) { |
| AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo( |
| mCurrentSsid, |
| NativeUtil.macAddressFromByteArray(bssid), |
| statusCode, timedOut); |
| handleAssocRejectEvent(assocRejectInfo); |
| } |
| } |
| |
| @Override |
| public void onAuthenticationTimeout(byte[/* 6 */] bssid) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onAuthenticationTimeout"); |
| mWifiMonitor.broadcastAuthenticationFailureEvent( |
| mIfaceName, WifiManager.ERROR_AUTH_FAILURE_TIMEOUT, -1); |
| mStateBeforeDisconnect = State.INACTIVE; |
| } |
| } |
| |
| @Override |
| public void onBssidChanged(byte reason, byte[/* 6 */] bssid) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onBssidChanged"); |
| if (reason == BssidChangeReason.ASSOC_START) { |
| mWifiMonitor.broadcastTargetBssidEvent( |
| mIfaceName, NativeUtil.macAddressFromByteArray(bssid)); |
| } else if (reason == BssidChangeReason.ASSOC_COMPLETE) { |
| mWifiMonitor.broadcastAssociatedBssidEvent( |
| mIfaceName, NativeUtil.macAddressFromByteArray(bssid)); |
| } |
| } |
| } |
| |
| public void onEapFailure(int errorCode) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onEapFailure"); |
| mWifiMonitor.broadcastAuthenticationFailureEvent( |
| mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, errorCode); |
| mStateBeforeDisconnect = State.INACTIVE; |
| } |
| } |
| |
| |
| @Override |
| public void onEapFailure() { |
| onEapFailure(-1); |
| } |
| |
| @Override |
| public void onWpsEventSuccess() { |
| mStaIfaceHal.logCallback("onWpsEventSuccess"); |
| synchronized (mLock) { |
| mWifiMonitor.broadcastWpsSuccessEvent(mIfaceName); |
| } |
| } |
| |
| @Override |
| public void onWpsEventFail(byte[/* 6 */] bssid, short configError, short errorInd) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onWpsEventFail"); |
| if (configError == WpsConfigError.MSG_TIMEOUT |
| && errorInd == WpsErrorIndication.NO_ERROR) { |
| mWifiMonitor.broadcastWpsTimeoutEvent(mIfaceName); |
| } else { |
| mWifiMonitor.broadcastWpsFailEvent(mIfaceName, configError, errorInd); |
| } |
| } |
| } |
| |
| @Override |
| public void onWpsEventPbcOverlap() { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onWpsEventPbcOverlap"); |
| mWifiMonitor.broadcastWpsOverlapEvent(mIfaceName); |
| } |
| } |
| |
| @Override |
| public void onExtRadioWorkStart(int id) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onExtRadioWorkStart"); |
| } |
| } |
| |
| @Override |
| public void onExtRadioWorkTimeout(int id) { |
| synchronized (mLock) { |
| mStaIfaceHal.logCallback("onExtRadioWorkTimeout"); |
| } |
| } |
| } |