aboutsummaryrefslogtreecommitdiff
path: root/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java')
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java1646
1 files changed, 1646 insertions, 0 deletions
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
new file mode 100644
index 00000000..f6c32eef
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
@@ -0,0 +1,1646 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net.ipsec.ike;
+
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+import static android.system.OsConstants.AF_INET;
+
+import static com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CMD_FORCE_TRANSITION;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS;
+import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA;
+import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_DELETE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_KE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NONCE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_INITIATOR;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_RESPONDER;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_ESP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionOptions;
+import android.net.ipsec.ike.IkeManager;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionOptions;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CreateChildSaHelper;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
+import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecord;
+import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecordConfig;
+import com.android.internal.net.ipsec.ike.SaRecord.ISaRecordHelper;
+import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Address;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Netmask;
+import com.android.internal.net.ipsec.ike.message.IkeDeletePayload;
+import com.android.internal.net.ipsec.ike.message.IkeKePayload;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeNoncePayload;
+import com.android.internal.net.ipsec.ike.message.IkeNotifyPayload;
+import com.android.internal.net.ipsec.ike.message.IkePayload;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+import com.android.internal.net.ipsec.ike.message.IkeTestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
+import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.internal.net.utils.Log;
+import com.android.server.IpSecService;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public final class ChildSessionStateMachineTest {
+ private static final String TAG = "ChildSessionStateMachineTest";
+
+ private static final Inet4Address LOCAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
+ private static final Inet4Address REMOTE_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+ private static final Inet4Address INTERNAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("203.0.113.100"));
+
+ private static final int IPV4_PREFIX_LEN = 32;
+
+ private static final String IKE_AUTH_RESP_SA_PAYLOAD =
+ "2c00002c0000002801030403cae7019f0300000c0100000c800e0080"
+ + "03000008030000020000000805000000";
+ private static final String REKEY_CHILD_RESP_SA_PAYLOAD =
+ "2800002c0000002801030403cd1736b30300000c0100000c800e0080"
+ + "03000008030000020000000805000000";
+ private static final String REKEY_CHILD_REQ_SA_PAYLOAD =
+ "2800002c0000002801030403c88336490300000c0100000c800e0080"
+ + "03000008030000020000000805000000";
+ private static final String REKEY_CHILD_UNACCEPTABLE_REQ_SA_PAYLOAD =
+ "2800002c0000002801030403c88336490300000c0100000c800e00c0"
+ + "03000008030000020000000805000000";
+
+ private static final int CURRENT_CHILD_SA_SPI_IN = 0x2ad4c0a2;
+ private static final int CURRENT_CHILD_SA_SPI_OUT = 0xcae7019f;
+
+ private static final int LOCAL_INIT_NEW_CHILD_SA_SPI_IN = 0x57a09b0f;
+ private static final int LOCAL_INIT_NEW_CHILD_SA_SPI_OUT = 0xcd1736b3;
+
+ private static final int REMOTE_INIT_NEW_CHILD_SA_SPI_IN = 0xd2d01795;
+ private static final int REMOTE_INIT_NEW_CHILD_SA_SPI_OUT = 0xc8833649;
+
+ private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6";
+ private static final byte[] SK_D = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING);
+
+ private static final int KEY_LEN_IKE_SKD = 20;
+
+ private IkeMacPrf mIkePrf;
+
+ private Context mContext;
+ private IpSecService mMockIpSecService;
+ private IpSecManager mMockIpSecManager;
+ private UdpEncapsulationSocket mMockUdpEncapSocket;
+
+ private TestLooper mLooper;
+ private ChildSessionStateMachine mChildSessionStateMachine;
+
+ private List<IkePayload> mFirstSaReqPayloads = new LinkedList<>();
+ private List<IkePayload> mFirstSaRespPayloads = new LinkedList<>();
+
+ private ChildSaRecord mSpyCurrentChildSaRecord;
+ private ChildSaRecord mSpyLocalInitNewChildSaRecord;
+ private ChildSaRecord mSpyRemoteInitNewChildSaRecord;
+
+ private Log mSpyIkeLog;
+
+ private ISaRecordHelper mMockSaRecordHelper;
+
+ private ChildSessionOptions mChildSessionOptions;
+ private EncryptionTransform mChildEncryptionTransform;
+ private IntegrityTransform mChildIntegrityTransform;
+ private DhGroupTransform mChildDhGroupTransform;
+
+ private ChildSaProposal mMockNegotiatedProposal;
+
+ private Executor mSpyUserCbExecutor;
+ private ChildSessionCallback mMockChildSessionCallback;
+ private IChildSessionSmCallback mMockChildSessionSmCallback;
+
+ private ArgumentCaptor<ChildSaRecordConfig> mChildSaRecordConfigCaptor =
+ ArgumentCaptor.forClass(ChildSaRecordConfig.class);
+ private ArgumentCaptor<List<IkePayload>> mPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+ private ArgumentCaptor<ChildSessionConfiguration> mChildConfigCaptor =
+ ArgumentCaptor.forClass(ChildSessionConfiguration.class);
+
+ private ArgumentMatcher<ChildLocalRequest> mRekeyChildLocalReqMatcher =
+ (argument) -> {
+ return CMD_LOCAL_REQUEST_REKEY_CHILD == argument.procedureType
+ && mMockChildSessionCallback == argument.childSessionCallback;
+ };
+
+ public ChildSessionStateMachineTest() {
+ mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
+ mMockChildSessionSmCallback = mock(IChildSessionSmCallback.class);
+
+ mChildEncryptionTransform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128);
+ mChildIntegrityTransform =
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ mChildDhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP);
+ }
+
+ @Before
+ public void setup() throws Exception {
+ mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG);
+ IkeManager.setIkeLog(mSpyIkeLog);
+
+ mIkePrf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+
+ mContext = InstrumentationRegistry.getContext();
+ mMockIpSecService = mock(IpSecService.class);
+ mMockIpSecManager = new IpSecManager(mContext, mMockIpSecService);
+ mMockUdpEncapSocket = mock(UdpEncapsulationSocket.class);
+
+ mMockNegotiatedProposal = mock(ChildSaProposal.class);
+
+ mSpyUserCbExecutor =
+ spy(
+ (command) -> {
+ command.run();
+ });
+
+ mMockChildSessionCallback = mock(ChildSessionCallback.class);
+ mChildSessionOptions = buildChildSessionOptions();
+
+ // Setup thread and looper
+ mLooper = new TestLooper();
+ mChildSessionStateMachine =
+ new ChildSessionStateMachine(
+ mLooper.getLooper(),
+ mContext,
+ mMockIpSecManager,
+ mChildSessionOptions,
+ mSpyUserCbExecutor,
+ mMockChildSessionCallback,
+ mMockChildSessionSmCallback);
+ mChildSessionStateMachine.setDbg(true);
+ SaRecord.setSaRecordHelper(mMockSaRecordHelper);
+
+ setUpFirstSaNegoPayloadLists();
+ setUpChildSaRecords();
+
+ mChildSessionStateMachine.start();
+ }
+
+ @After
+ public void tearDown() {
+ mChildSessionStateMachine.setDbg(false);
+ IkeManager.resetIkeLog();
+ SaRecord.setSaRecordHelper(new SaRecordHelper());
+ }
+
+ private ChildSaProposal buildSaProposal() throws Exception {
+ return new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .build();
+ }
+
+ private ChildSessionOptions buildChildSessionOptions() throws Exception {
+ return new TunnelModeChildSessionOptions.Builder()
+ .addSaProposal(buildSaProposal())
+ .addInternalAddressRequest(AF_INET, 1)
+ .addInternalAddressRequest(INTERNAL_ADDRESS, IPV4_PREFIX_LEN)
+ .build();
+ }
+
+ private void setUpChildSaRecords() {
+ mSpyCurrentChildSaRecord =
+ makeSpyChildSaRecord(CURRENT_CHILD_SA_SPI_IN, CURRENT_CHILD_SA_SPI_OUT);
+ mSpyLocalInitNewChildSaRecord =
+ makeSpyChildSaRecord(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_IN, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+ mSpyRemoteInitNewChildSaRecord =
+ makeSpyChildSaRecord(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_IN, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT);
+ }
+
+ private void setUpSpiResource(InetAddress address, int spiRequested) throws Exception {
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ eq(address.getHostAddress()), anyInt(), anyObject()))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(spiRequested));
+ }
+
+ private void setUpFirstSaNegoPayloadLists() throws Exception {
+ // Build locally generated SA payload that has its SPI resource allocated.
+ setUpSpiResource(LOCAL_ADDRESS, CURRENT_CHILD_SA_SPI_IN);
+ IkeSaPayload reqSaPayload =
+ IkeSaPayload.createChildSaRequestPayload(
+ mChildSessionOptions.getSaProposals(), mMockIpSecManager, LOCAL_ADDRESS);
+ mFirstSaReqPayloads.add(reqSaPayload);
+
+ // Build a remotely generated SA payload whoes SPI resource has not been allocated.
+ setUpSpiResource(REMOTE_ADDRESS, CURRENT_CHILD_SA_SPI_OUT);
+ IkeSaPayload respSaPayload =
+ (IkeSaPayload)
+ (IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA, true, IKE_AUTH_RESP_SA_PAYLOAD));
+ mFirstSaRespPayloads.add(respSaPayload);
+
+ // Build TS Payloads
+ IkeTsPayload tsInitPayload =
+ new IkeTsPayload(
+ true /*isInitiator*/, mChildSessionOptions.getLocalTrafficSelectors());
+ IkeTsPayload tsRespPayload =
+ new IkeTsPayload(
+ false /*isInitiator*/, mChildSessionOptions.getRemoteTrafficSelectors());
+
+ mFirstSaReqPayloads.add(tsInitPayload);
+ mFirstSaReqPayloads.add(tsRespPayload);
+ mFirstSaRespPayloads.add(tsInitPayload);
+ mFirstSaRespPayloads.add(tsRespPayload);
+
+ // Build Nonce Payloads
+ mFirstSaReqPayloads.add(new IkeNoncePayload());
+ mFirstSaRespPayloads.add(new IkeNoncePayload());
+
+ // Build Config Request Payload
+ List<ConfigAttribute> attrReqList = new LinkedList<>();
+ attrReqList.add(new ConfigAttributeIpv4Address(INTERNAL_ADDRESS));
+ attrReqList.add(new ConfigAttributeIpv4Netmask());
+ mFirstSaReqPayloads.add(new IkeConfigPayload(false /*isReply*/, attrReqList));
+
+ // Build Config Reply Payload
+ List<ConfigAttribute> attrRespList = new LinkedList<>();
+ attrRespList.add(new ConfigAttributeIpv4Address(INTERNAL_ADDRESS));
+ mFirstSaRespPayloads.add(new IkeConfigPayload(true /*isReply*/, attrRespList));
+ }
+
+ private ChildSaRecord makeSpyChildSaRecord(int inboundSpi, int outboundSpi) {
+ ChildSaRecord child =
+ spy(
+ new ChildSaRecord(
+ inboundSpi,
+ outboundSpi,
+ true /*localInit*/,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ mock(IpSecTransform.class),
+ mock(IpSecTransform.class),
+ mock(ChildLocalRequest.class)));
+ doNothing().when(child).close();
+ return child;
+ }
+
+ private void quitAndVerify() {
+ mChildSessionStateMachine.mCurrentChildSaRecord = null;
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = null;
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = null;
+
+ reset(mMockChildSessionSmCallback);
+ mChildSessionStateMachine.quit();
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSessionClosed(mMockChildSessionCallback);
+ }
+
+ private void verifyChildSaRecordConfig(
+ ChildSaRecordConfig childSaRecordConfig,
+ int initSpi,
+ int respSpi,
+ boolean isLocalInit) {
+ assertEquals(mContext, childSaRecordConfig.context);
+ assertEquals(initSpi, childSaRecordConfig.initSpi.getSpi());
+ assertEquals(respSpi, childSaRecordConfig.respSpi.getSpi());
+
+ if (isLocalInit) {
+ assertEquals(LOCAL_ADDRESS, childSaRecordConfig.initAddress);
+ assertEquals(REMOTE_ADDRESS, childSaRecordConfig.respAddress);
+ } else {
+ assertEquals(REMOTE_ADDRESS, childSaRecordConfig.initAddress);
+ assertEquals(LOCAL_ADDRESS, childSaRecordConfig.respAddress);
+ }
+
+ assertEquals(mMockUdpEncapSocket, childSaRecordConfig.udpEncapSocket);
+ assertEquals(mIkePrf, childSaRecordConfig.ikePrf);
+ assertArrayEquals(SK_D, childSaRecordConfig.skD);
+ assertFalse(childSaRecordConfig.isTransport);
+ assertEquals(isLocalInit, childSaRecordConfig.isLocalInit);
+ assertTrue(childSaRecordConfig.hasIntegrityAlgo);
+ assertEquals(
+ CMD_LOCAL_REQUEST_REKEY_CHILD, childSaRecordConfig.futureRekeyEvent.procedureType);
+ assertEquals(
+ mMockChildSessionCallback,
+ childSaRecordConfig.futureRekeyEvent.childSessionCallback);
+ }
+
+ private void verifyNotifyUsersCreateIpSecSa(
+ ChildSaRecord childSaRecord, boolean expectInbound) {
+ IpSecTransform transform =
+ expectInbound
+ ? childSaRecord.getInboundIpSecTransform()
+ : childSaRecord.getOutboundIpSecTransform();
+ int direction = expectInbound ? IpSecManager.DIRECTION_IN : IpSecManager.DIRECTION_OUT;
+
+ verify(mMockChildSessionCallback).onIpSecTransformCreated(eq(transform), eq(direction));
+ }
+
+ private void verifyInitCreateChildResp(
+ List<IkePayload> reqPayloads, List<IkePayload> respPayloads) throws Exception {
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(
+ mSpyCurrentChildSaRecord.getRemoteSpi(), mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // Validate negotiated SA proposal.
+ ChildSaProposal negotiatedProposal = mChildSessionStateMachine.mSaProposal;
+ assertNotNull(negotiatedProposal);
+ assertEquals(
+ new EncryptionTransform[] {mChildEncryptionTransform},
+ negotiatedProposal.getEncryptionTransforms());
+ assertEquals(
+ new IntegrityTransform[] {mChildIntegrityTransform},
+ negotiatedProposal.getIntegrityTransforms());
+
+ // Validate current ChildSaRecord
+ verify(mMockSaRecordHelper)
+ .makeChildSaRecord(
+ eq(reqPayloads), eq(respPayloads), mChildSaRecordConfigCaptor.capture());
+ ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue();
+
+ verifyChildSaRecordConfig(
+ childSaRecordConfig,
+ CURRENT_CHILD_SA_SPI_IN,
+ CURRENT_CHILD_SA_SPI_OUT,
+ true /*isLocalInit*/);
+
+ assertEquals(mSpyCurrentChildSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord);
+
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+ verify(mMockChildSessionSmCallback)
+ .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // Verify users have been notified
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verifyNotifyUsersCreateIpSecSa(mSpyCurrentChildSaRecord, true /*expectInbound*/);
+ verifyNotifyUsersCreateIpSecSa(mSpyCurrentChildSaRecord, false /*expectInbound*/);
+ verify(mMockChildSessionCallback).onOpened(mChildConfigCaptor.capture());
+
+ // Verify Child Session Configuration
+ ChildSessionConfiguration sessionConfig = mChildConfigCaptor.getValue();
+ verifyTsList(
+ Arrays.asList(mChildSessionOptions.getLocalTrafficSelectors()),
+ sessionConfig.getInboundTrafficSelectors());
+ verifyTsList(
+ Arrays.asList(mChildSessionOptions.getRemoteTrafficSelectors()),
+ sessionConfig.getOutboundTrafficSelectors());
+
+ List<LinkAddress> addrList = sessionConfig.getInternalAddressList();
+ assertEquals(1, addrList.size());
+ assertEquals(INTERNAL_ADDRESS, addrList.get(0).getAddress());
+ assertEquals(IPV4_PREFIX_LEN, addrList.get(0).getPrefixLength());
+ }
+
+ private void verifyTsList(
+ List<IkeTrafficSelector> expectedList, List<IkeTrafficSelector> tsList) {
+ assertEquals(expectedList.size(), tsList.size());
+ for (int i = 0; i < expectedList.size(); i++) {
+ assertEquals(expectedList.get(i), tsList.get(i));
+ }
+ }
+
+ @Test
+ public void testCreateFirstChild() throws Exception {
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenReturn(mSpyCurrentChildSaRecord);
+
+ mChildSessionStateMachine.handleFirstChildExchange(
+ mFirstSaReqPayloads,
+ mFirstSaRespPayloads,
+ LOCAL_ADDRESS,
+ REMOTE_ADDRESS,
+ mMockUdpEncapSocket,
+ mIkePrf,
+ SK_D);
+ mLooper.dispatchAll();
+
+ verifyInitCreateChildResp(mFirstSaReqPayloads, mFirstSaRespPayloads);
+
+ quitAndVerify();
+ }
+
+ private void verifyOutboundCreatePayloadTypes(
+ List<IkePayload> outboundPayloads, boolean isRekey) {
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_SA, IkeSaPayload.class, outboundPayloads));
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class, outboundPayloads));
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class, outboundPayloads));
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_NONCE, IkeNoncePayload.class, outboundPayloads));
+ assertNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_KE, IkeKePayload.class, outboundPayloads));
+
+ IkeConfigPayload configPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_CP, IkeConfigPayload.class, outboundPayloads);
+ if (isRekey) {
+ assertNull(configPayload);
+ } else {
+ assertNotNull(configPayload);
+ assertEquals(IkeConfigPayload.CONFIG_TYPE_REQUEST, configPayload.configType);
+ }
+ }
+
+ @Test
+ public void testCreateChild() throws Exception {
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenReturn(mSpyCurrentChildSaRecord);
+
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ // Validate outbound payload list
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ eq(false),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+
+ List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue();
+ verifyOutboundCreatePayloadTypes(reqPayloadList, false /*isRekey*/);
+ assertTrue(
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, reqPayloadList)
+ .isEmpty());
+
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_CREATE_CHILD_SA, mFirstSaRespPayloads);
+ mLooper.dispatchAll();
+
+ verifyInitCreateChildResp(reqPayloadList, mFirstSaRespPayloads);
+
+ quitAndVerify();
+ }
+
+ private <T extends IkeException> void verifyHandleFatalErrorAndQuit(Class<T> exceptionClass) {
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSessionClosed(mMockChildSessionCallback);
+
+ verify(mMockChildSessionCallback).onClosedExceptionally(any(exceptionClass));
+ }
+
+ @Test
+ public void testCreateChildHandlesErrorNotifyResp() throws Exception {
+ // Send out Create request
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ // Receive error notification in Create response
+ IkeNotifyPayload notifyPayload = new IkeNotifyPayload(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+ List<IkePayload> respPayloads = new LinkedList<>();
+ respPayloads.add(notifyPayload);
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify no SPI for provisional Child was registered.
+ verify(mMockChildSessionSmCallback, never())
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(NoValidProposalChosenException.class);
+ }
+
+ @Test
+ public void testCreateChildHandlesRespWithMissingPayload() throws Exception {
+ // Send out Create request
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ // Receive response with no Nonce Payload
+ List<IkePayload> respPayloads = new LinkedList<>();
+ for (IkePayload payload : mFirstSaRespPayloads) {
+ if (IkePayload.PAYLOAD_TYPE_NONCE == payload.payloadType) continue;
+ respPayloads.add(payload);
+ }
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify SPI for provisional Child was registered and unregistered.
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(CURRENT_CHILD_SA_SPI_OUT, mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(CURRENT_CHILD_SA_SPI_OUT);
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testCreateChildHandlesKeyCalculationFail() throws Exception {
+ // Throw exception when building ChildSaRecord
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenThrow(
+ new GeneralSecurityException("testCreateChildHandlesKeyCalculationFail"));
+
+ // Send out and receive Create Child message
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_CREATE_CHILD_SA, mFirstSaRespPayloads);
+ mLooper.dispatchAll();
+
+ // Verify SPI for provisional Child was registered and unregistered.
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(CURRENT_CHILD_SA_SPI_OUT, mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(CURRENT_CHILD_SA_SPI_OUT);
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(IkeInternalException.class);
+ }
+
+ private void setupIdleStateMachine() throws Exception {
+ mChildSessionStateMachine.mLocalAddress = LOCAL_ADDRESS;
+ mChildSessionStateMachine.mRemoteAddress = REMOTE_ADDRESS;
+ mChildSessionStateMachine.mUdpEncapSocket = mMockUdpEncapSocket;
+ mChildSessionStateMachine.mIkePrf = mIkePrf;
+ mChildSessionStateMachine.mSkD = SK_D;
+
+ mChildSessionStateMachine.mSaProposal = buildSaProposal();
+ mChildSessionStateMachine.mChildCipher = mock(IkeCipher.class);
+ mChildSessionStateMachine.mChildIntegrity = mock(IkeMacIntegrity.class);
+ mChildSessionStateMachine.mLocalTs = mChildSessionOptions.getLocalTrafficSelectors();
+ mChildSessionStateMachine.mRemoteTs = mChildSessionOptions.getRemoteTrafficSelectors();
+
+ mChildSessionStateMachine.mCurrentChildSaRecord = mSpyCurrentChildSaRecord;
+
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ }
+
+ private List<IkePayload> makeDeletePayloads(int spi) {
+ List<IkePayload> inboundPayloads = new ArrayList<>(1);
+ inboundPayloads.add(new IkeDeletePayload(new int[] {spi}));
+ return inboundPayloads;
+ }
+
+ private void verifyOutboundDeletePayload(int expectedSpi, boolean isResp) {
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(isResp),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+
+ List<IkePayload> outPayloadList = mPayloadListCaptor.getValue();
+ assertEquals(1, outPayloadList.size());
+
+ List<IkeDeletePayload> deletePayloads =
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_DELETE, IkeDeletePayload.class, outPayloadList);
+ assertEquals(1, deletePayloads.size());
+ IkeDeletePayload deletePayload = deletePayloads.get(0);
+ assertEquals(expectedSpi, deletePayload.spisToDelete[0]);
+ }
+
+ private void verifyNotifyUserDeleteChildSa(ChildSaRecord childSaRecord) {
+ verify(mMockChildSessionCallback)
+ .onIpSecTransformDeleted(
+ eq(childSaRecord.getInboundIpSecTransform()),
+ eq(IpSecManager.DIRECTION_IN));
+ verify(mMockChildSessionCallback)
+ .onIpSecTransformDeleted(
+ eq(childSaRecord.getOutboundIpSecTransform()),
+ eq(IpSecManager.DIRECTION_OUT));
+ }
+
+ private void verifyNotifyUsersDeleteSession() {
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockChildSessionCallback).onClosed();
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testDeleteChildLocal() throws Exception {
+ setupIdleStateMachine();
+
+ // Test initiating Delete request
+ mChildSessionStateMachine.deleteChildSession();
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.DeleteChildLocalDelete);
+ verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), false /*isResp*/);
+
+ // Test receiving Delete response
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verifyNotifyUsersDeleteSession();
+ }
+
+ @Test
+ public void testDeleteChildLocalHandlesInvalidResp() throws Exception {
+ setupIdleStateMachine();
+
+ // Test initiating Delete request
+ mChildSessionStateMachine.deleteChildSession();
+ mLooper.dispatchAll();
+
+ // Test receiving response with no Delete Payload
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_INFORMATIONAL, new LinkedList<>());
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ verify(mMockChildSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testDeleteChildLocalInInitial() throws Exception {
+ mChildSessionStateMachine.deleteChildSession();
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testSimultaneousDeleteChild() throws Exception {
+ setupIdleStateMachine();
+
+ mChildSessionStateMachine.deleteChildSession();
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+ assertTrue(respPayloadList.isEmpty());
+
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_INFORMATIONAL, new LinkedList<>());
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verifyNotifyUsersDeleteSession();
+ }
+
+ @Test
+ public void testReplyRekeyRequestDuringDeletion() throws Exception {
+ setupIdleStateMachine();
+
+ mChildSessionStateMachine.deleteChildSession();
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, mock(List.class));
+ mLooper.dispatchAll();
+
+ // Verify outbound response to Rekey Child request
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+ assertEquals(1, respPayloadList.size());
+
+ IkeNotifyPayload notifyPayload = (IkeNotifyPayload) respPayloadList.get(0);
+ assertEquals(ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType);
+ assertEquals(0, notifyPayload.notifyData.length);
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.DeleteChildLocalDelete);
+ }
+
+ @Test
+ public void testDeleteChildRemote() throws Exception {
+ setupIdleStateMachine();
+
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ // Verify response
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+
+ assertEquals(1, respPayloadList.size());
+ assertArrayEquals(
+ new int[] {mSpyCurrentChildSaRecord.getLocalSpi()},
+ ((IkeDeletePayload) respPayloadList.get(0)).spisToDelete);
+
+ verifyNotifyUsersDeleteSession();
+ }
+
+ private void verifyOutboundRekeySaPayload(List<IkePayload> outboundPayloads, boolean isResp) {
+ IkeSaPayload saPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_SA, IkeSaPayload.class, outboundPayloads);
+ assertEquals(isResp, saPayload.isSaResponse);
+ assertEquals(1, saPayload.proposalList.size());
+
+ IkeSaPayload.ChildProposal proposal =
+ (IkeSaPayload.ChildProposal) saPayload.proposalList.get(0);
+ assertEquals(1, proposal.number); // Must be 1-indexed
+ assertEquals(mChildSessionStateMachine.mSaProposal, proposal.saProposal);
+ }
+
+ private void verifyOutboundRekeyNotifyPayload(List<IkePayload> outboundPayloads) {
+ List<IkeNotifyPayload> notifyPayloads =
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, outboundPayloads);
+ assertEquals(1, notifyPayloads.size());
+ IkeNotifyPayload notifyPayload = notifyPayloads.get(0);
+ assertEquals(NOTIFY_TYPE_REKEY_SA, notifyPayload.notifyType);
+ assertEquals(PROTOCOL_ID_ESP, notifyPayload.protocolId);
+ assertEquals(mSpyCurrentChildSaRecord.getLocalSpi(), notifyPayload.spi);
+ }
+
+ @Test
+ public void testRekeyChildLocalCreateSendsRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ eq(false),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+
+ // Verify outbound payload list
+ List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue();
+ verifyOutboundCreatePayloadTypes(reqPayloadList, true /*isRekey*/);
+
+ verifyOutboundRekeySaPayload(reqPayloadList, false /*isResp*/);
+ verifyOutboundRekeyNotifyPayload(reqPayloadList);
+ }
+
+ private List<IkePayload> makeInboundRekeyChildPayloads(
+ int remoteSpi, String inboundSaHexString, boolean isLocalInitRekey) throws Exception {
+ List<IkePayload> inboundPayloads = new LinkedList<>();
+
+ IkeSaPayload saPayload =
+ (IkeSaPayload)
+ (IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA, true, inboundSaHexString));
+ inboundPayloads.add(saPayload);
+
+ // Build TS Payloads
+ IkeTrafficSelector[] initTs =
+ isLocalInitRekey
+ ? mChildSessionStateMachine.mLocalTs
+ : mChildSessionStateMachine.mRemoteTs;
+ IkeTrafficSelector[] respTs =
+ isLocalInitRekey
+ ? mChildSessionStateMachine.mRemoteTs
+ : mChildSessionStateMachine.mLocalTs;
+ inboundPayloads.add(new IkeTsPayload(true /*isInitiator*/, initTs));
+ inboundPayloads.add(new IkeTsPayload(false /*isInitiator*/, respTs));
+
+ // Build Nonce Payloads
+ inboundPayloads.add(new IkeNoncePayload());
+
+ if (isLocalInitRekey) {
+ // Rekey-Create response without Notify-Rekey payload is valid.
+ return inboundPayloads;
+ }
+
+ // Build Rekey-Notification
+ inboundPayloads.add(
+ new IkeNotifyPayload(
+ PROTOCOL_ID_ESP,
+ mSpyCurrentChildSaRecord.getRemoteSpi(),
+ NOTIFY_TYPE_REKEY_SA,
+ new byte[0]));
+
+ return inboundPayloads;
+ }
+
+ @Test
+ public void testRekeyChildLocalCreateValidatesResponse() throws Exception {
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+ setUpSpiResource(REMOTE_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+
+ // Prepare "rekeyed" SA and receive Rekey response
+ List<IkePayload> rekeyRespPayloads =
+ makeInboundRekeyChildPayloads(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_RESP_SA_PAYLOAD,
+ true /*isLocalInitRekey*/);
+ when(mMockSaRecordHelper.makeChildSaRecord(
+ any(List.class), eq(rekeyRespPayloads), any(ChildSaRecordConfig.class)))
+ .thenReturn(mSpyLocalInitNewChildSaRecord);
+
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyRespPayloads);
+ mLooper.dispatchAll();
+
+ // Verify state transition
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalDelete);
+
+ // Verify newly created ChildSaRecord
+ assertEquals(
+ mSpyLocalInitNewChildSaRecord,
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord);
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(
+ eq(mSpyLocalInitNewChildSaRecord.getRemoteSpi()),
+ eq(mChildSessionStateMachine));
+ verify(mMockChildSessionSmCallback)
+ .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
+
+ verify(mMockSaRecordHelper)
+ .makeChildSaRecord(
+ any(List.class),
+ eq(rekeyRespPayloads),
+ mChildSaRecordConfigCaptor.capture());
+ ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue();
+ verifyChildSaRecordConfig(
+ childSaRecordConfig,
+ LOCAL_INIT_NEW_CHILD_SA_SPI_IN,
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ true /*isLocalInit*/);
+
+ // Verify users have been notified
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verifyNotifyUsersCreateIpSecSa(mSpyLocalInitNewChildSaRecord, true /*expectInbound*/);
+ verifyNotifyUsersCreateIpSecSa(mSpyLocalInitNewChildSaRecord, false /*expectInbound*/);
+ }
+
+ @Test
+ public void testRekeyLocalCreateHandlesErrorNotifyResp() throws Exception {
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+
+ // Receive error notification in Create response
+ IkeNotifyPayload notifyPayload = new IkeNotifyPayload(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE);
+ List<IkePayload> respPayloads = new LinkedList<>();
+ respPayloads.add(notifyPayload);
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify rekey has been rescheduled and Child Session is alive
+ verify(mMockChildSessionSmCallback)
+ .scheduleRetryLocalRequest(
+ (ChildLocalRequest) mSpyCurrentChildSaRecord.getFutureRekeyEvent());
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // Verify no SPI for provisional Child was registered.
+ verify(mMockChildSessionSmCallback, never())
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+ }
+
+ @Test
+ public void testRekeyLocalCreateHandlesRespWithMissingPayload() throws Exception {
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+ reset(mMockChildSessionSmCallback);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+
+ // Receive response with no SA Payload
+ List<IkePayload> validRekeyRespPayloads =
+ makeInboundRekeyChildPayloads(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_RESP_SA_PAYLOAD,
+ true /*isLocalInitRekey*/);
+ List<IkePayload> respPayloads = new LinkedList<>();
+ for (IkePayload payload : validRekeyRespPayloads) {
+ if (IkePayload.PAYLOAD_TYPE_SA == payload.payloadType) continue;
+ respPayloads.add(payload);
+ }
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(InvalidSyntaxException.class);
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+
+ // Verify no SPI for provisional Child was registered.
+ verify(mMockChildSessionSmCallback, never())
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+
+ // Verify retry was not scheduled
+ verify(mMockChildSessionSmCallback, never()).scheduleRetryLocalRequest(any());
+ }
+
+ @Test
+ public void testRekeyLocalCreateChildHandlesKeyCalculationFail() throws Exception {
+ // Throw exception when building ChildSaRecord
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenThrow(
+ new GeneralSecurityException(
+ "testRekeyCreateChildHandlesKeyCalculationFail"));
+
+ // Setup for rekey negotiation
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+ setUpSpiResource(REMOTE_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+ reset(mMockChildSessionSmCallback);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+
+ // Receive Rekey response
+ List<IkePayload> rekeyRespPayloads =
+ makeInboundRekeyChildPayloads(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_RESP_SA_PAYLOAD,
+ true /*isLocalInitRekey*/);
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyRespPayloads);
+ mLooper.dispatchAll();
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(IkeInternalException.class);
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+
+ // Verify SPI for provisional Child was registered and unregistered.
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+
+ // Verify retry was not scheduled
+ verify(mMockChildSessionSmCallback, never()).scheduleRetryLocalRequest(any());
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteSendsRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Verify outbound delete request
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalDelete);
+ verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), false /*isResp*/);
+
+ assertEquals(mSpyCurrentChildSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord);
+ assertEquals(
+ mSpyLocalInitNewChildSaRecord, mChildSessionStateMachine.mChildSaRecordSurviving);
+ }
+
+ void verifyChildSaUpdated(ChildSaRecord oldSaRecord, ChildSaRecord newSaRecord) {
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(oldSaRecord.getRemoteSpi());
+ verify(oldSaRecord).close();
+
+ assertNull(mChildSessionStateMachine.mChildSaRecordSurviving);
+ assertEquals(newSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteValidatesResponse() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete response
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // First invoked in #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyLocalInitNewChildSaRecord);
+
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockChildSessionCallback, never()).onClosed();
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteHandlesInvalidResp() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete response with missing Delete payload
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_INFORMATIONAL, new ArrayList<IkePayload>());
+ mLooper.dispatchAll();
+
+ // Verify rekey has finished
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyLocalInitNewChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+
+ // First invoked in #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+ }
+
+ @Test
+ public void testRekeyChildRemoteCreate() throws Exception {
+ setupIdleStateMachine();
+
+ // Setup for new Child SA negotiation.
+ setUpSpiResource(LOCAL_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_IN);
+ setUpSpiResource(REMOTE_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT);
+
+ List<IkePayload> rekeyReqPayloads =
+ makeInboundRekeyChildPayloads(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_REQ_SA_PAYLOAD,
+ false /*isLocalInitRekey*/);
+ when(mMockSaRecordHelper.makeChildSaRecord(
+ eq(rekeyReqPayloads), any(List.class), any(ChildSaRecordConfig.class)))
+ .thenReturn(mSpyRemoteInitNewChildSaRecord);
+
+ // Receive rekey Child request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildRemoteDelete);
+
+ // Verify outbound rekey response
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+ verifyOutboundCreatePayloadTypes(respPayloadList, true /*isRekey*/);
+
+ verifyOutboundRekeySaPayload(respPayloadList, true /*isResp*/);
+ verifyOutboundRekeyNotifyPayload(respPayloadList);
+
+ // Verify new Child SA
+ assertEquals(
+ mSpyRemoteInitNewChildSaRecord,
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord);
+
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(
+ eq(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()),
+ eq(mChildSessionStateMachine));
+ verify(mMockChildSessionSmCallback)
+ .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
+
+ verify(mMockSaRecordHelper)
+ .makeChildSaRecord(
+ eq(rekeyReqPayloads),
+ any(List.class),
+ mChildSaRecordConfigCaptor.capture());
+ ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue();
+ verifyChildSaRecordConfig(
+ childSaRecordConfig,
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REMOTE_INIT_NEW_CHILD_SA_SPI_IN,
+ false /*isLocalInit*/);
+
+ // Verify that users are notified the creation of new inbound IpSecTransform
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, true /*expectInbound*/);
+ }
+
+ private void verifyOutboundErrorNotify(int exchangeType, int errorCode) {
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(exchangeType),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+
+ assertEquals(1, respPayloadList.size());
+ IkePayload payload = respPayloadList.get(0);
+ assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType);
+ assertEquals(errorCode, ((IkeNotifyPayload) payload).notifyType);
+ }
+
+ @Test
+ public void testRekeyChildRemoteCreateHandlesInvalidReq() throws Exception {
+ setupIdleStateMachine();
+
+ List<IkePayload> rekeyReqPayloads =
+ makeInboundRekeyChildPayloads(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_UNACCEPTABLE_REQ_SA_PAYLOAD,
+ false /*isLocalInitRekey*/);
+
+ // Receive rekey Child request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent and state machind was back to Idle
+ verifyOutboundErrorNotify(EXCHANGE_TYPE_CREATE_CHILD_SA, ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyChildRemoteCreateSaCreationFail() throws Exception {
+ // Throw exception when building ChildSaRecord
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenThrow(
+ new GeneralSecurityException("testRekeyChildRemoteCreateSaCreationFail"));
+
+ setupIdleStateMachine();
+
+ List<IkePayload> rekeyReqPayloads =
+ makeInboundRekeyChildPayloads(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_REQ_SA_PAYLOAD,
+ false /*isLocalInitRekey*/);
+
+ // Receive rekey Child request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent and state machind was back to Idle
+ verifyOutboundErrorNotify(EXCHANGE_TYPE_CREATE_CHILD_SA, ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyChildRemoteDelete() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+
+ // Test receiving Delete request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ // Verify outbound Delete response
+ verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), true /*isResp*/);
+
+ // Verify Child SA has been updated
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyRemoteInitNewChildSaRecord);
+
+ // Verify procedure has been finished. #onProcedureFinished was first invoked in
+ // #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+ verify(mMockChildSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteWithReqForNewSa() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete new Child SA request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyLocalInitNewChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ // Verify outbound Delete response on new Child SA
+ verifyOutboundDeletePayload(mSpyLocalInitNewChildSaRecord.getLocalSpi(), true /*isResp*/);
+ verify(mMockChildSessionSmCallback)
+ .onChildSaDeleted(mSpyLocalInitNewChildSaRecord.getRemoteSpi());
+ verify(mSpyLocalInitNewChildSaRecord).close();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyLocalInitNewChildSaRecord);
+
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildRemoteDeleteWithReqForNewSa() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete new Child SA request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ // Verify outbound Delete response on new Child SA
+ verifyOutboundDeletePayload(mSpyRemoteInitNewChildSaRecord.getLocalSpi(), true /*isResp*/);
+ verify(mMockChildSessionSmCallback)
+ .onChildSaDeleted(mSpyRemoteInitNewChildSaRecord.getRemoteSpi());
+ verify(mSpyRemoteInitNewChildSaRecord).close();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verify(mSpyUserCbExecutor, times(3)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyRemoteInitNewChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildRemoteDeleteTimeout() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS);
+ mLooper.dispatchAll();
+
+ // Verify no response sent.
+ verify(mMockChildSessionSmCallback, never())
+ .onOutboundPayloadsReady(anyInt(), anyBoolean(), any(List.class), anyObject());
+
+ // Verify Child SA has been renewed
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyRemoteInitNewChildSaRecord);
+
+ // Verify procedure has been finished. #onProcedureFinished was first invoked in
+ // #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+
+ verify(mMockChildSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildRemoteDeleteExitAndRenter() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Trigger a timeout, and immediately re-enter remote-delete
+ mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mChildSessionStateMachine.sendMessage(ChildSessionStateMachine.TIMEOUT_REKEY_REMOTE_DELETE);
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Shift time forward
+ mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mLooper.dispatchAll();
+
+ // Verify final state has not changed - timeout was not triggered.
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildRemoteDelete);
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+
+ verify(mMockChildSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testCloseSessionNow() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+
+ mChildSessionStateMachine.killSession();
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verify(mSpyUserCbExecutor, times(3)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyLocalInitNewChildSaRecord);
+
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testValidateExpectKeExistCase() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform});
+ List<IkePayload> payloadList = new LinkedList<>();
+ payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_1024_BIT_MODP));
+
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ }
+
+ @Test
+ public void testValidateExpectNoKeExistCase() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms()).thenReturn(new DhGroupTransform[0]);
+ List<IkePayload> payloadList = new LinkedList<>();
+
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ }
+
+ @Test
+ public void testThrowWhenKeMissing() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform});
+ List<IkePayload> payloadList = new LinkedList<>();
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to the absence of KE Payload");
+ } catch (InvalidSyntaxException expected) {
+ }
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to the absence of KE Payload");
+ } catch (InvalidKeException expected) {
+ }
+ }
+
+ @Test
+ public void testThrowWhenKeHasMismatchedDhGroup() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform});
+ List<IkePayload> payloadList = new LinkedList<>();
+ payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_2048_BIT_MODP));
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to mismatched DH Group");
+ } catch (InvalidSyntaxException expected) {
+ }
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to mismatched DH Group");
+ } catch (InvalidKeException expected) {
+ }
+ }
+
+ @Test
+ public void testThrowForUnexpectedKe() throws Exception {
+ DhGroupTransform noneGroup = new DhGroupTransform(SaProposal.DH_GROUP_NONE);
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {noneGroup});
+ List<IkePayload> payloadList = new LinkedList<>();
+ payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_2048_BIT_MODP));
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to unexpected KE payload.");
+ } catch (InvalidSyntaxException expected) {
+ }
+
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ }
+
+ @Test
+ public void testHandleUnexpectedException() throws Exception {
+ Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG);
+ IkeManager.setIkeLog(spyIkeLog);
+
+ mChildSessionStateMachine.createChildSession(
+ null /*localAddress*/, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ verifyHandleFatalErrorAndQuit(IkeInternalException.class);
+ verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
+ }
+}