blob: e9f77d4f62578aadbfbe44e7ff1944035cb28fec [file] [log] [blame]
/*
* Copyright (C) 2018 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 android.net.wifi.WifiManager.EASY_CONNECT_NETWORK_ROLE_AP;
import android.content.Context;
import android.hardware.wifi.supplicant.V1_2.DppAkm;
import android.hardware.wifi.supplicant.V1_2.DppFailureCode;
import android.hardware.wifi.supplicant.V1_2.DppNetRole;
import android.hardware.wifi.supplicant.V1_2.DppProgressCode;
import android.net.wifi.EasyConnectStatusCallback;
import android.net.wifi.IDppCallback;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.WakeupMessage;
import com.android.server.wifi.WifiNative.DppEventCallback;
/**
* DPP Manager class
* Implements the DPP Initiator APIs and callbacks
*/
public class DppManager {
private static final String TAG = "DppManager";
public Handler mHandler;
private DppRequestInfo mDppRequestInfo = null;
private final WifiNative mWifiNative;
private String mClientIfaceName;
private boolean mVerboseLoggingEnabled;
WifiConfigManager mWifiConfigManager;
private final Context mContext;
@VisibleForTesting
public WakeupMessage mDppTimeoutMessage = null;
private final Clock mClock;
private static final String DPP_TIMEOUT_TAG = TAG + " Request Timeout";
private static final int DPP_TIMEOUT_MS = 40_000; // 40 seconds
private final DppMetrics mDppMetrics;
private final DppEventCallback mDppEventCallback = new DppEventCallback() {
@Override
public void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration) {
mHandler.post(() -> {
DppManager.this.onSuccessConfigReceived(newWifiConfiguration);
});
}
@Override
public void onSuccessConfigSent() {
mHandler.post(() -> {
DppManager.this.onSuccessConfigSent();
});
}
@Override
public void onProgress(int dppStatusCode) {
mHandler.post(() -> {
DppManager.this.onProgress(dppStatusCode);
});
}
@Override
public void onFailure(int dppStatusCode) {
mHandler.post(() -> {
DppManager.this.onFailure(dppStatusCode);
});
}
};
DppManager(Looper looper, WifiNative wifiNative, WifiConfigManager wifiConfigManager,
Context context, DppMetrics dppMetrics) {
mHandler = new Handler(looper);
mWifiNative = wifiNative;
mWifiConfigManager = wifiConfigManager;
mWifiNative.registerDppEventCallback(mDppEventCallback);
mContext = context;
mClock = new Clock();
mDppMetrics = dppMetrics;
// Setup timer
mDppTimeoutMessage = new WakeupMessage(mContext, mHandler,
DPP_TIMEOUT_TAG, () -> {
timeoutDppRequest();
});
}
private static String encodeStringToHex(String str) {
if ((str.length() > 1) && (str.charAt(0) == '"') && (str.charAt(str.length() - 1) == '"')) {
// Remove the surrounding quotes
str = str.substring(1, str.length() - 1);
// Convert to Hex
char[] charsArray = str.toCharArray();
StringBuffer hexBuffer = new StringBuffer();
for (int i = 0; i < charsArray.length; i++) {
hexBuffer.append(Integer.toHexString((int) charsArray[i]));
}
return hexBuffer.toString();
}
return str;
}
private void timeoutDppRequest() {
logd("DPP timeout");
if (mDppRequestInfo == null) {
Log.e(TAG, "DPP timeout with no request info");
return;
}
// Clean up supplicant resources
if (!mWifiNative.stopDppInitiator(mClientIfaceName)) {
Log.e(TAG, "Failed to stop DPP Initiator");
}
// Clean up resources and let the caller know about the timeout
onFailure(DppFailureCode.TIMEOUT);
}
/**
* Start DPP request in Configurator-Initiator mode. The goal of this call is to send the
* selected Wi-Fi configuration to a remote peer so it could join that network.
*
* @param uid User ID
* @param binder Binder object
* @param enrolleeUri The Enrollee URI, scanned externally (e.g. via QR code)
* @param selectedNetworkId The selected Wi-Fi network ID to be sent
* @param enrolleeNetworkRole Network role of remote enrollee: STA or AP
* @param callback DPP Callback object
*/
public void startDppAsConfiguratorInitiator(int uid, IBinder binder,
String enrolleeUri, int selectedNetworkId,
@WifiManager.EasyConnectNetworkRole int enrolleeNetworkRole, IDppCallback callback) {
mDppMetrics.updateDppConfiguratorInitiatorRequests();
if (mDppRequestInfo != null) {
try {
Log.e(TAG, "DPP request already in progress");
Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: "
+ uid);
mDppMetrics.updateDppFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_BUSY);
// On going DPP. Call the failure callback directly
callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY);
} catch (RemoteException e) {
// Empty
}
return;
}
mClientIfaceName = mWifiNative.getClientInterfaceName();
if (mClientIfaceName == null) {
try {
Log.e(TAG, "Wi-Fi client interface does not exist");
// On going DPP. Call the failure callback directly
mDppMetrics.updateDppFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_GENERIC);
callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC);
} catch (RemoteException e) {
// Empty
}
return;
}
WifiConfiguration selectedNetwork = mWifiConfigManager
.getConfiguredNetworkWithoutMasking(selectedNetworkId);
if (selectedNetwork == null) {
try {
Log.e(TAG, "Selected network is null");
// On going DPP. Call the failure callback directly
mDppMetrics.updateDppFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK);
callback.onFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK);
} catch (RemoteException e) {
// Empty
}
return;
}
String password = null;
String psk = null;
int securityAkm;
// Currently support either SAE mode or PSK mode
if (selectedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
// SAE
password = selectedNetwork.preSharedKey;
securityAkm = DppAkm.SAE;
} else if (selectedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
if (selectedNetwork.preSharedKey.matches(String.format("[0-9A-Fa-f]{%d}", 64))) {
// PSK
psk = selectedNetwork.preSharedKey;
} else {
// Passphrase
password = selectedNetwork.preSharedKey;
}
securityAkm = DppAkm.PSK;
} else {
try {
// Key management must be either PSK or SAE
Log.e(TAG, "Key management must be either PSK or SAE");
mDppMetrics.updateDppFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK);
callback.onFailure(
EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK);
} catch (RemoteException e) {
// Empty
}
return;
}
mDppRequestInfo = new DppRequestInfo();
mDppRequestInfo.uid = uid;
mDppRequestInfo.binder = binder;
mDppRequestInfo.callback = callback;
if (!linkToDeath(mDppRequestInfo)) {
// Notify failure and clean up
onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC);
return;
}
logd("Interface " + mClientIfaceName + ": Initializing URI: " + enrolleeUri);
mDppRequestInfo.startTime = mClock.getElapsedSinceBootMillis();
mDppTimeoutMessage.schedule(mDppRequestInfo.startTime + DPP_TIMEOUT_MS);
// Send Enrollee URI and get a peer ID
int peerId = mWifiNative.addDppPeerUri(mClientIfaceName, enrolleeUri);
if (peerId < 0) {
Log.e(TAG, "DPP add URI failure");
// Notify failure and clean up
onFailure(DppFailureCode.INVALID_URI);
return;
}
mDppRequestInfo.peerId = peerId;
// Auth init
logd("Authenticating");
String ssidEncoded = encodeStringToHex(selectedNetwork.SSID);
String passwordEncoded = null;
if (password != null) {
passwordEncoded = encodeStringToHex(selectedNetwork.preSharedKey);
}
if (!mWifiNative.startDppConfiguratorInitiator(mClientIfaceName,
mDppRequestInfo.peerId, 0, ssidEncoded, passwordEncoded, psk,
enrolleeNetworkRole == EASY_CONNECT_NETWORK_ROLE_AP ? DppNetRole.AP
: DppNetRole.STA,
securityAkm)) {
Log.e(TAG, "DPP Start Configurator Initiator failure");
// Notify failure and clean up
onFailure(DppFailureCode.FAILURE);
return;
}
logd("Success: Started DPP Initiator with peer ID "
+ mDppRequestInfo.peerId);
}
/**
* Start DPP request in Enrollee-Initiator mode. The goal of this call is to receive a
* Wi-Fi configuration object from the peer configurator in order to join a network.
*
* @param uid User ID
* @param binder Binder object
* @param configuratorUri The Configurator URI, scanned externally (e.g. via QR code)
* @param callback DPP Callback object
*/
public void startDppAsEnrolleeInitiator(int uid, IBinder binder,
String configuratorUri, IDppCallback callback) {
mDppMetrics.updateDppEnrolleeInitiatorRequests();
if (mDppRequestInfo != null) {
try {
Log.e(TAG, "DPP request already in progress");
Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: "
+ uid);
mDppMetrics.updateDppFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_BUSY);
// On going DPP. Call the failure callback directly
callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY);
} catch (RemoteException e) {
// Empty
}
return;
}
mDppRequestInfo = new DppRequestInfo();
mDppRequestInfo.uid = uid;
mDppRequestInfo.binder = binder;
mDppRequestInfo.callback = callback;
if (!linkToDeath(mDppRequestInfo)) {
// Notify failure and clean up
onFailure(DppFailureCode.FAILURE);
return;
}
mDppRequestInfo.startTime = mClock.getElapsedSinceBootMillis();
mDppTimeoutMessage.schedule(mDppRequestInfo.startTime + DPP_TIMEOUT_MS);
mClientIfaceName = mWifiNative.getClientInterfaceName();
logd("Interface " + mClientIfaceName + ": Initializing URI: " + configuratorUri);
// Send Configurator URI and get a peer ID
int peerId = mWifiNative.addDppPeerUri(mClientIfaceName, configuratorUri);
if (peerId < 0) {
Log.e(TAG, "DPP add URI failure");
onFailure(DppFailureCode.INVALID_URI);
return;
}
mDppRequestInfo.peerId = peerId;
// Auth init
logd("Authenticating");
if (!mWifiNative.startDppEnrolleeInitiator(mClientIfaceName, mDppRequestInfo.peerId,
0)) {
Log.e(TAG, "DPP Start Enrollee Initiator failure");
// Notify failure and clean up
onFailure(DppFailureCode.FAILURE);
return;
}
logd("Success: Started DPP Initiator with peer ID "
+ mDppRequestInfo.peerId);
}
/**
* Stop a current DPP session
*
* @param uid User ID
*/
public void stopDppSession(int uid) {
if (mDppRequestInfo == null) {
logd("UID " + uid + " called stop DPP session with no active DPP session");
return;
}
if (mDppRequestInfo.uid != uid) {
Log.e(TAG, "UID " + uid + " called stop DPP session but UID " + mDppRequestInfo.uid
+ " has started it");
return;
}
// Clean up supplicant resources
if (!mWifiNative.stopDppInitiator(mClientIfaceName)) {
Log.e(TAG, "Failed to stop DPP Initiator");
}
cleanupDppResources();
logd("Success: Stopped DPP Initiator");
}
private void cleanupDppResources() {
logd("DPP clean up resources");
if (mDppRequestInfo == null) {
return;
}
// Cancel pending timeout
mDppTimeoutMessage.cancel();
// Remove the URI from the supplicant list
if (!mWifiNative.removeDppUri(mClientIfaceName, mDppRequestInfo.peerId)) {
Log.e(TAG, "Failed to remove DPP URI ID " + mDppRequestInfo.peerId);
}
mDppRequestInfo.binder.unlinkToDeath(mDppRequestInfo.dr, 0);
mDppRequestInfo = null;
}
private static class DppRequestInfo {
public int uid;
public IBinder binder;
public IBinder.DeathRecipient dr;
public int peerId;
public IDppCallback callback;
public long startTime;
@Override
public String toString() {
return new StringBuilder("DppRequestInfo: uid=").append(uid).append(", binder=").append(
binder).append(", dr=").append(dr)
.append(", callback=").append(callback)
.append(", peerId=").append(peerId).toString();
}
}
/**
* Enable vervose logging from DppManager
*
* @param verbose 0 to disable verbose logging, or any other value to enable.
*/
public void enableVerboseLogging(int verbose) {
mVerboseLoggingEnabled = verbose != 0 ? true : false;
}
private void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration) {
try {
logd("onSuccessConfigReceived");
if (mDppRequestInfo != null) {
long now = mClock.getElapsedSinceBootMillis();
mDppMetrics.updateDppOperationTime((int) (now - mDppRequestInfo.startTime));
NetworkUpdateResult networkUpdateResult = mWifiConfigManager
.addOrUpdateNetwork(newWifiConfiguration, mDppRequestInfo.uid);
if (networkUpdateResult.isSuccess()) {
mDppMetrics.updateDppEnrolleeSuccess();
mDppRequestInfo.callback.onSuccessConfigReceived(
networkUpdateResult.getNetworkId());
} else {
Log.e(TAG, "DPP configuration received, but failed to update network");
mDppMetrics.updateDppFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION);
mDppRequestInfo.callback.onFailure(EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION);
}
} else {
Log.e(TAG, "Unexpected null Wi-Fi configuration object");
}
} catch (RemoteException e) {
Log.e(TAG, "Callback failure");
}
// Success, DPP is complete. Clear the DPP session automatically
cleanupDppResources();
}
private void onSuccessConfigSent() {
try {
if (mDppRequestInfo == null) {
Log.e(TAG, "onDppSuccessConfigSent event without a request information object");
return;
}
logd("onSuccessConfigSent");
long now = mClock.getElapsedSinceBootMillis();
mDppMetrics.updateDppOperationTime((int) (now - mDppRequestInfo.startTime));
mDppMetrics.updateDppConfiguratorSuccess(
EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT);
mDppRequestInfo.callback.onSuccess(
EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT);
} catch (RemoteException e) {
Log.e(TAG, "Callback failure");
}
// Success, DPP is complete. Clear the DPP session automatically
cleanupDppResources();
}
private void onProgress(int dppStatusCode) {
try {
if (mDppRequestInfo == null) {
Log.e(TAG, "onProgress event without a request information object");
return;
}
logd("onProgress: " + dppStatusCode);
int dppProgressCode;
// Convert from HAL codes to WifiManager/user codes
switch (dppStatusCode) {
case DppProgressCode.AUTHENTICATION_SUCCESS:
dppProgressCode = EasyConnectStatusCallback
.EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS;
break;
case DppProgressCode.RESPONSE_PENDING:
dppProgressCode = EasyConnectStatusCallback
.EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING;
break;
default:
Log.e(TAG, "onProgress: unknown code " + dppStatusCode);
return;
}
mDppRequestInfo.callback.onProgress(dppProgressCode);
} catch (RemoteException e) {
Log.e(TAG, "Callback failure");
}
}
private void onFailure(int dppStatusCode) {
try {
if (mDppRequestInfo == null) {
Log.e(TAG, "onFailure event without a request information object");
return;
}
logd("OnFailure: " + dppStatusCode);
long now = mClock.getElapsedSinceBootMillis();
mDppMetrics.updateDppOperationTime((int) (now - mDppRequestInfo.startTime));
int dppFailureCode;
// Convert from HAL codes to WifiManager/user codes
switch (dppStatusCode) {
case DppFailureCode.INVALID_URI:
dppFailureCode =
EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI;
break;
case DppFailureCode.AUTHENTICATION:
dppFailureCode =
EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION;
break;
case DppFailureCode.NOT_COMPATIBLE:
dppFailureCode =
EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE;
break;
case DppFailureCode.CONFIGURATION:
dppFailureCode =
EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION;
break;
case DppFailureCode.BUSY:
dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY;
break;
case DppFailureCode.TIMEOUT:
dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT;
break;
case DppFailureCode.NOT_SUPPORTED:
dppFailureCode =
EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED;
break;
case DppFailureCode.FAILURE:
default:
dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
break;
}
mDppMetrics.updateDppFailure(dppFailureCode);
mDppRequestInfo.callback.onFailure(dppFailureCode);
} catch (RemoteException e) {
Log.e(TAG, "Callback failure");
}
// All failures are fatal, clear the DPP session
cleanupDppResources();
}
private void logd(String message) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, message);
}
}
private boolean linkToDeath(DppRequestInfo dppRequestInfo) {
// register for binder death
dppRequestInfo.dr = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (dppRequestInfo == null) {
return;
}
logd("binderDied: uid=" + dppRequestInfo.uid);
mHandler.post(() -> {
cleanupDppResources();
});
}
};
try {
dppRequestInfo.binder.linkToDeath(dppRequestInfo.dr, 0);
} catch (RemoteException e) {
Log.e(TAG, "Error on linkToDeath - " + e);
dppRequestInfo.dr = null;
return false;
}
return true;
}
}