diff options
author | Ecco Park <eccopark@google.com> | 2018-09-04 13:48:53 -0700 |
---|---|---|
committer | Ecco Park <eccopark@google.com> | 2018-10-01 06:09:43 +0000 |
commit | 63d022cb4aae028e21902cc6839875bc8c091aca (patch) | |
tree | 58c9d0b5c59782cf0361a5f70f68297ea7813e16 | |
parent | 01d1d200d69e83238f44aa5a0e43051125730ecf (diff) |
passpoint-r2: sending a second SOAP message and receiving a response
After completing user input, client needs to send sppPostDevData message
with a request reason as "User input completed".
Once server receives the message from client, server will send a
sppPostDevDataResponse (sessionID, sppStatus, addMO: PerProviderSubscription MO).
to client.
Client needs to parse the data from the response and install this into
the device.
Bug: 74244324
Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Test: live test with Passpoint R2 service provider AP.
Change-Id: I1f2952da90184ddb183572bd4e0d91e9e29393f0
Signed-off-by: Ecco Park <eccopark@google.com>
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)); + } +} |