diff options
author | Ecco Park <eccopark@google.com> | 2018-10-15 13:15:58 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-10-15 13:15:58 +0000 |
commit | 1ff63057eb276526c10e8aab0f8b6687cca38cfe (patch) | |
tree | c237b83c5f3b523761d53e1d6b55dbc74ba6bb85 | |
parent | 1bcfb32f1e784e64d8b956732725b314765a8e29 (diff) | |
parent | 53eb6d52d7e110c8e9098226b62c665c8e3bef83 (diff) |
Merge "passpoint-r2: sending a third SOAP message and receiving a response"
7 files changed, 381 insertions, 3 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java index 825ffe388..25073506e 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java @@ -34,11 +34,13 @@ import android.os.UserHandle; import android.util.Log; import com.android.server.wifi.WifiNative; +import com.android.server.wifi.hotspot2.soap.ExchangeCompleteMessage; import com.android.server.wifi.hotspot2.soap.PostDevDataMessage; import com.android.server.wifi.hotspot2.soap.PostDevDataResponse; 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.UpdateResponseMessage; 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; @@ -151,6 +153,7 @@ public class PasspointProvisioner { 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; + static final int STATE_WAITING_FOR_THIRD_SOAP_RESPONSE = 8; private OsuProvider mOsuProvider; private IProvisioningCallback mProvisioningCallback; @@ -429,7 +432,37 @@ public class PasspointProvisioner { PasspointConfiguration passpointConfig = buildPasspointConfiguration( (PpsMoData) devDataResponse.getSppCommand().getCommandData()); - // TODO(b/74244324): Implement a routine to transmit third SOAP message. + thirdSoapExchange(passpointConfig == null); + } else if (mState == STATE_WAITING_FOR_THIRD_SOAP_RESPONSE) { + if (responseMessage.getMessageType() + != SppResponseMessage.MessageType.EXCHANGE_COMPLETE) { + Log.e(TAG, "Expected a ExchangeCompleteMessage, but got " + + responseMessage.getMessageType()); + resetStateMachine( + ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE); + return; + } + + ExchangeCompleteMessage exchangeCompleteMessage = + (ExchangeCompleteMessage) responseMessage; + if (exchangeCompleteMessage.getStatus() + != SppConstants.SppStatus.EXCHANGE_COMPLETE) { + Log.e(TAG, "Expected a ExchangeCompleteMessage Status, but got " + + exchangeCompleteMessage.getStatus()); + resetStateMachine( + ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS); + return; + } + + if (exchangeCompleteMessage.getError() != SppConstants.INVALID_SPP_CONSTANT) { + Log.e(TAG, + "In the SppExchangeComplete, got error " + + exchangeCompleteMessage.getError()); + resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); + return; + } + + // TODO(b/74244324): Implement a routine to get CAs for AAA, Remediation, Policy. } else { if (mVerboseLoggingEnabled) { Log.v(TAG, "Received an unexpected SOAP message in state=" + mState); @@ -629,6 +662,33 @@ public class PasspointProvisioner { } } + /** + * Initiates the third SOAP message exchange with sending the sppUpdateResponse message. + */ + private void thirdSoapExchange(boolean isError) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Initiates the third soap message exchange in state =" + mState); + } + + if (mState != STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) { + Log.e(TAG, "Initiates the third soap message exchange in wrong state=" + mState); + resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); + return; + } + + // Sending the sppUpdateResponse message. + if (mOsuServerConnection.exchangeSoapMessage( + UpdateResponseMessage.serializeToSoapEnvelope(mSessionId, isError))) { + invokeProvisioningCallback(PROVISIONING_STATUS, + ProvisioningCallback.OSU_STATUS_THIRD_SOAP_EXCHANGE); + changeState(STATE_WAITING_FOR_THIRD_SOAP_RESPONSE); + } else { + Log.e(TAG, "HttpsConnection is not established for soap message exchange"); + resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE); + return; + } + } + private PasspointConfiguration buildPasspointConfiguration(@NonNull PpsMoData moData) { String moTree = moData.getPpsMoTree(); diff --git a/service/java/com/android/server/wifi/hotspot2/soap/ExchangeCompleteMessage.java b/service/java/com/android/server/wifi/hotspot2/soap/ExchangeCompleteMessage.java new file mode 100644 index 000000000..a07e9c787 --- /dev/null +++ b/service/java/com/android/server/wifi/hotspot2/soap/ExchangeCompleteMessage.java @@ -0,0 +1,52 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.util.Log; + +import org.ksoap2.serialization.SoapObject; + +/** + * Represents the sppExchangeComplete message sent by the server. + * For the details, refer to A.3.2 section in Hotspot2.0 rel2 specification. + */ +public class ExchangeCompleteMessage extends SppResponseMessage { + private static final String TAG = "ExchangeCompleteMessage"; + + private ExchangeCompleteMessage(@NonNull SoapObject response) throws IllegalArgumentException { + super(response, MessageType.EXCHANGE_COMPLETE); + } + + /** + * create an instance of {@link ExchangeCompleteMessage} + * + * @param response SOAP response message received from server. + * @return Instance of {@link ExchangeCompleteMessage}, {@code null} in any failure. + */ + public static ExchangeCompleteMessage createInstance(@NonNull SoapObject response) { + ExchangeCompleteMessage exchangeCompleteMessage; + + try { + exchangeCompleteMessage = new ExchangeCompleteMessage(response); + } catch (IllegalArgumentException e) { + Log.e(TAG, "fails to create an Instance: " + e); + return null; + } + return exchangeCompleteMessage; + } +} diff --git a/service/java/com/android/server/wifi/hotspot2/soap/SoapParser.java b/service/java/com/android/server/wifi/hotspot2/soap/SoapParser.java index cb7270b48..e8f725440 100644 --- a/service/java/com/android/server/wifi/hotspot2/soap/SoapParser.java +++ b/service/java/com/android/server/wifi/hotspot2/soap/SoapParser.java @@ -37,6 +37,9 @@ public class SoapParser { case "sppPostDevDataResponse": responseMessage = PostDevDataResponse.createInstance(response); break; + case "sppExchangeComplete": + responseMessage = ExchangeCompleteMessage.createInstance(response); + break; default: responseMessage = null; } diff --git a/service/java/com/android/server/wifi/hotspot2/soap/UpdateResponseMessage.java b/service/java/com/android/server/wifi/hotspot2/soap/UpdateResponseMessage.java new file mode 100644 index 000000000..53c2a5e78 --- /dev/null +++ b/service/java/com/android/server/wifi/hotspot2/soap/UpdateResponseMessage.java @@ -0,0 +1,68 @@ +/* + * 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; + +import android.annotation.NonNull; + +import org.ksoap2.SoapEnvelope; +import org.ksoap2.serialization.SoapObject; +import org.ksoap2.serialization.SoapSerializationEnvelope; + +/** + * This class represents sppUpdateResponse message, as part of the + * Subscription Provisioning Protocol. + * For the detail, refer to the Hotspot 2.0 rel2 specification. + */ +public class UpdateResponseMessage { + + /** + * Serialize the given request to a SOAP envelope. + * + * @param sessionId session id generated by the server to identify the session between device + * and server. + * @param isError {@code true} if the error happens during updating or installing PPS MO. + * @return {@link SoapSerializationEnvelope} + */ + public static SoapSerializationEnvelope serializeToSoapEnvelope(@NonNull String sessionId, + boolean isError) { + SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER12); + envelope.implicitTypes = true; // Do not include type in element attribute + envelope.setAddAdornments(false); // Do not generate/include IDs for each element + + SoapObject requestObject = + new SoapObject(SoapEnvelope.NS20, SppConstants.METHOD_UPDATE_RESPONSE); + requestObject.addAttribute(SoapEnvelope.NS20, SppConstants.ATTRIBUTE_SPP_VERSION, + SppConstants.SUPPORTED_SPP_VERSION); + requestObject.addAttribute(SoapEnvelope.NS20, SppConstants.ATTRIBUTE_SESSION_ID, sessionId); + if (isError) { + requestObject.addAttribute(SoapEnvelope.NS20, SppConstants.ATTRIBUTE_SPP_STATUS, + SppConstants.mapStatusIntToString(SppConstants.SppStatus.ERROR)); + SoapObject sppError = + new SoapObject(SoapEnvelope.NS20, SppConstants.PROPERTY_SPP_ERROR); + sppError.addAttribute(SppConstants.ATTRIBUTE_ERROR_CODE, + SppConstants.mapErrorIntToString( + SppConstants.SppError.MO_ADD_OR_UPDATE_FAILED)); + requestObject.addProperty(SoapEnvelope.NS20, SppConstants.PROPERTY_SPP_ERROR, sppError); + } else { + requestObject.addAttribute(SoapEnvelope.NS20, SppConstants.ATTRIBUTE_SPP_STATUS, + SppConstants.mapStatusIntToString(SppConstants.SppStatus.OK)); + } + + envelope.setOutputSoapObject(requestObject); + return 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 4b5a822d3..2214d53d3 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java @@ -50,8 +50,10 @@ import android.telephony.TelephonyManager; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.org.conscrypt.TrustManagerImpl; import com.android.server.wifi.WifiNative; +import com.android.server.wifi.hotspot2.soap.ExchangeCompleteMessage; import com.android.server.wifi.hotspot2.soap.PostDevDataResponse; 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; @@ -83,6 +85,7 @@ public class PasspointProvisionerTest { 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 int STEP_WAIT_FOR_THIRD_SOAP_RESPONSE = 5; private static final String TEST_DEV_ID = "12312341"; private static final String TEST_MANUFACTURER = Build.MANUFACTURER; @@ -130,6 +133,7 @@ public class PasspointProvisionerTest { @Mock SSLContext mTlsContext; @Mock WifiNative mWifiNative; @Mock PostDevDataResponse mSppResponseMessage; + @Mock ExchangeCompleteMessage mExchangeCompleteMessage; @Mock SystemInfo mSystemInfo; @Mock TelephonyManager mTelephonyManager; @Mock SppCommand mSppCommand; @@ -184,6 +188,12 @@ public class PasspointProvisionerTest { when(mSystemInfo.getFirmwareVersion()).thenReturn(TEST_FW_VERSION); when(mTelephonyManager.getSubscriberId()).thenReturn(TEST_IMSI); + when(mExchangeCompleteMessage.getMessageType()).thenReturn( + SppResponseMessage.MessageType.EXCHANGE_COMPLETE); + when(mExchangeCompleteMessage.getStatus()).thenReturn( + SppConstants.SppStatus.EXCHANGE_COMPLETE); + when(mExchangeCompleteMessage.getSessionID()).thenReturn(TEST_SESSION_ID); + when(mExchangeCompleteMessage.getError()).thenReturn(SppConstants.INVALID_SPP_CONSTANT); when(mSppResponseMessage.getMessageType()).thenReturn( SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE); when(mSppResponseMessage.getSppCommand()).thenReturn(mSppCommand); @@ -291,7 +301,14 @@ public class PasspointProvisionerTest { mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), mSppResponseMessage); mLooper.dispatchAll(); + } else if (step == STEP_WAIT_FOR_THIRD_SOAP_RESPONSE) { + verify(mCallback).onProvisioningStatus( + ProvisioningCallback.OSU_STATUS_THIRD_SOAP_EXCHANGE); + // Received a third soapMessageResponse + mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), + mExchangeCompleteMessage); + mLooper.dispatchAll(); } } } @@ -581,7 +598,7 @@ public class PasspointProvisionerTest { /** * Verifies that the right provisioning callbacks are invoked when a command of a second soap - * response is not for ADD MO command. + * response {@link PostDevDataResponse} is not for ADD MO command. */ @Test public void verifyNotAddMoCommandFailureForSecondSoapResponse() throws RemoteException { @@ -607,12 +624,34 @@ public class PasspointProvisionerTest { } /** + * Verifies that the right provisioning callbacks are invoked when a message of a third soap + * response {@link ExchangeCompleteMessage} has an error property. + */ + @Test + public void verifyHandlingErrorPropertyInThirdSoapResponse() throws RemoteException { + when(mExchangeCompleteMessage.getError()).thenReturn( + SppConstants.SppError.PROVISIONING_FAILED); + stopAfterStep(STEP_WAIT_FOR_SECOND_SOAP_RESPONSE); + + verify(mCallback).onProvisioningStatus( + ProvisioningCallback.OSU_STATUS_THIRD_SOAP_EXCHANGE); + + // Received a third soapMessageResponse + mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), + mExchangeCompleteMessage); + mLooper.dispatchAll(); + + verify(mCallback).onProvisioningFailure( + ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); + } + + /** * 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); + stopAfterStep(STEP_WAIT_FOR_THIRD_SOAP_RESPONSE); // No further runnables posted verifyNoMoreInteractions(mCallback); diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/ExchangeCompleteMessageTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/ExchangeCompleteMessageTest.java new file mode 100644 index 000000000..dcddb5380 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/ExchangeCompleteMessageTest.java @@ -0,0 +1,65 @@ +/* + * 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; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.ksoap2.serialization.SoapObject; + +/** + * Unit tests for {@link ExchangeCompleteMessage}. + */ +@SmallTest +public class ExchangeCompleteMessageTest { + private static final String TEST_STATUS = "OK"; + private static final String TEST_SESSION_ID = "D215D696517BA138F1D28442DF0F4E07"; + private static final String TEST_VERSION = "1.0"; + + private ExchangeCompleteMessage mExchangeCompleteMessage; + + /** + * Verify if the ExchangeCompleteMessage message is properly parsed. + */ + @Test + public void verifyValidExchangeComplete() { + SoapObject response = new SoapObject(); + response.addAttribute(SppResponseMessage.SPPStatusAttribute, TEST_STATUS); + response.addAttribute(SppResponseMessage.SPPSessionIDAttribute, TEST_SESSION_ID); + response.addAttribute(SppResponseMessage.SPPVersionAttribute, TEST_VERSION); + + mExchangeCompleteMessage = ExchangeCompleteMessage.createInstance(response); + + assertNotNull(mExchangeCompleteMessage); + } + + /** + * Verify if the exchangeComplete message missing session id will return {@code null} + */ + @Test + public void verifyInvalidExchangeCompleteReturnNull() { + SoapObject response = new SoapObject(); + response.addAttribute(SppResponseMessage.SPPStatusAttribute, TEST_STATUS); + response.addAttribute(SppResponseMessage.SPPVersionAttribute, TEST_VERSION); + mExchangeCompleteMessage = ExchangeCompleteMessage.createInstance(response); + + assertNull(mExchangeCompleteMessage); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/UpdateResponseMessageTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/UpdateResponseMessageTest.java new file mode 100644 index 000000000..edda5273e --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/UpdateResponseMessageTest.java @@ -0,0 +1,91 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.ksoap2.SoapEnvelope; +import org.ksoap2.serialization.SoapObject; +import org.ksoap2.serialization.SoapSerializationEnvelope; + +/** + * Unit tests for {@link UpdateResponseMessage}. + */ +@SmallTest +public class UpdateResponseMessageTest { + private static final String TEST_SESSION_ID = "123456"; + + /** + * Verify the request is organized correctly with a session id. + */ + @Test + public void serializeUpdateResponseMessageWithSessionIdWithoutError() { + SoapSerializationEnvelope request = UpdateResponseMessage.serializeToSoapEnvelope( + TEST_SESSION_ID, false); + assertNotNull(request); + assertEquals(SoapEnvelope.VER12, request.version); + SoapObject soapRequest = (SoapObject) request.bodyOut; + verifyCommonFields(soapRequest); + + // Should have an OK status attribute + assertEquals(SppConstants.mapStatusIntToString(SppConstants.SppStatus.OK), + soapRequest.getAttributeAsString(SoapEnvelope.NS20, + SppConstants.ATTRIBUTE_SPP_STATUS)); + } + + /** + * Verify the request is organized correctly with a session id and an error. + */ + @Test + public void serializeUpdateResponseMessageWithError() { + SoapSerializationEnvelope request = UpdateResponseMessage.serializeToSoapEnvelope( + TEST_SESSION_ID, true); + assertNotNull(request); + assertEquals(SoapEnvelope.VER12, request.version); + SoapObject soapRequest = (SoapObject) request.bodyOut; + verifyCommonFields(soapRequest); + + // Should have an error status attribute + assertEquals(SppConstants.mapStatusIntToString(SppConstants.SppStatus.ERROR), + soapRequest.getAttributeAsString(SoapEnvelope.NS20, + SppConstants.ATTRIBUTE_SPP_STATUS)); + assertEquals(1, soapRequest.getPropertyCount()); + + SoapObject sppError = (SoapObject) soapRequest.getProperty(0); + assertNotNull(sppError); + assertEquals(sppError.getNamespace(), SoapEnvelope.NS20); + assertEquals( + SppConstants.mapErrorIntToString(SppConstants.SppError.MO_ADD_OR_UPDATE_FAILED), + sppError.getAttributeAsString(SppConstants.ATTRIBUTE_ERROR_CODE)); + } + + private void verifyCommonFields(SoapObject request) { + assertEquals(request.getNamespace(), SoapEnvelope.NS20); + assertEquals(request.getName(), SppConstants.METHOD_UPDATE_RESPONSE); + assertEquals(SppConstants.SUPPORTED_SPP_VERSION, + request.getAttributeAsString(SoapEnvelope.NS20, + SppConstants.ATTRIBUTE_SPP_VERSION)); + + // Should have a session id attribute + assertEquals(TEST_SESSION_ID, request.getAttributeAsString(SoapEnvelope.NS20, + SppConstants.ATTRIBUTE_SESSION_ID)); + } +} |