summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java25
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java388
-rw-r--r--service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java127
-rw-r--r--service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java1
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java10
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java54
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/command/PpsMoDataTest.java121
7 files changed, 539 insertions, 187 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
index fd993e599..9952ce43b 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
@@ -162,7 +162,6 @@ public class OsuServerConnection {
return false;
}
mUrlConnection = urlConnection;
- mHttpsTransport = HttpsTransport.createInstance(network, url);
return true;
}
@@ -233,13 +232,13 @@ public class OsuServerConnection {
mServiceConnection.disconnect();
}
- mServiceConnection = getServiceConnection();
+ mServiceConnection = getServiceConnection(mUrl, mNetwork);
if (mServiceConnection == null) {
Log.e(TAG, "ServiceConnection for https is null");
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null);
- return;
}
+ return;
}
SppResponseMessage sppResponse = null;
@@ -252,16 +251,16 @@ public class OsuServerConnection {
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
null);
- return;
}
+ return;
}
if (!(response instanceof SoapObject)) {
Log.e(TAG, "Not a SoapObject instance");
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
null);
- return;
}
+ return;
}
SoapObject soapResponse = (SoapObject) response;
if (mVerboseLoggingEnabled) {
@@ -277,19 +276,18 @@ public class OsuServerConnection {
sppResponse = SoapParser.getResponse(soapResponse);
} catch (Exception e) {
if (e instanceof SSLHandshakeException) {
- Log.e(TAG, "Failed to make TLS connection");
+ Log.e(TAG, "Failed to make TLS connection: " + e);
} else {
- Log.e(TAG, "Failed to exchange the SOAP message");
+ Log.e(TAG, "Failed to exchange the SOAP message: " + e);
}
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null);
- return;
}
+ return;
} finally {
mServiceConnection.disconnect();
mServiceConnection = null;
}
-
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
sppResponse);
@@ -301,9 +299,12 @@ public class OsuServerConnection {
*
* @return {@link HttpsServiceConnection}
*/
- private HttpsServiceConnection getServiceConnection() {
+ private HttpsServiceConnection getServiceConnection(@NonNull URL url,
+ @NonNull Network network) {
HttpsServiceConnection serviceConnection;
try {
+ // Creates new HTTPS connection.
+ mHttpsTransport = HttpsTransport.createInstance(network, url);
serviceConnection = (HttpsServiceConnection) mHttpsTransport.getServiceConnection();
if (serviceConnection != null) {
serviceConnection.setSSLSocketFactory(mSocketFactory);
@@ -365,8 +366,8 @@ public class OsuServerConnection {
}
}
if (mOsuServerCallbacks != null) {
- mOsuServerCallbacks.onServerValidationStatus(
- mOsuServerCallbacks.getSessionId(), certsValid);
+ mOsuServerCallbacks.onServerValidationStatus(mOsuServerCallbacks.getSessionId(),
+ certsValid);
}
}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
index d47f5eece..825ffe388 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.hotspot2;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
@@ -23,7 +24,9 @@ import android.net.Network;
import android.net.wifi.WifiManager;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -37,6 +40,7 @@ import com.android.server.wifi.hotspot2.soap.RedirectListener;
import com.android.server.wifi.hotspot2.soap.SppConstants;
import com.android.server.wifi.hotspot2.soap.SppResponseMessage;
import com.android.server.wifi.hotspot2.soap.command.BrowserUri;
+import com.android.server.wifi.hotspot2.soap.command.PpsMoData;
import com.android.server.wifi.hotspot2.soap.command.SppCommand;
import java.net.MalformedURLException;
@@ -146,6 +150,7 @@ public class PasspointProvisioner {
static final int STATE_OSU_SERVER_CONNECTED = 4;
static final int STATE_WAITING_FOR_FIRST_SOAP_RESPONSE = 5;
static final int STATE_WAITING_FOR_REDIRECT_RESPONSE = 6;
+ static final int STATE_WAITING_FOR_SECOND_SOAP_RESPONSE = 7;
private OsuProvider mOsuProvider;
private IProvisioningCallback mProvisioningCallback;
@@ -285,6 +290,207 @@ public class PasspointProvisioner {
}
/**
+ * Handles next step once receiving a HTTP redirect response.
+ *
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleRedirectResponse() {
+ if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
+ Log.e(TAG, "Received redirect request in wrong state=" + mState);
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ return;
+ }
+
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
+ mRedirectListener.stopServer();
+ secondSoapExchange();
+ }
+
+ /**
+ * Handles next step when timeout occurs because {@link RedirectListener} doesn't
+ * receive a HTTP redirect response.
+ *
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleTimeOutForRedirectResponse() {
+ Log.e(TAG, "Timed out for HTTP redirect response");
+
+ if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
+ Log.e(TAG, "Received timeout error for HTTP redirect response in wrong state="
+ + mState);
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ return;
+ }
+ mRedirectListener.stopServer();
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
+ }
+
+ /**
+ * Connected event received
+ *
+ * @param network Network object for this connection
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleConnectedEvent(Network network) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connected event received in state=" + mState);
+ }
+ if (mState != STATE_WAITING_TO_CONNECT) {
+ // Not waiting for a connection
+ Log.wtf(TAG, "Connection event unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
+ changeState(STATE_OSU_AP_CONNECTED);
+ initiateServerConnection(network);
+ }
+
+ /**
+ * Handles SOAP message response sent by server
+ *
+ * @param sessionId indicating current session ID
+ * @param responseMessage SOAP SPP response, or {@code null} in any failure.
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleSoapMessageResponse(int sessionId,
+ @Nullable SppResponseMessage responseMessage) {
+ if (sessionId != mCurrentSessionId) {
+ Log.w(TAG, "Expected soapMessageResponse callback for currentSessionId="
+ + mCurrentSessionId);
+ return;
+ }
+
+ if (responseMessage == null) {
+ Log.e(TAG, "failed to send the sppPostDevData message");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
+ return;
+ }
+
+ if (mState == STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
+ if (responseMessage.getMessageType()
+ != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
+ Log.e(TAG, "Expected a PostDevDataResponse, but got "
+ + responseMessage.getMessageType());
+ resetStateMachine(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
+ return;
+ }
+
+ PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage;
+ mSessionId = devDataResponse.getSessionID();
+ if (devDataResponse.getSppCommand().getExecCommandId()
+ != SppCommand.ExecCommandId.BROWSER) {
+ Log.e(TAG, "Expected a launchBrowser command, but got "
+ + devDataResponse.getSppCommand().getExecCommandId());
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ return;
+ }
+
+ Log.d(TAG, "Exec: " + devDataResponse.getSppCommand().getExecCommandId() + ", for '"
+ + devDataResponse.getSppCommand().getCommandData() + "'");
+
+ mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri();
+ if (mWebUrl == null) {
+ Log.e(TAG, "No Web-Url");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ return;
+ }
+
+ if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) {
+ Log.e(TAG, "Bad or Missing session ID in webUrl");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ return;
+ }
+ launchOsuWebView();
+ } else if (mState == STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) {
+ if (responseMessage.getMessageType()
+ != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
+ Log.e(TAG, "Expected a PostDevDataResponse, but got "
+ + responseMessage.getMessageType());
+ resetStateMachine(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
+ return;
+ }
+
+ PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage;
+ if (devDataResponse.getSppCommand() == null
+ || devDataResponse.getSppCommand().getSppCommandId()
+ != SppCommand.CommandId.ADD_MO) {
+ Log.e(TAG, "Expected a ADD_MO command, but got " + (
+ (devDataResponse.getSppCommand() == null) ? "null"
+ : devDataResponse.getSppCommand().getSppCommandId()));
+ resetStateMachine(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ return;
+ }
+
+ PasspointConfiguration passpointConfig = buildPasspointConfiguration(
+ (PpsMoData) devDataResponse.getSppCommand().getCommandData());
+
+ // TODO(b/74244324): Implement a routine to transmit third SOAP message.
+ } else {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Received an unexpected SOAP message in state=" + mState);
+ }
+ }
+ }
+
+ /**
+ * Disconnect event received
+ *
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleDisconnect() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connection failed in state=" + mState);
+ }
+ if (mState == STATE_INIT) {
+ Log.w(TAG, "Disconnect event unhandled in state=" + mState);
+ return;
+ }
+ mNetwork = null;
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ }
+
+ private void initiateServerConnection(Network network) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Initiating server connection in state=" + mState);
+ }
+ if (mState != STATE_OSU_AP_CONNECTED) {
+ Log.wtf(TAG , "Initiating server connection aborted in invalid state=" + mState);
+ return;
+ }
+ if (!mOsuServerConnection.connect(mServerUrl, network)) {
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
+ return;
+ }
+ mNetwork = network;
+ changeState(STATE_OSU_SERVER_CONNECTED);
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED);
+ }
+
+ private void invokeProvisioningCallback(int callbackType, int status) {
+ if (mProvisioningCallback == null) {
+ Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+ + " not invoked");
+ return;
+ }
+ try {
+ if (callbackType == PROVISIONING_STATUS) {
+ mProvisioningCallback.onProvisioningStatus(status);
+ } else {
+ mProvisioningCallback.onProvisioningFailure(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote Exception while posting callback type=" + callbackType
+ + " status=" + status);
+ }
+ }
+
+ /**
* Validate the OSU Server certificate based on the procedure in 7.3.2.2 of Hotspot2.0
* rel2 spec.
*/
@@ -407,178 +613,34 @@ public class PasspointProvisioner {
resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
- // TODO(b/74244324): Implement a routine to transmit second SOAP message.
- }
- /**
- * Handles SOAP message response sent by server
- *
- * @param sessionId indicating current session ID
- * @param responseMessage SOAP SPP response, or {@code null} in any failure.
- * Note: Called on main thread (WifiService thread).
- */
- public void handleSoapMessageResponse(int sessionId,
- @Nullable SppResponseMessage responseMessage) {
- if (sessionId != mCurrentSessionId) {
- Log.w(TAG, "Expected soapMessageResponse callback for currentSessionId="
- + mCurrentSessionId);
- return;
- }
-
- if (responseMessage == null) {
- Log.e(TAG, "failed to send the sppPostDevData message");
+ // Sending the second sppPostDevDataRequest message.
+ if (mOsuServerConnection.exchangeSoapMessage(
+ PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo,
+ mRedirectListener.getServerUrl().toString(),
+ SppConstants.SppReason.USER_INPUT_COMPLETED, mSessionId))) {
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_SECOND_SOAP_EXCHANGE);
+ changeState(STATE_WAITING_FOR_SECOND_SOAP_RESPONSE);
+ } else {
+ Log.e(TAG, "HttpsConnection is not established for soap message exchange");
resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
return;
}
-
- if (mState == STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
- if (responseMessage.getMessageType()
- != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
- Log.e(TAG, "Expected a PostDevDataResponse, but got "
- + responseMessage.getMessageType());
- resetStateMachine(
- ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
- return;
- }
-
- PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage;
- mSessionId = devDataResponse.getSessionID();
- if (devDataResponse.getSppCommand().getExecCommandId()
- != SppCommand.ExecCommandId.BROWSER) {
- Log.e(TAG, "Expected a launchBrowser command, but got "
- + devDataResponse.getSppCommand().getExecCommandId());
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
- return;
- }
-
- Log.d(TAG, "Exec: " + devDataResponse.getSppCommand().getExecCommandId() + ", for '"
- + devDataResponse.getSppCommand().getCommandData() + "'");
-
- mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri();
- if (mWebUrl == null) {
- Log.e(TAG, "No Web-Url");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
- return;
- }
-
- if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) {
- Log.e(TAG, "Bad or Missing session ID in webUrl");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
- return;
- }
- launchOsuWebView();
- }
}
- /**
- * Handles next step once receiving a HTTP redirect response.
- *
- * Note: Called on main thread (WifiService thread).
- */
- public void handleRedirectResponse() {
- if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
- Log.e(TAG, "Received redirect request in wrong state=" + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
- return;
- }
-
- invokeProvisioningCallback(PROVISIONING_STATUS,
- ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
- mRedirectListener.stopServer();
- secondSoapExchange();
- }
+ private PasspointConfiguration buildPasspointConfiguration(@NonNull PpsMoData moData) {
+ String moTree = moData.getPpsMoTree();
- /**
- * Handles next step when timeout occurs because {@link RedirectListener} doesn't
- * receive a HTTP redirect response.
- *
- * Note: Called on main thread (WifiService thread).
- */
- public void handleTimeOutForRedirectResponse() {
- Log.e(TAG, "Timed out for HTTP redirect response");
-
- if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
- Log.e(TAG, "Received timeout error for HTTP redirect response in wrong state="
- + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
- return;
- }
- mRedirectListener.stopServer();
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
- }
-
- /**
- * Connected event received
- *
- * @param network Network object for this connection
- * Note: Called on main thread (WifiService thread).
- */
- public void handleConnectedEvent(Network network) {
- if (mVerboseLoggingEnabled) {
- Log.v(TAG, "Connected event received in state=" + mState);
- }
- if (mState != STATE_WAITING_TO_CONNECT) {
- // Not waiting for a connection
- Log.wtf(TAG, "Connection event unhandled in state=" + mState);
- return;
- }
- invokeProvisioningCallback(PROVISIONING_STATUS,
- ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
- changeState(STATE_OSU_AP_CONNECTED);
- initiateServerConnection(network);
- }
-
- private void initiateServerConnection(Network network) {
- if (mVerboseLoggingEnabled) {
- Log.v(TAG, "Initiating server connection in state=" + mState);
- }
- if (mState != STATE_OSU_AP_CONNECTED) {
- Log.wtf(TAG , "Initiating server connection aborted in invalid state=" + mState);
- return;
- }
- if (!mOsuServerConnection.connect(mServerUrl, network)) {
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
- return;
- }
- mNetwork = network;
- changeState(STATE_OSU_SERVER_CONNECTED);
- invokeProvisioningCallback(PROVISIONING_STATUS,
- ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED);
- }
-
- /**
- * Disconnect event received
- *
- * Note: Called on main thread (WifiService thread).
- */
- public void handleDisconnect() {
- if (mVerboseLoggingEnabled) {
- Log.v(TAG, "Connection failed in state=" + mState);
- }
- if (mState == STATE_INIT) {
- Log.w(TAG, "Disconnect event unhandled in state=" + mState);
- return;
- }
- mNetwork = null;
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
- }
-
- private void invokeProvisioningCallback(int callbackType, int status) {
- if (mProvisioningCallback == null) {
- Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
- + " not invoked");
- return;
- }
- try {
- if (callbackType == PROVISIONING_STATUS) {
- mProvisioningCallback.onProvisioningStatus(status);
- } else {
- mProvisioningCallback.onProvisioningFailure(status);
+ PasspointConfiguration passpointConfiguration = PpsMoParser.parseMoText(moTree);
+ if (passpointConfiguration == null) {
+ Log.e(TAG, "fails to parse the MoTree");
+ } else {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "The parsed PasspointConfiguration: " + passpointConfiguration);
}
- } catch (RemoteException e) {
- Log.e(TAG, "Remote Exception while posting callback type=" + callbackType
- + " status=" + status);
}
+ return passpointConfiguration;
}
private void changeState(int nextState) {
diff --git a/service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java b/service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java
new file mode 100644
index 000000000..20cf6de86
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java
@@ -0,0 +1,127 @@
+/*
+ * 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.hotspot2.soap.command;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.ksoap2.serialization.PropertyInfo;
+import org.ksoap2.serialization.SoapPrimitive;
+
+import java.util.Objects;
+
+/**
+ * Represents PPS (PerProviderSubscription) MO (Management Object) defined by SPP (Subscription
+ * Provisioning Protocol).
+ */
+public class PpsMoData implements SppCommand.SppCommandData {
+ @VisibleForTesting
+ public static final String ADD_MO_COMMAND = "addMO";
+ @VisibleForTesting
+ public static final String ATTRIBUTE_MANAGEMENT_TREE_URI = "managementTreeURI";
+ @VisibleForTesting
+ public static final String ATTRIBUTE_MO_URN = "moURN";
+
+ private static final String TAG = "PpsMoData";
+ private final String mBaseUri;
+ private final String mUrn;
+ private final String mPpsMoTree;
+
+ private PpsMoData(String baseUri, String urn, String ppsMoTree) {
+ mBaseUri = baseUri;
+ mUrn = urn;
+ mPpsMoTree = ppsMoTree;
+ }
+
+ /**
+ * Create an instance of {@link PpsMoData}
+ *
+ * @param command command message embedded in SOAP sppPostDevDataResponse.
+ * @return instance of {@link PpsMoData}, {@code null} in any failure.
+ */
+ public static PpsMoData createInstance(@NonNull PropertyInfo command) {
+ if (command == null || command.getValue() == null) {
+ Log.e(TAG, "command message is null");
+ return null;
+ }
+
+ if (!TextUtils.equals(command.getName(), ADD_MO_COMMAND)) {
+ Log.e(TAG, "the response is not for addMO command");
+ return null;
+ }
+
+ if (!(command.getValue() instanceof SoapPrimitive)) {
+ Log.e(TAG, "the addMO element is not valid format");
+ return null;
+ }
+
+ SoapPrimitive soapObject = (SoapPrimitive) command.getValue();
+ if (!soapObject.hasAttribute(ATTRIBUTE_MANAGEMENT_TREE_URI)) {
+ Log.e(TAG, "managementTreeURI Attribute is missing");
+ return null;
+ }
+
+ if (!soapObject.hasAttribute(ATTRIBUTE_MO_URN)) {
+ Log.e(TAG, "moURN Attribute is missing");
+ return null;
+ }
+
+ if (soapObject.getValue() == null) {
+ Log.e(TAG, "PPSMO Tree is missing");
+ return null;
+ }
+
+ return new PpsMoData(
+ (String) soapObject.getAttributeSafelyAsString(ATTRIBUTE_MANAGEMENT_TREE_URI),
+ (String) soapObject.getAttributeSafelyAsString(ATTRIBUTE_MO_URN),
+ soapObject.getValue().toString());
+ }
+
+ /**
+ * Get PPS (PerProviderSubscription) MO (Management Object) with XML format.
+ *
+ * @return PPS MO Tree
+ */
+ public String getPpsMoTree() {
+ return mPpsMoTree;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) return true;
+ if (thatObject == null) return false;
+ if (!(thatObject instanceof PpsMoData)) return false;
+ PpsMoData ppsMoData = (PpsMoData) thatObject;
+ return TextUtils.equals(mBaseUri, ppsMoData.mBaseUri)
+ && TextUtils.equals(mUrn, ppsMoData.mUrn)
+ && TextUtils.equals(mPpsMoTree, ppsMoData.mPpsMoTree);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBaseUri, mUrn, mPpsMoTree);
+ }
+
+ @Override
+ public String toString() {
+ return "PpsMoData{Base URI: " + mBaseUri + ", MOURN: " + mUrn + ", PPS MO: " + mPpsMoTree
+ + "}";
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java b/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java
index ffc8a5ab3..02df6d3ec 100644
--- a/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java
+++ b/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java
@@ -139,6 +139,7 @@ public class SppCommand {
* at the specified location to be added.
* If there is already a management object at that location, the object is replaced.
*/
+ mCommandData = PpsMoData.createInstance(soapResponse);
break;
case CommandId.UPDATE_NODE:
/*
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
index 8a123c2f2..f0a72efce 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
@@ -305,20 +305,16 @@ public class OsuServerConnectionTest {
public void verifyExchangeSoapMessage() {
// static mocking
MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
- HttpsTransport.class).startMocking();
+ HttpsTransport.class).mockStatic(SoapParser.class).startMocking();
try {
mOsuServerConnection.init(mTlsContext, mDelegate);
mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
- when(HttpsTransport.createInstance(any(Network.class), any(URL.class))).thenReturn(
- mHttpsTransport);
assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
- session.finishMocking();
- // new static mocking
- session = ExtendedMockito.mockitoSession().mockStatic(
- SoapParser.class).startMocking();
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER12);
envelope.bodyIn = new SoapObject();
+ when(HttpsTransport.createInstance(any(Network.class), any(URL.class))).thenReturn(
+ mHttpsTransport);
when(SoapParser.getResponse(any(SoapObject.class))).thenReturn(mSppResponseMessage);
assertTrue(mOsuServerConnection.exchangeSoapMessage(envelope));
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
index 5e73cd652..4b5a822d3 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
@@ -54,6 +54,7 @@ import com.android.server.wifi.hotspot2.soap.PostDevDataResponse;
import com.android.server.wifi.hotspot2.soap.RedirectListener;
import com.android.server.wifi.hotspot2.soap.SppResponseMessage;
import com.android.server.wifi.hotspot2.soap.command.BrowserUri;
+import com.android.server.wifi.hotspot2.soap.command.PpsMoData;
import com.android.server.wifi.hotspot2.soap.command.SppCommand;
import org.junit.After;
@@ -81,6 +82,7 @@ public class PasspointProvisionerTest {
private static final int STEP_AP_CONNECT = 1;
private static final int STEP_SERVER_CONNECT = 2;
private static final int STEP_WAIT_FOR_REDIRECT_RESPONSE = 3;
+ private static final int STEP_WAIT_FOR_SECOND_SOAP_RESPONSE = 4;
private static final String TEST_DEV_ID = "12312341";
private static final String TEST_MANUFACTURER = Build.MANUFACTURER;
@@ -132,6 +134,7 @@ public class PasspointProvisionerTest {
@Mock TelephonyManager mTelephonyManager;
@Mock SppCommand mSppCommand;
@Mock BrowserUri mBrowserUri;
+ @Mock PpsMoData mPpsMoData;
@Mock RedirectListener mRedirectListener;
@Mock PackageManager mPackageManager;
@@ -185,6 +188,7 @@ public class PasspointProvisionerTest {
SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE);
when(mSppResponseMessage.getSppCommand()).thenReturn(mSppCommand);
when(mSppResponseMessage.getSessionID()).thenReturn(TEST_SESSION_ID);
+ when(mSppCommand.getSppCommandId()).thenReturn(SppCommand.CommandId.EXEC);
when(mSppCommand.getExecCommandId()).thenReturn(SppCommand.ExecCommandId.BROWSER);
when(mSppCommand.getCommandData()).thenReturn(mBrowserUri);
when(mBrowserUri.getUri()).thenReturn(TEST_URL);
@@ -257,7 +261,7 @@ public class PasspointProvisionerTest {
verify(mCallback).onProvisioningStatus(
ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
- // Received soapMessageResponse
+ // Received a first soapMessageResponse
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
mSppResponseMessage);
mLooper.dispatchAll();
@@ -268,6 +272,26 @@ public class PasspointProvisionerTest {
.startServer(mOnRedirectReceivedArgumentCaptor.capture());
mRedirectReceivedListener = mOnRedirectReceivedArgumentCaptor.getValue();
verifyNoMoreInteractions(mCallback);
+ } else if (step == STEP_WAIT_FOR_SECOND_SOAP_RESPONSE) {
+ when(mSppCommand.getSppCommandId()).thenReturn(SppCommand.CommandId.ADD_MO);
+ when(mSppCommand.getExecCommandId()).thenReturn(-1);
+ when(mSppCommand.getCommandData()).thenReturn(mPpsMoData);
+
+ // Received HTTP redirect response.
+ mRedirectReceivedListener.onRedirectReceived();
+ mLooper.dispatchAll();
+
+ verify(mRedirectListener, atLeastOnce()).stopServer();
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_SECOND_SOAP_EXCHANGE);
+
+ // Received a second soapMessageResponse
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ mSppResponseMessage);
+ mLooper.dispatchAll();
+
}
}
}
@@ -556,11 +580,11 @@ public class PasspointProvisionerTest {
}
/**
- * Verifies that the right provisioning callbacks are invoked as the provisioner progresses
- * to the end as successful case.
+ * Verifies that the right provisioning callbacks are invoked when a command of a second soap
+ * response is not for ADD MO command.
*/
@Test
- public void verifyProvisioningFlowForSuccessfulCase() throws RemoteException {
+ public void verifyNotAddMoCommandFailureForSecondSoapResponse() throws RemoteException {
stopAfterStep(STEP_WAIT_FOR_REDIRECT_RESPONSE);
// Received HTTP redirect response.
@@ -570,7 +594,27 @@ public class PasspointProvisionerTest {
verify(mRedirectListener, atLeastOnce()).stopServer();
verify(mCallback).onProvisioningStatus(
ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
- // No further runnables posted
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_SECOND_SOAP_EXCHANGE);
+
+ // Received a second soapMessageResponse
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ mSppResponseMessage);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onProvisioningFailure(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ }
+
+ /**
+ * Verifies that the right provisioning callbacks are invoked as the provisioner progresses
+ * to the end as successful case.
+ */
+ @Test
+ public void verifyProvisioningFlowForSuccessfulCase() throws RemoteException {
+ stopAfterStep(STEP_WAIT_FOR_SECOND_SOAP_RESPONSE);
+
+ // No further runnables posted
verifyNoMoreInteractions(mCallback);
}
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/command/PpsMoDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/command/PpsMoDataTest.java
new file mode 100644
index 000000000..e4f2a1521
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/command/PpsMoDataTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.hotspot2.soap.command;
+
+import static com.android.server.wifi.hotspot2.soap.command.PpsMoData.ADD_MO_COMMAND;
+import static com.android.server.wifi.hotspot2.soap.command.PpsMoData.ATTRIBUTE_MANAGEMENT_TREE_URI;
+import static com.android.server.wifi.hotspot2.soap.command.PpsMoData.ATTRIBUTE_MO_URN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.ksoap2.serialization.PropertyInfo;
+import org.ksoap2.serialization.SoapPrimitive;
+
+/**
+ * Unit tests for {@link PpsMoData}.
+ */
+@SmallTest
+public class PpsMoDataTest {
+ private static final String TEST_PPS_MO_XML = "<MgmtTree>test</MgmtTree>";
+ private static final String TEST_TREE_URI = "testTreeURI";
+ private static final String TEST_MO_URN = "testMoURN";
+
+ /**
+ * Sets up test.
+ */
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ }
+
+ /**
+ * Verify if a message is valid format, it will return a PPS MO XML.
+ */
+ @Test
+ public void verifyGetPpsMo() {
+ PropertyInfo propertyInfo = new PropertyInfo();
+ propertyInfo.setName(ADD_MO_COMMAND);
+ SoapPrimitive soapPrimitive = new SoapPrimitive("namespace", "name", TEST_PPS_MO_XML);
+ soapPrimitive.addAttribute(ATTRIBUTE_MANAGEMENT_TREE_URI, TEST_TREE_URI);
+ soapPrimitive.addAttribute(ATTRIBUTE_MO_URN, TEST_MO_URN);
+ propertyInfo.setValue(soapPrimitive);
+
+ assertEquals(TEST_PPS_MO_XML, PpsMoData.createInstance(propertyInfo).getPpsMoTree());
+ }
+
+ /**
+ * Verify if a message does not have PPS MO XML, it will return {@code null}.
+ */
+ @Test
+ public void verifyMissingPpsMoReturnNull() {
+ PropertyInfo propertyInfo = new PropertyInfo();
+ propertyInfo.setName(ADD_MO_COMMAND);
+ SoapPrimitive soapPrimitive = new SoapPrimitive("namespace", "name", TEST_PPS_MO_XML);
+ soapPrimitive.addAttribute(ATTRIBUTE_MANAGEMENT_TREE_URI, TEST_TREE_URI);
+ soapPrimitive.addAttribute(ATTRIBUTE_MO_URN, TEST_MO_URN);
+ propertyInfo.setValue(null);
+
+ assertNull(PpsMoData.createInstance(propertyInfo));
+ }
+
+ /**
+ * Verify if a message is missing {@link PpsMoData#ATTRIBUTE_MANAGEMENT_TREE_URI}, it will
+ * return {@code null}.
+ */
+ @Test
+ public void verifyMissingTreeURIAttributeReturnNull() {
+ PropertyInfo propertyInfo = new PropertyInfo();
+ propertyInfo.setName(ADD_MO_COMMAND);
+ SoapPrimitive soapPrimitive = new SoapPrimitive("namespace", "name", TEST_PPS_MO_XML);
+ soapPrimitive.addAttribute(ATTRIBUTE_MO_URN, TEST_MO_URN);
+ propertyInfo.setValue(soapPrimitive);
+
+ assertNull(PpsMoData.createInstance(propertyInfo));
+ }
+
+ /**
+ * Verify if a message is missing {@link PpsMoData#ATTRIBUTE_MO_URN}, it will return
+ * {@code null}.
+ */
+ @Test
+ public void verifyMissingMoUrnAttributeReturnNull() {
+ PropertyInfo propertyInfo = new PropertyInfo();
+ propertyInfo.setName(ADD_MO_COMMAND);
+ SoapPrimitive soapPrimitive = new SoapPrimitive("namespace", "name", TEST_PPS_MO_XML);
+ soapPrimitive.addAttribute(ATTRIBUTE_MANAGEMENT_TREE_URI, TEST_TREE_URI);
+ propertyInfo.setValue(soapPrimitive);
+
+ assertNull(PpsMoData.createInstance(propertyInfo));
+ }
+
+ /**
+ * Verify if a message that is not for addMO command, it will return {@code null}.
+ */
+ @Test
+ public void verifyNonAddMoCommandMessageReturnNull() {
+ PropertyInfo propertyInfo = new PropertyInfo();
+ propertyInfo.setName("InvalidCommand");
+
+ assertNull(PpsMoData.createInstance(propertyInfo));
+ }
+}