blob: 494997260cd5709bab0ef9fe85b23dc157a06c16 [file] [log] [blame]
/*
* 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");
}
}
}