aboutsummaryrefslogtreecommitdiff
path: root/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java')
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java4036
1 files changed, 4036 insertions, 0 deletions
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
new file mode 100644
index 00000000..94f4d622
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
@@ -0,0 +1,4036 @@
+/*
+ * 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_CHILD_SA_NOT_FOUND;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
+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.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
+
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET;
+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.RETRY_INTERVAL_MS;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.SA_SOFT_LIFETIME_MS;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.TEMP_FAILURE_RETRY_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_IKEV2_FRAGMENTATION_SUPPORTED;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
+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 org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+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.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+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.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionOptions;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeManager;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionOptions;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TransportModeChildSessionOptions;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.eap.EapAuthenticator;
+import com.android.internal.net.eap.IEapCallback;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachineFactory.ChildSessionFactoryHelper;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachineFactory.IChildSessionFactoryHelper;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest;
+import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.ReceivedIkePacket;
+import com.android.internal.net.ipsec.ike.SaRecord.ISaRecordHelper;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecordConfig;
+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.AuthenticationFailedException;
+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.exceptions.UnsupportedCriticalPayloadException;
+import com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload;
+import com.android.internal.net.ipsec.ike.message.IkeAuthPayload;
+import com.android.internal.net.ipsec.ike.message.IkeAuthPskPayload;
+import com.android.internal.net.ipsec.ike.message.IkeCertX509CertPayload;
+import com.android.internal.net.ipsec.ike.message.IkeDeletePayload;
+import com.android.internal.net.ipsec.ike.message.IkeEapPayload;
+import com.android.internal.net.ipsec.ike.message.IkeHeader;
+import com.android.internal.net.ipsec.ike.message.IkeIdPayload;
+import com.android.internal.net.ipsec.ike.message.IkeInformationalPayload;
+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.IkeMessage.DecodeResult;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultOk;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultPartial;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultProtectedError;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultUnprotectedError;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.IIkeMessageHelper;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.IkeMessageHelper;
+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.IkeSkfPayload;
+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.CertUtils;
+import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.internal.net.ipsec.ike.utils.Retransmitter;
+import com.android.internal.net.ipsec.ike.utils.Retransmitter.IBackoffTimeoutCalculator;
+import com.android.internal.net.utils.Log;
+import com.android.internal.util.State;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public final class IkeSessionStateMachineTest {
+ private static final String TAG = "IkeSessionStateMachineTest";
+
+ private static final Inet4Address LOCAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
+ private static final Inet4Address REMOTE_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("127.0.0.1"));
+
+ private static final String IKE_INIT_RESP_HEX_STRING =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c21202220000000000000014c220000300000"
+ + "002c010100040300000c0100000c800e008003000008030000020300000802000002"
+ + "00000008040000022800008800020000fe014fefed55a4229928bfa3dad1ea6ffaca"
+ + "abfb5f5bdd71790e99a192530e3f849d3a3d96dc6e0a7a10ff6f72a6162103ac573c"
+ + "acd41d08b7a034cad8f5eab09c14ced5a9e4af5692dff028f21c1119dd75226b6af6"
+ + "b2f009245369c9892cc5742e5c94a254ebff052470771fb2cb4f29a35d8953e18a1a"
+ + "6c6fbc56acc188a5290000249756112ca539f5c25abacc7ee92b73091942a9c06950"
+ + "f98848f1af1694c4ddff2900001c00004004c53f054b976a25d75fde72dbf1c7b6c8"
+ + "c9aa9ca12900001c00004005b16d79b21c1bc89ca7350f42de805be0227e2ed62b00"
+ + "00080000401400000014882fe56d6fd20dbc2251613b2ebe5beb";
+ private static final String IKE_SA_PAYLOAD_HEX_STRING =
+ "220000300000002c010100040300000c0100000c800e00800300000803000002030"
+ + "00008020000020000000804000002";
+ private static final String IKE_REKEY_SA_PAYLOAD_HEX_STRING =
+ "22000038000000340101080400000000000000FF0300000c0100000c800e0080030"
+ + "000080300000203000008020000020000000804000002";
+ private static final String IKE_REKEY_UNACCEPTABLE_SA_PAYLOAD_HEX_STRING =
+ "22000038000000340101080400000000000000FF0300000c0100000c800e0080030"
+ + "00008030000020300000802000002000000080400000e";
+ private static final int IKE_REKEY_SA_INITIATOR_SPI = 0xff;
+ private static final String KE_PAYLOAD_HEX_STRING =
+ "2800008800020000b4a2faf4bb54878ae21d638512ece55d9236fc50"
+ + "46ab6cef82220f421f3ce6361faf36564ecb6d28798a94aa"
+ + "d7b2b4b603ddeaaa5630adb9ece8ac37534036040610ebdd"
+ + "92f46bef84f0be7db860351843858f8acf87056e272377f7"
+ + "0c9f2d81e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2"
+ + "6bbeb08214c7071376079587";
+ private static final String NONCE_INIT_PAYLOAD_HEX_STRING =
+ "29000024c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412";
+ private static final String NONCE_RESP_PAYLOAD_HEX_STRING =
+ "290000249756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff";
+ private static final String NONCE_INIT_HEX_STRING =
+ "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412";
+ private static final String NONCE_RESP_HEX_STRING =
+ "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff";
+ private static final String NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING =
+ "2900001c00004004e54f73b7d83f6beb881eab2051d8663f421d10b0";
+ private static final String NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING =
+ "2b00001c00004005d915368ca036004cb578ae3e3fb268509aeab190";
+ private static final String FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING = "290000080000402e";
+ private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000";
+ private static final String NOTIFY_REKEY_IKE_PAYLOAD_HEX_STRING = "2100000800004009";
+ private static final String ID_PAYLOAD_INITIATOR_HEX_STRING =
+ "290000180200000031313233343536373839414243444546";
+ private static final String ID_PAYLOAD_RESPONDER_HEX_STRING = "2700000c010000007f000001";
+ private static final String PSK_AUTH_RESP_PAYLOAD_HEX_STRING =
+ "2100001c0200000058f36412e9b7b38df817a9f7779b7a008dacdd25";
+ private static final String GENERIC_DIGITAL_SIGN_AUTH_RESP_HEX_STRING =
+ "300000580e0000000f300d06092a864886f70d01010b05006f76af4150d653c5d413"
+ + "6b9f69d905849bf075c563e6d14ccda42361ec3e7d12c72e2dece5711ea1d952f7b8e"
+ + "12c5d982aa4efdaeac36a02b222aa96242cc424";
+ private static final String CHILD_SA_PAYLOAD_HEX_STRING =
+ "2c00002c0000002801030403cae7019f0300000c0100000c800e008003000008030"
+ + "000020000000805000000";
+ private static final String TS_INIT_PAYLOAD_HEX_STRING =
+ "2d00001801000000070000100000ffff00000000ffffffff";
+ private static final String TS_RESP_PAYLOAD_HEX_STRING =
+ "2900001801000000070000100000ffff000000000fffffff";
+
+ private static final String PSK_HEX_STRING = "6A756E69706572313233";
+
+ private static final String PRF_KEY_INIT_HEX_STRING =
+ "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String PRF_KEY_RESP_HEX_STRING =
+ "A30E6B08BE56C0E6BFF4744143C75219299E1BEB";
+
+ private static final byte[] EAP_DUMMY_MSG = "EAP Message".getBytes();
+
+ private static final int KEY_LEN_IKE_INTE = 20;
+ private static final int KEY_LEN_IKE_ENCR = 16;
+ private static final int KEY_LEN_IKE_PRF = 20;
+ private static final int KEY_LEN_IKE_SKD = KEY_LEN_IKE_PRF;
+
+ private static final int CHILD_SPI_LOCAL = 0x2ad4c0a2;
+ private static final int CHILD_SPI_REMOTE = 0xcae7019f;
+
+ private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234;
+ private static final int UDP_ENCAP_PORT = 34567;
+
+ private static final int EAP_SIM_SUB_ID = 1;
+
+ private static final int PAYLOAD_TYPE_UNSUPPORTED = 127;
+
+ private static final long RETRANSMIT_BACKOFF_TIMEOUT_MS = 5000L;
+
+ private MockIpSecTestUtils mMockIpSecTestUtils;
+ private Context mContext;
+ private IpSecManager mIpSecManager;
+ private UdpEncapsulationSocket mUdpEncapSocket;
+
+ private IkeSocket mSpyIkeSocket;
+
+ private TestLooper mLooper;
+ private IkeSessionStateMachine mIkeSessionStateMachine;
+
+ private byte[] mPsk;
+
+ private ChildSessionOptions mChildSessionOptions;
+
+ private Executor mSpyUserCbExecutor;
+ private IkeSessionCallback mMockIkeSessionCallback;
+ private ChildSessionCallback mMockChildSessionCallback;
+
+ private EncryptionTransform mIkeEncryptionTransform;
+ private IntegrityTransform mIkeIntegrityTransform;
+ private PrfTransform mIkePrfTransform;
+ private DhGroupTransform mIkeDhGroupTransform;
+
+ private IIkeMessageHelper mMockIkeMessageHelper;
+ private ISaRecordHelper mMockSaRecordHelper;
+ private IBackoffTimeoutCalculator mMockBackoffTimeoutCalculator;
+
+ private ChildSessionStateMachine mMockChildSessionStateMachine;
+ private IChildSessionFactoryHelper mMockChildSessionFactoryHelper;
+ private IChildSessionSmCallback mDummyChildSmCallback;
+
+ private IkeSaRecord mSpyCurrentIkeSaRecord;
+ private IkeSaRecord mSpyLocalInitIkeSaRecord;
+ private IkeSaRecord mSpyRemoteInitIkeSaRecord;
+
+ private Log mSpyIkeLog;
+
+ private int mExpectedCurrentSaLocalReqMsgId;
+ private int mExpectedCurrentSaRemoteReqMsgId;
+
+ private EapSessionConfig mEapSessionConfig;
+ private IkeEapAuthenticatorFactory mMockEapAuthenticatorFactory;
+ private EapAuthenticator mMockEapAuthenticator;
+
+ private X509Certificate mRootCertificate;
+ private X509Certificate mServerEndCertificate;
+
+ private ArgumentCaptor<IkeMessage> mIkeMessageCaptor =
+ ArgumentCaptor.forClass(IkeMessage.class);
+ private ArgumentCaptor<IkeSaRecordConfig> mIkeSaRecordConfigCaptor =
+ ArgumentCaptor.forClass(IkeSaRecordConfig.class);
+ private ArgumentCaptor<IChildSessionSmCallback> mChildSessionSmCbCaptor =
+ ArgumentCaptor.forClass(IChildSessionSmCallback.class);
+ private ArgumentCaptor<List<IkePayload>> mPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+
+ private ReceivedIkePacket makeDummyReceivedIkeInitRespPacket(
+ long initiatorSpi,
+ long responderSpi,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ boolean fromIkeInit,
+ List<Integer> payloadTypeList,
+ List<String> payloadHexStringList)
+ throws Exception {
+
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp);
+ // Build a remotely generated NAT_DETECTION_SOURCE_IP payload to mock a remote node's
+ // network that is not behind NAT.
+ IkePayload sourceNatPayload =
+ new IkeNotifyPayload(
+ NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP,
+ IkeNotifyPayload.generateNatDetectionData(
+ initiatorSpi,
+ responderSpi,
+ REMOTE_ADDRESS,
+ IkeSocket.IKE_SERVER_PORT));
+ payloadList.add(sourceNatPayload);
+ return makeDummyUnencryptedReceivedIkePacket(
+ initiatorSpi, responderSpi, eType, isResp, fromIkeInit, payloadList);
+ }
+
+ private ReceivedIkePacket makeDummyUnencryptedReceivedIkePacket(
+ long initiatorSpi,
+ long responderSpi,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ boolean fromIkeInit,
+ List<IkePayload> payloadList)
+ throws Exception {
+ IkeMessage dummyIkeMessage =
+ makeDummyIkeMessageForTest(
+ initiatorSpi,
+ responderSpi,
+ eType,
+ isResp,
+ fromIkeInit,
+ 0,
+ false /*isEncrypted*/,
+ payloadList);
+
+ byte[] dummyIkePacketBytes = new byte[0];
+ when(mMockIkeMessageHelper.decode(0, dummyIkeMessage.ikeHeader, dummyIkePacketBytes))
+ .thenReturn(new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes));
+
+ return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
+ }
+
+ private ReceivedIkePacket makeDummyEncryptedReceivedIkePacket(
+ IkeSaRecord ikeSaRecord,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ List<Integer> payloadTypeList,
+ List<String> payloadHexStringList)
+ throws Exception {
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp);
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ ikeSaRecord, eType, isResp, payloadList);
+ }
+
+ private ReceivedIkePacket makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ IkeSaRecord ikeSaRecord,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ List<IkePayload> payloadList)
+ throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ ikeSaRecord,
+ eType,
+ isResp,
+ isResp
+ ? ikeSaRecord.getLocalRequestMessageId()
+ : ikeSaRecord.getRemoteRequestMessageId(),
+ payloadList,
+ new byte[0] /*dummyIkePacketBytes*/);
+ }
+
+ private ReceivedIkePacket makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ IkeSaRecord ikeSaRecord,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ int msgId,
+ List<IkePayload> payloadList,
+ byte[] dummyIkePacketBytes)
+ throws Exception {
+ boolean fromIkeInit = !ikeSaRecord.isLocalInit;
+ IkeMessage dummyIkeMessage =
+ makeDummyIkeMessageForTest(
+ ikeSaRecord.getInitiatorSpi(),
+ ikeSaRecord.getResponderSpi(),
+ eType,
+ isResp,
+ fromIkeInit,
+ msgId,
+ true /*isEncyprted*/,
+ payloadList);
+
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ dummyIkeMessage.ikeHeader,
+ null /*collectedFrags*/,
+ new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes));
+
+ return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkePacketWithInvalidSyntax(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType) {
+ return makeDummyReceivedIkePacketWithDecodingError(
+ ikeSaRecord, isResp, eType, new InvalidSyntaxException("IkeStateMachineTest"));
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkePacketWithDecodingError(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeProtocolException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK);
+ byte[] dummyPacket = new byte[0];
+ when(mMockIkeMessageHelper.decode(
+ anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any()))
+ .thenReturn(new DecodeResultProtectedError(exception, dummyPacket));
+
+ return new ReceivedIkePacket(header, dummyPacket);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkePacketWithUnprotectedError(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK);
+ byte[] dummyPacket = new byte[0];
+ when(mMockIkeMessageHelper.decode(
+ anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any()))
+ .thenReturn(new DecodeResultUnprotectedError(exception));
+
+ return new ReceivedIkePacket(header, dummyPacket);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkeFragmentPacket(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ IkeSkfPayload skfPayload,
+ int nextPayloadType,
+ DecodeResultPartial collectedFrags) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ byte[] dummyPacket = new byte[0];
+ DecodeResultPartial resultFrags =
+ new DecodeResultPartial(
+ header, dummyPacket, skfPayload, nextPayloadType, collectedFrags);
+ setDecodeEncryptedPacketResult(ikeSaRecord, header, collectedFrags, resultFrags);
+
+ return new ReceivedIkePacket(header, dummyPacket);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketOk(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ DecodeResultPartial collectedFrags,
+ List<IkePayload> payloadList,
+ byte[] firstFragBytes) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ IkeMessage completeMessage = new IkeMessage(header, payloadList);
+
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ header,
+ collectedFrags,
+ new DecodeResultOk(completeMessage, firstFragBytes));
+
+ return new ReceivedIkePacket(header, new byte[0] /*dummyIkePacketBytes*/);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketError(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ DecodeResultPartial collectedFrags,
+ IkeException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ byte[] dummyIkePacketBytes = new byte[0];
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ header,
+ collectedFrags,
+ new DecodeResultProtectedError(exception, dummyIkePacketBytes));
+
+ return new ReceivedIkePacket(header, dummyIkePacketBytes);
+ }
+
+ private IkeHeader makeDummyIkeHeader(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, int firstPayloadType) {
+ return new IkeHeader(
+ ikeSaRecord.getInitiatorSpi(),
+ ikeSaRecord.getResponderSpi(),
+ firstPayloadType,
+ eType,
+ isResp,
+ !ikeSaRecord.isLocalInit,
+ isResp
+ ? ikeSaRecord.getLocalRequestMessageId()
+ : ikeSaRecord.getRemoteRequestMessageId());
+ }
+
+ private void setDecodeEncryptedPacketResult(
+ IkeSaRecord ikeSaRecord,
+ IkeHeader header,
+ DecodeResultPartial collectedFrags,
+ DecodeResult result) {
+ when(mMockIkeMessageHelper.decode(
+ anyInt(),
+ any(),
+ any(),
+ eq(ikeSaRecord),
+ eq(header),
+ any(),
+ eq(collectedFrags)))
+ .thenReturn(result);
+ }
+
+ private IkeMessage makeDummyIkeMessageForTest(
+ long initSpi,
+ long respSpi,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ boolean fromikeInit,
+ int messageId,
+ boolean isEncrypted,
+ List<IkePayload> payloadList)
+ throws Exception {
+ int firstPayloadType =
+ isEncrypted ? IkePayload.PAYLOAD_TYPE_SK : IkePayload.PAYLOAD_TYPE_NO_NEXT;
+
+ IkeHeader header =
+ new IkeHeader(
+ initSpi, respSpi, firstPayloadType, eType, isResp, fromikeInit, messageId);
+
+ return new IkeMessage(header, payloadList);
+ }
+
+ private static List<IkePayload> hexStrListToIkePayloadList(
+ List<Integer> payloadTypeList, List<String> payloadHexStringList, boolean isResp)
+ throws Exception {
+ List<IkePayload> payloadList = new LinkedList<>();
+ for (int i = 0; i < payloadTypeList.size(); i++) {
+ payloadList.add(
+ IkeTestUtils.hexStringToIkePayload(
+ payloadTypeList.get(i), isResp, payloadHexStringList.get(i)));
+ }
+ return payloadList;
+ }
+
+ private void verifyDecodeEncryptedMessage(IkeSaRecord record, ReceivedIkePacket rcvPacket)
+ throws Exception {
+ verify(mMockIkeMessageHelper)
+ .decode(
+ anyInt(),
+ any(),
+ any(),
+ eq(record),
+ eq(rcvPacket.ikeHeader),
+ eq(rcvPacket.ikePacketBytes),
+ eq(null));
+ }
+
+ private static IkeSaRecord makeDummyIkeSaRecord(long initSpi, long respSpi, boolean isLocalInit)
+ throws IOException {
+ Inet4Address initAddress = isLocalInit ? LOCAL_ADDRESS : REMOTE_ADDRESS;
+ Inet4Address respAddress = isLocalInit ? REMOTE_ADDRESS : LOCAL_ADDRESS;
+
+ return new IkeSaRecord(
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(initAddress, initSpi),
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(respAddress, respSpi),
+ isLocalInit,
+ TestUtils.hexStringToByteArray(NONCE_INIT_HEX_STRING),
+ TestUtils.hexStringToByteArray(NONCE_RESP_HEX_STRING),
+ new byte[KEY_LEN_IKE_SKD],
+ new byte[KEY_LEN_IKE_INTE],
+ new byte[KEY_LEN_IKE_INTE],
+ new byte[KEY_LEN_IKE_ENCR],
+ new byte[KEY_LEN_IKE_ENCR],
+ TestUtils.hexStringToByteArray(PRF_KEY_INIT_HEX_STRING),
+ TestUtils.hexStringToByteArray(PRF_KEY_RESP_HEX_STRING),
+ new LocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG);
+ IkeManager.setIkeLog(mSpyIkeLog);
+
+ mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec();
+ mIpSecManager = mMockIpSecTestUtils.getIpSecManager();
+ mContext = mMockIpSecTestUtils.getContext();
+ mUdpEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+ mEapSessionConfig =
+ new EapSessionConfig.Builder()
+ .setEapSimConfig(EAP_SIM_SUB_ID, TelephonyManager.APPTYPE_USIM)
+ .build();
+
+ mMockEapAuthenticatorFactory = mock(IkeEapAuthenticatorFactory.class);
+ mMockEapAuthenticator = mock(EapAuthenticator.class);
+ when(mMockEapAuthenticatorFactory.newEapAuthenticator(any(), any(), any(), any()))
+ .thenReturn(mMockEapAuthenticator);
+
+ mRootCertificate = CertUtils.createCertFromPemFile("self-signed-ca-a.pem");
+ mServerEndCertificate = CertUtils.createCertFromPemFile("end-cert-a.pem");
+
+ mPsk = TestUtils.hexStringToByteArray(PSK_HEX_STRING);
+
+ mChildSessionOptions = buildChildSessionOptions();
+
+ mIkeEncryptionTransform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128);
+ mIkeIntegrityTransform =
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+ mIkePrfTransform = new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ mIkeDhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP);
+
+ mSpyUserCbExecutor =
+ spy(
+ (command) -> {
+ command.run();
+ });
+
+ mMockIkeSessionCallback = mock(IkeSessionCallback.class);
+ mMockChildSessionCallback = mock(ChildSessionCallback.class);
+
+ mLooper = new TestLooper();
+
+ mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class);
+ mMockChildSessionFactoryHelper = mock(IChildSessionFactoryHelper.class);
+ ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
+ mMockChildSessionFactoryHelper);
+ setupChildStateMachineFactory(mMockChildSessionStateMachine);
+
+ // Inject longer retransmission timeout
+ mMockBackoffTimeoutCalculator = mock(IBackoffTimeoutCalculator.class);
+ when(mMockBackoffTimeoutCalculator.getExponentialBackoffTimeout(anyInt()))
+ .thenReturn(RETRANSMIT_BACKOFF_TIMEOUT_MS);
+ Retransmitter.setBackoffTimeoutCalculator(mMockBackoffTimeoutCalculator);
+
+ // Setup state machine
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsPsk(mPsk));
+
+ mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class);
+ IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
+
+ mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
+ SaRecord.setSaRecordHelper(mMockSaRecordHelper);
+
+ mSpyCurrentIkeSaRecord = spy(makeDummyIkeSaRecord(11, 12, true));
+ mSpyLocalInitIkeSaRecord = spy(makeDummyIkeSaRecord(21, 22, true));
+ mSpyRemoteInitIkeSaRecord = spy(makeDummyIkeSaRecord(31, 32, false));
+
+ mExpectedCurrentSaLocalReqMsgId = 0;
+ mExpectedCurrentSaRemoteReqMsgId = 0;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mIkeSessionStateMachine.quit();
+ mIkeSessionStateMachine.setDbg(false);
+ mUdpEncapSocket.close();
+
+ mSpyCurrentIkeSaRecord.close();
+ mSpyLocalInitIkeSaRecord.close();
+ mSpyRemoteInitIkeSaRecord.close();
+
+ IkeManager.resetIkeLog();
+ Retransmitter.resetBackoffTimeoutCalculator();
+ IkeMessage.setIkeMessageHelper(new IkeMessageHelper());
+ SaRecord.setSaRecordHelper(new SaRecordHelper());
+ ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
+ new ChildSessionFactoryHelper());
+ }
+
+ private IkeSessionStateMachine makeAndStartIkeSession(IkeSessionOptions ikeOptions)
+ throws Exception {
+ IkeSessionStateMachine ikeSession =
+ new IkeSessionStateMachine(
+ mLooper.getLooper(),
+ mContext,
+ mIpSecManager,
+ ikeOptions,
+ mChildSessionOptions,
+ mSpyUserCbExecutor,
+ mMockIkeSessionCallback,
+ mMockChildSessionCallback,
+ mMockEapAuthenticatorFactory);
+ ikeSession.setDbg(true);
+
+ mLooper.dispatchAll();
+ ikeSession.mLocalAddress = LOCAL_ADDRESS;
+
+ mSpyIkeSocket = spy(IkeSocket.getIkeSocket(mUdpEncapSocket, ikeSession));
+ doNothing().when(mSpyIkeSocket).sendIkePacket(any(), any());
+ ikeSession.mIkeSocket = mSpyIkeSocket;
+
+ return ikeSession;
+ }
+
+ public static IkeSaProposal buildSaProposal() throws Exception {
+ return new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .build();
+ }
+
+ private IkeSessionOptions.Builder buildIkeSessionOptionsCommon() throws Exception {
+ return new IkeSessionOptions.Builder()
+ .setServerAddress(REMOTE_ADDRESS)
+ .setUdpEncapsulationSocket(mUdpEncapSocket)
+ .addSaProposal(buildSaProposal())
+ .setLocalIdentification(new IkeIpv4AddrIdentification((Inet4Address) LOCAL_ADDRESS))
+ .setRemoteIdentification(
+ new IkeIpv4AddrIdentification((Inet4Address) REMOTE_ADDRESS));
+ }
+
+ private IkeSessionOptions buildIkeSessionOptionsPsk(byte[] psk) throws Exception {
+ return buildIkeSessionOptionsCommon().setAuthPsk(psk).build();
+ }
+
+ private IkeSessionOptions buildIkeSessionOptionsEap() throws Exception {
+ return buildIkeSessionOptionsCommon()
+ .setAuthEap(mRootCertificate, mEapSessionConfig)
+ .build();
+ }
+
+ private ChildSessionOptions buildChildSessionOptions() throws Exception {
+ ChildSaProposal saProposal =
+ new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .build();
+
+ return new TransportModeChildSessionOptions.Builder().addSaProposal(saProposal).build();
+ }
+
+ private ReceivedIkePacket makeIkeInitResponse() throws Exception {
+ // TODO: Build real IKE INIT response when IKE INIT response validation is implemented.
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+
+ payloadHexStringList.add(IKE_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING);
+
+ // In each test assign different IKE responder SPI in IKE INIT response to avoid remote SPI
+ // collision during response validation.
+ // STOPSHIP: b/131617794 allow #mockIkeSetup to be independent in each test after we can
+ // support IkeSession cleanup.
+ return makeDummyReceivedIkeInitRespPacket(
+ 1L /*initiator SPI*/,
+ 2L /*responder SPI*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ true /*isResp*/,
+ false /*fromIkeInit*/,
+ payloadTypeList,
+ payloadHexStringList);
+ }
+
+ private List<IkePayload> getIkeAuthPayloadListWithChildPayloads(
+ List<IkePayload> authRelatedPayloads) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
+
+ payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
+
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, true /*isResp*/);
+ payloadList.addAll(authRelatedPayloads);
+
+ return payloadList;
+ }
+
+ private ReceivedIkePacket makeIkeAuthRespWithChildPayloads(List<IkePayload> authRelatedPayloads)
+ throws Exception {
+ List<IkePayload> payloadList = getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads);
+
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ payloadList);
+ }
+
+ private ReceivedIkePacket makeIkeAuthRespWithoutChildPayloads(
+ List<IkePayload> authRelatedPayloads) throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ authRelatedPayloads);
+ }
+
+ private ReceivedIkePacket makeCreateChildCreateMessage(boolean isResp) throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ isResp,
+ makeCreateChildPayloadList(isResp));
+ }
+
+ private ReceivedIkePacket makeRekeyChildCreateMessage(boolean isResp, int spi)
+ throws Exception {
+ IkeNotifyPayload rekeyPayload =
+ new IkeNotifyPayload(
+ IkePayload.PROTOCOL_ID_ESP,
+ spi,
+ IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA,
+ new byte[0]);
+
+ List<IkePayload> payloadList = makeCreateChildPayloadList(isResp);
+ payloadList.add(rekeyPayload);
+
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ isResp,
+ payloadList);
+ }
+
+ private List<IkePayload> makeCreateChildPayloadList(boolean isResp) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
+
+ payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
+
+ return hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp);
+ }
+
+ private ReceivedIkePacket makeDeleteChildPacket(IkeDeletePayload[] payloads, boolean isResp)
+ throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ isResp,
+ Arrays.asList(payloads));
+ }
+
+ private ReceivedIkePacket makeRekeyIkeResponse() throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+
+ payloadHexStringList.add(IKE_REKEY_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
+
+ return makeDummyEncryptedReceivedIkePacket(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ true /*isResp*/,
+ payloadTypeList,
+ payloadHexStringList);
+ }
+
+ private ReceivedIkePacket makeDeleteIkeResponse(IkeSaRecord ikeSaRecord) throws Exception {
+ return makeDummyEncryptedReceivedIkePacket(
+ ikeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ true /*isResp*/,
+ new LinkedList<>(),
+ new LinkedList<>());
+ }
+
+ private ReceivedIkePacket makeDpdIkeRequest(IkeSaRecord saRecord) throws Exception {
+ return makeDummyEncryptedReceivedIkePacket(
+ saRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ new LinkedList<>(),
+ new LinkedList<>());
+ }
+
+ private ReceivedIkePacket makeDpdIkeRequest(int msgId, byte[] dummyIkePacketBytes)
+ throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ msgId,
+ new LinkedList<>(),
+ dummyIkePacketBytes);
+ }
+
+ private ReceivedIkePacket makeRekeyIkeRequest() throws Exception {
+ IkeSaPayload saPayload =
+ (IkeSaPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA,
+ false /*isResp*/,
+ IKE_REKEY_SA_PAYLOAD_HEX_STRING);
+ return makeRekeyIkeRequest(saPayload);
+ }
+
+ private ReceivedIkePacket makeRekeyIkeRequestWithUnacceptableProposal() throws Exception {
+ IkeSaPayload saPayload =
+ (IkeSaPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA,
+ false /*isResp*/,
+ IKE_REKEY_UNACCEPTABLE_SA_PAYLOAD_HEX_STRING);
+ return makeRekeyIkeRequest(saPayload);
+ }
+
+ private ReceivedIkePacket makeRekeyIkeRequest(IkeSaPayload saPayload) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+
+ payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_INIT_PAYLOAD_HEX_STRING);
+
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, false /*isResp*/);
+ payloadList.add(saPayload);
+
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ false /*isResp*/,
+ payloadList);
+ }
+
+ private ReceivedIkePacket makeDeleteIkeRequest(IkeSaRecord saRecord) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_DELETE);
+
+ payloadHexStringList.add(DELETE_IKE_PAYLOAD_HEX_STRING);
+
+ return makeDummyEncryptedReceivedIkePacket(
+ saRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ payloadTypeList,
+ payloadHexStringList);
+ }
+
+ private ReceivedIkePacket makeResponseWithErrorNotify(IkeNotifyPayload notify)
+ throws Exception {
+ List<IkePayload> payloads = new LinkedList<>();
+ payloads.add(notify);
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord, EXCHANGE_TYPE_INFORMATIONAL, true /*isResp*/, payloads);
+ }
+
+ private static boolean isIkePayloadExist(
+ List<IkePayload> payloadList, @IkePayload.PayloadType int payloadType) {
+ for (IkePayload payload : payloadList) {
+ if (payload.payloadType == payloadType) return true;
+ }
+ return false;
+ }
+
+ private static boolean isNotifyExist(
+ List<IkePayload> payloadList, @IkeNotifyPayload.NotifyType int notifyType) {
+ for (IkeNotifyPayload notify :
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, payloadList)) {
+ if (notify.notifyType == notifyType) return true;
+ }
+ return false;
+ }
+
+ private void verifyIncrementLocaReqMsgId() {
+ assertEquals(
+ ++mExpectedCurrentSaLocalReqMsgId,
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId());
+ }
+
+ private void verifyIncrementRemoteReqMsgId() {
+ assertEquals(
+ ++mExpectedCurrentSaRemoteReqMsgId,
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId());
+ }
+
+ private void verifyRetransmissionStarted() {
+ assertTrue(
+ mIkeSessionStateMachine
+ .getHandler()
+ .hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT));
+ }
+
+ private void verifyRetransmissionStopped() {
+ assertFalse(
+ mIkeSessionStateMachine
+ .getHandler()
+ .hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT));
+ }
+
+ private IkeMessage verifyEncryptAndEncodeAndGetMessage(IkeSaRecord ikeSaRecord) {
+ verify(mMockIkeMessageHelper)
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ eq(ikeSaRecord),
+ mIkeMessageCaptor.capture(),
+ anyBoolean(),
+ anyInt());
+ return mIkeMessageCaptor.getValue();
+ }
+
+ private void verifyEncryptAndEncodeNeverCalled(IkeSaRecord ikeSaRecord) {
+ verify(mMockIkeMessageHelper, never())
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ eq(ikeSaRecord),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt());
+ }
+
+ private void verifyEncryptAndEncodeNeverCalled() {
+ verify(mMockIkeMessageHelper, never())
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ any(IkeSaRecord.class),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt());
+ }
+
+ private void resetMockIkeMessageHelper() {
+ reset(mMockIkeMessageHelper);
+ when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]);
+ when(mMockIkeMessageHelper.encryptAndEncode(
+ any(), any(), any(), any(), anyBoolean(), anyInt()))
+ .thenReturn(new byte[1][0]);
+ }
+
+ @Test
+ public void testQuit() {
+ mIkeSessionStateMachine.quit();
+ mLooper.dispatchAll();
+
+ verify(mSpyIkeSocket).releaseReference(eq(mIkeSessionStateMachine));
+ verify(mSpyIkeSocket).close();
+ }
+
+ @Test
+ public void testAllocateIkeSpi() throws Exception {
+ // Test randomness.
+ IkeSecurityParameterIndex ikeSpiOne =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+ IkeSecurityParameterIndex ikeSpiTwo =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+
+ assertNotEquals(ikeSpiOne.getSpi(), ikeSpiTwo.getSpi());
+ ikeSpiTwo.close();
+
+ // Test duplicate SPIs.
+ long spiValue = ikeSpiOne.getSpi();
+ try {
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue);
+ fail("Expected to fail because duplicate SPI was assigned to the same address.");
+ } catch (IOException expected) {
+
+ }
+
+ ikeSpiOne.close();
+ IkeSecurityParameterIndex ikeSpiThree =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue);
+ ikeSpiThree.close();
+ }
+
+ private void setupFirstIkeSa() throws Exception {
+ // Inject IkeSaRecord and release IKE SPI resource since we will lose their references
+ // later.
+ when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ captureAndReleaseIkeSpiResource(invocation, 2);
+ return mSpyCurrentIkeSaRecord;
+ });
+ }
+
+ private void setupRekeyedIkeSa(IkeSaRecord rekeySaRecord) throws Exception {
+ // Inject IkeSaRecord and release IKE SPI resource since we will lose their references
+ // later.
+ when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
+ eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ captureAndReleaseIkeSpiResource(invocation, 4);
+ return rekeySaRecord;
+ });
+ }
+
+ private void throwExceptionWhenMakeRekeyIkeSa(Exception exception) throws Exception {
+ // Inject IkeSaRecord and release IKE SPI resource since we will lose their references
+ // later.
+ when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
+ eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ captureAndReleaseIkeSpiResource(invocation, 4);
+ throw exception;
+ });
+ }
+
+ private void captureAndReleaseIkeSpiResource(InvocationOnMock invocation, int ikeConfigIndex) {
+ IkeSaRecordConfig config = (IkeSaRecordConfig) invocation.getArguments()[ikeConfigIndex];
+ config.initSpi.close();
+ config.respSpi.close();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeInit() throws Exception {
+ setupFirstIkeSa();
+
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Receive IKE INIT response
+ ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+
+ // Validate outbound IKE INIT request
+ verify(mMockIkeMessageHelper, times(2)).encode(mIkeMessageCaptor.capture());
+ IkeMessage ikeInitReqMessage = mIkeMessageCaptor.getValue();
+
+ IkeHeader ikeHeader = ikeInitReqMessage.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ List<IkePayload> payloadList = ikeInitReqMessage.ikePayloadList;
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_KE));
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_NONCE));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED));
+
+ verify(mSpyIkeSocket)
+ .registerIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+
+ verify(mMockIkeMessageHelper)
+ .decode(0, dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Validate negotiated SA proposal.
+ IkeSaProposal negotiatedProposal = mIkeSessionStateMachine.mSaProposal;
+ assertNotNull(negotiatedProposal);
+
+ assertEquals(
+ new EncryptionTransform[] {mIkeEncryptionTransform},
+ negotiatedProposal.getEncryptionTransforms());
+ assertEquals(
+ new IntegrityTransform[] {mIkeIntegrityTransform},
+ negotiatedProposal.getIntegrityTransforms());
+ assertEquals(new PrfTransform[] {mIkePrfTransform}, negotiatedProposal.getPrfTransforms());
+
+ // Validate current IkeSaRecord.
+ verify(mMockSaRecordHelper)
+ .makeFirstIkeSaRecord(
+ any(IkeMessage.class),
+ any(IkeMessage.class),
+ mIkeSaRecordConfigCaptor.capture());
+
+ IkeSaRecordConfig ikeSaRecordConfig = mIkeSaRecordConfigCaptor.getValue();
+ assertEquals(KEY_LEN_IKE_PRF, ikeSaRecordConfig.prf.getKeyLength());
+ assertEquals(KEY_LEN_IKE_INTE, ikeSaRecordConfig.integrityKeyLength);
+ assertEquals(KEY_LEN_IKE_ENCR, ikeSaRecordConfig.encryptionKeyLength);
+ assertEquals(CMD_LOCAL_REQUEST_REKEY_IKE, ikeSaRecordConfig.futureRekeyEvent.procedureType);
+
+ // Validate NAT detection
+ assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat);
+ assertFalse(mIkeSessionStateMachine.mIsRemoteBehindNat);
+
+ // Validate fragmentation support negotiation
+ assertTrue(mIkeSessionStateMachine.mSupportFragment);
+ }
+
+ private void setIkeInitResults() throws Exception {
+ mIkeSessionStateMachine.mIkeCipher = mock(IkeCipher.class);
+ mIkeSessionStateMachine.mIkeIntegrity = mock(IkeMacIntegrity.class);
+ mIkeSessionStateMachine.mIkePrf = mock(IkeMacPrf.class);
+ mIkeSessionStateMachine.mSaProposal = buildSaProposal();
+ mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyCurrentIkeSaRecord;
+ mIkeSessionStateMachine.mLocalAddress = LOCAL_ADDRESS;
+ mIkeSessionStateMachine.mIsLocalBehindNat = true;
+ mIkeSessionStateMachine.mIsRemoteBehindNat = false;
+ mIkeSessionStateMachine.mSupportFragment = true;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyCurrentIkeSaRecord);
+ }
+
+ /** Initializes the mIkeSessionStateMachine in the IDLE state. */
+ private void setupIdleStateMachine() throws Exception {
+ setIkeInitResults();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ mDummyChildSmCallback =
+ createChildAndGetChildSessionSmCallback(
+ mMockChildSessionStateMachine, CHILD_SPI_REMOTE, mMockChildSessionCallback);
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ private void mockIkeInitAndTransitionToIkeAuth(State authState) throws Exception {
+ setIkeInitResults();
+
+ // Need to create a real IkeMacPrf instance for authentication because we cannot inject a
+ // method stub for IkeMacPrf#signBytes. IkeMacPrf#signBytes is inheritted from a package
+ // protected class IkePrf. We don't have the visibility to mock it.
+ mIkeSessionStateMachine.mIkePrf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+
+ mIkeSessionStateMachine.mIkeInitRequestBytes = new byte[0];
+ mIkeSessionStateMachine.mIkeInitResponseBytes = new byte[0];
+ mIkeSessionStateMachine.mIkeInitNoncePayload = new IkeNoncePayload();
+ mIkeSessionStateMachine.mIkeRespNoncePayload = new IkeNoncePayload();
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_FORCE_TRANSITION, authState);
+ mLooper.dispatchAll();
+ }
+
+ private void setupChildStateMachineFactory(ChildSessionStateMachine child) {
+ // After state machine start, add to the callback->statemachine map
+ when(mMockChildSessionFactoryHelper.makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ any(ChildSessionCallback.class),
+ any(IChildSessionSmCallback.class)))
+ .thenReturn(child);
+ }
+
+ /**
+ * Utility to register a new callback -> state machine mapping.
+ *
+ * <p>Must be used if IkeSessionStateMachine.openChildSession() is not called, but commands
+ * injected instead.
+ *
+ * @param callback The callback to be used for the mapping
+ * @param sm The ChildSessionStateMachine instance to be used.
+ */
+ private void registerChildStateMachine(
+ ChildSessionCallback callback, ChildSessionStateMachine sm) {
+ setupChildStateMachineFactory(sm);
+ mIkeSessionStateMachine.registerChildSessionCallback(
+ mChildSessionOptions, callback, false /*isFirstChild*/);
+ }
+
+ @Test
+ public void testCreateAdditionalChild() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionCallback childCallback = mock(ChildSessionCallback.class);
+ ChildSessionStateMachine childStateMachine = mock(ChildSessionStateMachine.class);
+ registerChildStateMachine(childCallback, childStateMachine);
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD,
+ childCallback,
+ mChildSessionOptions));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(childStateMachine)
+ .createChildSession(
+ eq(LOCAL_ADDRESS),
+ eq(REMOTE_ADDRESS),
+ any(), // udpEncapSocket
+ eq(mIkeSessionStateMachine.mIkePrf),
+ any()); // sk_d
+
+ // Once for initial child, a second time for the additional child.
+ verify(mMockChildSessionFactoryHelper)
+ .makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ eq(childCallback),
+ mChildSessionSmCbCaptor.capture());
+ IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue();
+
+ // Mocking sending request
+ cb.onOutboundPayloadsReady(
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ false /*isResp*/,
+ new LinkedList<>(),
+ childStateMachine);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ IkeMessage createChildRequest = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = createChildRequest.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+ assertEquals(mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), ikeHeader.messageId);
+ assertTrue(createChildRequest.ikePayloadList.isEmpty());
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ // Mocking receiving response
+ ReceivedIkePacket dummyCreateChildResp = makeCreateChildCreateMessage(true /*isResp*/);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyCreateChildResp);
+ mLooper.dispatchAll();
+
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyCreateChildResp);
+
+ verify(childStateMachine)
+ .receiveResponse(
+ eq(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA), mPayloadListCaptor.capture());
+
+ List<IkePayload> childRespList = mPayloadListCaptor.getValue();
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_NONCE));
+
+ // Mock finishing procedure
+ cb.onProcedureFinished(childStateMachine);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testTriggerDeleteChildLocal() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).deleteChildSession();
+ }
+
+ @Test
+ public void testHandleDeleteChildBeforeCreation() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
+ mock(ChildSessionCallback.class),
+ null /*childOptions*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testTriggerRekeyChildLocal() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).rekeyChildSession();
+ }
+
+ @Test
+ public void testScheduleAndTriggerRekeyChildLocal() throws Exception {
+ setupIdleStateMachine();
+ long dummyRekeyTimeout = 10000L;
+
+ ChildLocalRequest rekeyRequest =
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/);
+ mDummyChildSmCallback.scheduleLocalRequest(rekeyRequest, dummyRekeyTimeout);
+
+ mLooper.moveTimeForward(dummyRekeyTimeout);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).rekeyChildSession();
+ }
+
+ private IChildSessionSmCallback createChildAndGetChildSessionSmCallback(
+ ChildSessionStateMachine child, int remoteSpi) throws Exception {
+ return createChildAndGetChildSessionSmCallback(
+ child, remoteSpi, mock(ChildSessionCallback.class));
+ }
+
+ private IChildSessionSmCallback createChildAndGetChildSessionSmCallback(
+ ChildSessionStateMachine child, int remoteSpi, ChildSessionCallback childCallback)
+ throws Exception {
+ registerChildStateMachine(childCallback, child);
+
+ IChildSessionSmCallback cb = mIkeSessionStateMachine.new ChildSessionSmCallback();
+ cb.onChildSaCreated(remoteSpi, child);
+ mLooper.dispatchAll();
+
+ return cb;
+ }
+
+ private void transitionToChildProcedureOngoing() {
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mChildProcedureOngoing);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ }
+
+ private void verifyChildReceiveDeleteRequest(
+ ChildSessionStateMachine child, IkeDeletePayload[] expectedDelPayloads) {
+ verify(child)
+ .receiveRequest(
+ eq(IKE_EXCHANGE_SUBTYPE_DELETE_CHILD),
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ mPayloadListCaptor.capture());
+ List<IkePayload> reqPayloads = mPayloadListCaptor.getValue();
+
+ int numExpectedDelPayloads = expectedDelPayloads.length;
+ assertEquals(numExpectedDelPayloads, reqPayloads.size());
+
+ for (int i = 0; i < numExpectedDelPayloads; i++) {
+ assertEquals(expectedDelPayloads[i], (IkeDeletePayload) reqPayloads.get(i));
+ }
+ }
+
+ private void outboundDeleteChildPayloadsReady(
+ IChildSessionSmCallback childSmCb,
+ IkeDeletePayload delPayload,
+ boolean isResp,
+ ChildSessionStateMachine child) {
+ List<IkePayload> outPayloadList = new LinkedList<>();
+ outPayloadList.add(delPayload);
+ childSmCb.onOutboundPayloadsReady(
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, isResp, outPayloadList, child);
+ mLooper.dispatchAll();
+ }
+
+ private List<IkePayload> verifyOutInfoMsgHeaderAndGetPayloads(boolean isResp) {
+ IkeMessage deleteChildMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = deleteChildMessage.ikeHeader;
+ assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi);
+ assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), ikeHeader.ikeResponderSpi);
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertEquals(isResp, ikeHeader.isResponseMsg);
+
+ return deleteChildMessage.ikePayloadList;
+ }
+
+ @Test
+ public void testDeferChildRequestToChildProcedureOngoing() throws Exception {
+ setupIdleStateMachine();
+
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})};
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+ }
+
+ @Test
+ public void testRemoteDeleteOneChild() throws Exception {
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})};
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+
+ // Outbound payload list ready
+ IkeDeletePayload outDelPayload = new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL});
+ outboundDeleteChildPayloadsReady(
+ mDummyChildSmCallback,
+ outDelPayload,
+ true /*isResp*/,
+ mMockChildSessionStateMachine);
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloadList.size());
+ assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0)));
+ }
+
+ @Test
+ public void testRemoteDeleteMultipleChildSession() throws Exception {
+ ChildSessionStateMachine childOne = mock(ChildSessionStateMachine.class);
+ int childOneRemoteSpi = 11;
+ int childOneLocalSpi = 12;
+
+ ChildSessionStateMachine childTwo = mock(ChildSessionStateMachine.class);
+ int childTwoRemoteSpi = 21;
+ int childTwoLocalSpi = 22;
+
+ setupIdleStateMachine();
+ IChildSessionSmCallback childSmCbOne =
+ createChildAndGetChildSessionSmCallback(childOne, childOneRemoteSpi);
+ IChildSessionSmCallback childSmCbTwo =
+ createChildAndGetChildSessionSmCallback(childTwo, childTwoRemoteSpi);
+
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {
+ new IkeDeletePayload(new int[] {childOneRemoteSpi, childTwoRemoteSpi})
+ };
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(childOne, inboundDelPayloads);
+ verifyChildReceiveDeleteRequest(childTwo, inboundDelPayloads);
+
+ // childOne outbound payload list ready
+ IkeDeletePayload outDelPayloadOne = new IkeDeletePayload(new int[] {childOneLocalSpi});
+ outboundDeleteChildPayloadsReady(childSmCbOne, outDelPayloadOne, true /*isResp*/, childOne);
+ mLooper.dispatchAll();
+
+ // Verify that no response is sent
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // childTwo outbound payload list ready
+ IkeDeletePayload outDelPayloadTwo = new IkeDeletePayload(new int[] {childTwoLocalSpi});
+ outboundDeleteChildPayloadsReady(childSmCbTwo, outDelPayloadTwo, true /*isResp*/, childTwo);
+ mLooper.dispatchAll();
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(2, payloadList.size());
+ assertEquals(outDelPayloadOne, ((IkeDeletePayload) payloadList.get(0)));
+ assertEquals(outDelPayloadTwo, ((IkeDeletePayload) payloadList.get(1)));
+ }
+
+ @Test
+ public void testRemoteDeleteMultipleChildSaInSameSession() throws Exception {
+ int newChildRemoteSpi = 21;
+ int newChildLocalSpi = 22;
+
+ setupIdleStateMachine();
+ mDummyChildSmCallback.onChildSaCreated(newChildRemoteSpi, mMockChildSessionStateMachine);
+
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {
+ new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE}),
+ new IkeDeletePayload(new int[] {newChildRemoteSpi})
+ };
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+
+ // child outbound payload list ready
+ IkeDeletePayload outDelPayload =
+ new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL, newChildLocalSpi});
+ outboundDeleteChildPayloadsReady(
+ mDummyChildSmCallback,
+ outDelPayload,
+ true /*isResp*/,
+ mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloadList.size());
+ assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0)));
+ }
+
+ @Test
+ public void testIgnoreUnrecognizedChildSpi() throws Exception {
+ int unrecognizedSpi = 2;
+
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {
+ new IkeDeletePayload(new int[] {unrecognizedSpi, CHILD_SPI_REMOTE})
+ };
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+
+ // child outbound payload list ready
+ IkeDeletePayload outPayload = new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL});
+ outboundDeleteChildPayloadsReady(
+ mDummyChildSmCallback, outPayload, true /*isResp*/, mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloadList.size());
+ assertEquals(outPayload, ((IkeDeletePayload) payloadList.get(0)));
+ }
+
+ @Test
+ public void testRemoteDeleteChildHandlesReqWithNoRecognizedSpi() throws Exception {
+ int unrecognizedSpi = 2;
+
+ setupIdleStateMachine();
+
+ // Receive Delete Child Request without any recognized SPI
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {new IkeDeletePayload(new int[] {unrecognizedSpi})};
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify outbound empty response was sent
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertTrue(payloadList.isEmpty());
+
+ // Verify IKE Session was back to Idle
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRemoteCreateChild() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET, makeCreateChildCreateMessage(false /*isResp*/));
+
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ assertEquals(
+ ERROR_TYPE_NO_ADDITIONAL_SAS,
+ ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType);
+ }
+
+ @Test
+ public void testTriggerRemoteRekeyChild() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET,
+ makeRekeyChildCreateMessage(false /*isResp*/, CHILD_SPI_REMOTE));
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionStateMachine)
+ .receiveRequest(
+ eq(IKE_EXCHANGE_SUBTYPE_REKEY_CHILD),
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ any(List.class));
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ }
+
+ @Test
+ public void testHandleRekeyChildReqWithUnrecognizedSpi() throws Exception {
+ int unrecognizedSpi = 2;
+
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET,
+ makeRekeyChildCreateMessage(false /*isResp*/, unrecognizedSpi));
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionStateMachine, never()).receiveRequest(anyInt(), anyInt(), any());
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ IkeNotifyPayload notifyPayload = (IkeNotifyPayload) ikePayloadList.get(0);
+ assertEquals(ERROR_TYPE_CHILD_SA_NOT_FOUND, notifyPayload.notifyType);
+ assertEquals(unrecognizedSpi, notifyPayload.spi);
+ }
+
+ private void verifyNotifyUserCloseSession() {
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testRcvRemoteDeleteIkeWhenChildProcedureOngoing() throws Exception {
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET, makeDeleteIkeRequest(mSpyCurrentIkeSaRecord));
+
+ mLooper.dispatchAll();
+
+ verifyNotifyUserCloseSession();
+
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertTrue(ikePayloadList.isEmpty());
+ }
+
+ @Test
+ public void testRcvRemoteRekeyIkeWhenChildProcedureOngoing() throws Exception {
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, makeRekeyIkeRequest());
+
+ mLooper.dispatchAll();
+
+ // Since we have forced state machine to transition to ChildProcedureOngoing state without
+ // really starting any Child procedure, it should transition to Idle at this time.
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ assertEquals(
+ ERROR_TYPE_TEMPORARY_FAILURE,
+ ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType);
+ }
+
+ @Test
+ public void testKillChildSessions() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionStateMachine childOne = mock(ChildSessionStateMachine.class);
+ ChildSessionStateMachine childTwo = mock(ChildSessionStateMachine.class);
+ registerChildStateMachine(mock(ChildSessionCallback.class), childOne);
+ registerChildStateMachine(mock(ChildSessionCallback.class), childTwo);
+
+ mIkeSessionStateMachine.mCurrentIkeSaRecord = null;
+
+ mIkeSessionStateMachine.quitNow();
+
+ mLooper.dispatchAll();
+
+ verify(childOne).killSession();
+ verify(childTwo).killSession();
+ }
+
+ private IkeMessage verifyAuthReqAndGetMsg() {
+ IkeMessage ikeAuthReqMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = ikeAuthReqMessage.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ return ikeAuthReqMessage;
+ }
+
+ private IkeMessage verifyAuthReqWithChildPayloadsAndGetMsg() {
+ IkeMessage ikeAuthReqMessage = verifyAuthReqAndGetMsg();
+
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_ID_INITIATOR, IkeIdPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER, IkeIdPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class));
+
+ return ikeAuthReqMessage;
+ }
+
+ private void verifySharedKeyAuthentication(
+ IkeAuthPskPayload spyAuthPayload,
+ IkeIdPayload respIdPayload,
+ List<IkePayload> authRelatedPayloads,
+ boolean hasChildPayloads)
+ throws Exception {
+ // Send IKE AUTH response to IKE state machine
+ ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp);
+ mLooper.dispatchAll();
+
+ // Validate outbound IKE AUTH request
+ IkeMessage ikeAuthReqMessage;
+ if (hasChildPayloads) {
+ ikeAuthReqMessage = verifyAuthReqWithChildPayloadsAndGetMsg();
+ } else {
+ ikeAuthReqMessage = verifyAuthReqAndGetMsg();
+ }
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPskPayload.class));
+
+ // Validate inbound IKE AUTH response
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, authResp);
+
+ // Validate authentication is done. Cannot use matchers because IkeAuthPskPayload is final.
+ verify(spyAuthPayload)
+ .verifyInboundSignature(
+ mPsk,
+ mIkeSessionStateMachine.mIkeInitRequestBytes,
+ mSpyCurrentIkeSaRecord.nonceInitiator,
+ respIdPayload.getEncodedPayloadBody(),
+ mIkeSessionStateMachine.mIkePrf,
+ mSpyCurrentIkeSaRecord.getSkPr());
+
+ // Validate that user has been notified
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onOpened(any());
+ // TODO: Verify sessionConfiguration
+
+ // Verify payload list pair for first Child negotiation
+ ArgumentCaptor<List<IkePayload>> mReqPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+ ArgumentCaptor<List<IkePayload>> mRespPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mMockChildSessionStateMachine)
+ .handleFirstChildExchange(
+ mReqPayloadListCaptor.capture(),
+ mRespPayloadListCaptor.capture(),
+ eq(LOCAL_ADDRESS),
+ eq(REMOTE_ADDRESS),
+ any(), // udpEncapSocket
+ eq(mIkeSessionStateMachine.mIkePrf),
+ any()); // sk_d
+ List<IkePayload> childReqList = mReqPayloadListCaptor.getValue();
+ List<IkePayload> childRespList = mRespPayloadListCaptor.getValue();
+
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR));
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER));
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_NONCE));
+ IkeSaPayload reqSaPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, childReqList);
+ assertFalse(reqSaPayload.isSaResponse);
+
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_NONCE));
+ IkeSaPayload respSaPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, childRespList);
+ assertTrue(respSaPayload.isSaResponse);
+
+ // Mock finishing first Child SA negotiation.
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ verify(mMockChildSessionFactoryHelper)
+ .makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ eq(mMockChildSessionCallback),
+ mChildSessionSmCbCaptor.capture());
+ IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue();
+
+ cb.onProcedureFinished(mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ private IkeAuthPskPayload makeSpyRespPskPayload() throws Exception {
+ IkeAuthPskPayload spyAuthPayload =
+ spy(
+ (IkeAuthPskPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ true /*isResp*/,
+ PSK_AUTH_RESP_PAYLOAD_HEX_STRING));
+
+ doNothing()
+ .when(spyAuthPayload)
+ .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ return spyAuthPayload;
+ }
+
+ private IkeAuthDigitalSignPayload makeSpyDigitalSignAuthPayload() throws Exception {
+ IkeAuthDigitalSignPayload spyAuthPayload =
+ spy(
+ (IkeAuthDigitalSignPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ true /*isResp*/,
+ GENERIC_DIGITAL_SIGN_AUTH_RESP_HEX_STRING));
+ doNothing()
+ .when(spyAuthPayload)
+ .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ return spyAuthPayload;
+ }
+
+ private IkeIdPayload makeRespIdPayload() throws Exception {
+ return (IkeIdPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
+ true /*isResp*/,
+ ID_PAYLOAD_RESPONDER_HEX_STRING);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPsk() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, true);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPskVerifyFail() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Build IKE AUTH response with invalid Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ doThrow(new AuthenticationFailedException("DummyAuthFailException"))
+ .when(spyAuthPayload)
+ .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ // Send response to IKE state machine
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session was closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(any(AuthenticationFailedException.class));
+ }
+
+ @Test
+ public void testAuthPskHandleRespWithParsingError() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord, true /*isResp*/, IkeHeader.EXCHANGE_TYPE_IKE_AUTH);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPreEap() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Mock IKE INIT
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with EAP. Auth, ID-Resp and Cert payloads.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+
+ authRelatedPayloads.add(new IkeEapPayload(EAP_DUMMY_MSG));
+ authRelatedPayloads.add(makeSpyDigitalSignAuthPayload());
+ authRelatedPayloads.add(makeRespIdPayload());
+
+ IkeCertX509CertPayload certPayload = new IkeCertX509CertPayload(mServerEndCertificate);
+ authRelatedPayloads.add(certPayload);
+
+ // Send IKE AUTH response to IKE state machine
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeIkeAuthRespWithoutChildPayloads(authRelatedPayloads));
+ mLooper.dispatchAll();
+
+ // Validate outbound IKE AUTH request
+ IkeMessage ikeAuthReqMessage = verifyAuthReqWithChildPayloadsAndGetMsg();
+ assertNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPayload.class));
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthInEap);
+ verifyRetransmissionStopped();
+ assertNotNull(mIkeSessionStateMachine.mInitIdPayload);
+ assertNotNull(mIkeSessionStateMachine.mRespIdPayload);
+ }
+
+ private IEapCallback verifyEapAuthenticatorCreatedAndGetCallback() {
+ ArgumentCaptor<IEapCallback> captor = ArgumentCaptor.forClass(IEapCallback.class);
+
+ verify(mMockEapAuthenticatorFactory)
+ .newEapAuthenticator(
+ eq(mIkeSessionStateMachine.getHandler().getLooper()),
+ captor.capture(),
+ eq(mContext),
+ eq(mEapSessionConfig));
+
+ return captor.getValue();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapStartsAuthenticatorAndProxiesMessage()
+ throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EAP_START_EAP_AUTH, new IkeEapPayload(EAP_DUMMY_MSG));
+ mLooper.dispatchAll();
+
+ verifyEapAuthenticatorCreatedAndGetCallback();
+
+ verify(mMockEapAuthenticator).processEapMessage(eq(EAP_DUMMY_MSG));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesOutboundResponse() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+ callback.onResponse(EAP_DUMMY_MSG);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Verify EAP response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+
+ assertEquals(1, resp.ikePayloadList.size());
+ assertArrayEquals(EAP_DUMMY_MSG, ((IkeEapPayload) resp.ikePayloadList.get(0)).eapMessage);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesMissingEapPacket() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ // Mock sending IKE_AUTH{EAP} request
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+ callback.onResponse(EAP_DUMMY_MSG);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Send IKE AUTH response with no EAP Payload to IKE state machine
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeIkeAuthRespWithoutChildPayloads(new LinkedList<>()));
+ mLooper.dispatchAll();
+
+ // Verify state machine quit properly
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(any(AuthenticationFailedException.class));
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesSuccess() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+
+ // Setup dummy initIdPayload for next state.
+ mIkeSessionStateMachine.mInitIdPayload = mock(IkeIdPayload.class);
+ when(mIkeSessionStateMachine.mInitIdPayload.getEncodedPayloadBody())
+ .thenReturn(new byte[0]);
+
+ callback.onSuccess(mPsk, new byte[0]); // use mPsk as MSK, eMSK does not matter
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthPostEap);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesError() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+
+ Throwable error = new IllegalArgumentException();
+ callback.onError(error);
+ mLooper.dispatchAll();
+
+ // Fires user error callbacks
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(argThat(err -> err.getCause() == error));
+
+ // Verify state machine quit properly
+ verify(mSpyCurrentIkeSaRecord).close();
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesFailure() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+ callback.onFail();
+ mLooper.dispatchAll();
+
+ // Fires user error callbacks
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(any(AuthenticationFailedException.class));
+
+ // Verify state machine quit properly
+ verify(mSpyCurrentIkeSaRecord).close();
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPostEap() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ reset(mMockChildSessionFactoryHelper);
+ setupChildStateMachineFactory(mMockChildSessionStateMachine);
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup dummy state from IkeAuthPreEap for next state.
+ mIkeSessionStateMachine.mInitIdPayload = mock(IkeIdPayload.class);
+ when(mIkeSessionStateMachine.mInitIdPayload.getEncodedPayloadBody())
+ .thenReturn(new byte[0]);
+ mIkeSessionStateMachine.mRespIdPayload =
+ (IkeIdPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
+ true /*isResp*/,
+ ID_PAYLOAD_RESPONDER_HEX_STRING);
+
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
+
+ payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
+
+ mIkeSessionStateMachine.mFirstChildReqList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, false /*isResp*/);
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthPostEap);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_EAP_FINISH_EAP_AUTH, mPsk);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+
+ verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, false);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesFirstFrag() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Received IKE fragment
+ byte[] unencryptedData = "testCreateIkeLocalIkeAuthHandlesFrag".getBytes();
+ int fragNum = 1;
+ int totalFragments = 2;
+ IkeSkfPayload skfPayload =
+ IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments);
+
+ ReceivedIkePacket packet =
+ makeDummyReceivedIkeFragmentPacket(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ skfPayload,
+ PAYLOAD_TYPE_AUTH,
+ null /* collectedFrags*/);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify state doesn't change
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+
+ // Verify the IkeSaRecord has stored the fragment.
+ DecodeResultPartial resultPartial =
+ mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/);
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType);
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(false /*isResp*/));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesLastFragOk() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Set previously collected IKE fragments
+ DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class);
+ mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/);
+
+ // Build reassembled IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ List<IkePayload> authPayloadList =
+ getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads);
+
+ // Receive last auth response and do IKE_AUTH
+ ReceivedIkePacket packet =
+ makeDummyReceivedLastIkeFragmentPacketOk(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ mockCollectedFrags,
+ authPayloadList,
+ "FirstFrag".getBytes());
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify IKE AUTH is done
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ // Verify collected response fragments are cleared.
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/));
+ verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesLastFragError() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Set previously collected IKE fragments
+ DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class);
+ mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/);
+
+ // Receive last auth response with syntax error
+ ReceivedIkePacket packet =
+ makeDummyReceivedLastIkeFragmentPacketError(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ mockCollectedFrags,
+ new InvalidSyntaxException("IkeStateMachineTest"));
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify Delete request is sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+
+ // Collected response fragments are cleared
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/));
+ verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/);
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateSendsRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ verifyRetransmissionStarted();
+
+ // Verify outbound message
+ IkeMessage rekeyMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = rekeyMsg.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType);
+ assertEquals(mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), ikeHeader.messageId);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ // Verify SA payload & proposals
+ IkeSaPayload saPayload =
+ rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class);
+ assertFalse(saPayload.isSaResponse);
+ assertEquals(1, saPayload.proposalList.size());
+
+ IkeSaPayload.IkeProposal proposal =
+ (IkeSaPayload.IkeProposal) saPayload.proposalList.get(0);
+ assertEquals(1, proposal.number); // Must be 1-indexed
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IKE, proposal.spiSize);
+ assertEquals(mIkeSessionStateMachine.mSaProposal, proposal.saProposal);
+
+ // Verify Nonce and KE payloads exist.
+ assertNotNull(
+ rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class));
+
+ IkeKePayload kePayload =
+ rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class);
+ assertNotNull(kePayload);
+ assertTrue(kePayload.isOutbound);
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandlesResponse() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Prepare "rekeyed" SA
+ setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+
+ // Receive Rekey response
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRespReceivedPacket);
+
+ // Verify in delete state, and new SA record was saved:
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalDelete);
+ verifyRetransmissionStarted();
+ assertEquals(mSpyLocalInitIkeSaRecord, mIkeSessionStateMachine.mLocalInitNewIkeSaRecord);
+ verify(mSpyIkeSocket)
+ .registerIke(
+ eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleRespWithParsingError() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleRespWithNonFatalErrorNotify() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Mock receiving packet with NO_PROPOSAL_CHOSEN
+ ReceivedIkePacket resp =
+ makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_NO_PROPOSAL_CHOSEN));
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify IKE Session goes back to Idle
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ // Move time forward to trigger retry
+ mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleRespWithFatalErrorNotify() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with NO_PROPOSAL_CHOSEN
+ ReceivedIkePacket resp =
+ makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_INVALID_SYNTAX));
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify no message was sent because a fatal error notification was received
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateSaCreationFail() throws Exception {
+ // Throw error when building new IKE SA
+ throwExceptionWhenMakeRekeyIkeSa(
+ new GeneralSecurityException("testRekeyIkeLocalCreateSaCreationFail"));
+
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ resetMockIkeMessageHelper();
+
+ // Receive Rekey response
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleReqWithNonFatalError() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Build protocol exception
+ List<Integer> unsupportedPayloads = new LinkedList<>();
+ unsupportedPayloads.add(PAYLOAD_TYPE_UNSUPPORTED);
+ UnsupportedCriticalPayloadException exception =
+ new UnsupportedCriticalPayloadException(unsupportedPayloads);
+
+ // Mock receiving packet with unsupported critical payload
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithDecodingError(
+ mSpyCurrentIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ exception);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloads.size());
+
+ IkePayload payload = payloads.get(0);
+ assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType);
+ assertEquals(
+ ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD, ((IkeNotifyPayload) payload).notifyType);
+
+ // Verify IKE Session stays in the same state
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ private void mockCreateAndTransitionToRekeyDeleteLocal() {
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyLocalInitIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeLocalDelete);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteSendsRequest() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Verify Rekey-Delete request
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalDelete);
+ verifyRetransmissionStarted();
+
+ // Verify outbound message
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = delMsg.ikeHeader;
+ assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi);
+ assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), ikeHeader.ikeResponderSpi);
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertFalse(ikeHeader.isResponseMsg);
+
+ List<IkeDeletePayload> deletePayloadList =
+ delMsg.getPayloadListForType(
+ IkePayload.PAYLOAD_TYPE_DELETE, IkeDeletePayload.class);
+ assertEquals(1, deletePayloadList.size());
+
+ IkeDeletePayload deletePayload = deletePayloadList.get(0);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(0, deletePayload.numSpi);
+ assertEquals(0, deletePayload.spiSize);
+ assertArrayEquals(new int[0], deletePayload.spisToDelete);
+ }
+
+ private void verifyRekeyReplaceSa(IkeSaRecord newSaRecord) {
+ verify(mSpyCurrentIkeSaRecord).close();
+ verify(mSpyIkeSocket).unregisterIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()));
+ verify(mSpyIkeSocket, never()).unregisterIke(eq(newSaRecord.getLocalSpi()));
+
+ assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, newSaRecord);
+
+ verify(mMockChildSessionStateMachine).setSkD(newSaRecord.getSkD());
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteHandlesResponse() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Receive Delete response
+ ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
+ makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRespReceivedPacket);
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verify(mMockIkeSessionCallback, never()).onClosed();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteHandlesRespWithParsingError() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify no more request out
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteWithRequestOnNewSa() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Receive an empty (DPD) request on the new IKE SA
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDpdIkeRequest(mSpyLocalInitIkeSaRecord));
+ mLooper.dispatchAll();
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteWithRequestFragOnNewSa() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Received IKE fragment
+ byte[] unencryptedData = "testRekeyIkeLocalDeleteWithRequestFragOnNewSa".getBytes();
+ int fragNum = 1;
+ int totalFragments = 2;
+ IkeSkfPayload skfPayload =
+ IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments);
+
+ ReceivedIkePacket packet =
+ makeDummyReceivedIkeFragmentPacket(
+ mSpyLocalInitIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ skfPayload,
+ PAYLOAD_TYPE_SA,
+ null /* collectedFrags*/);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify rekey is done.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verifyRetransmissionStopped();
+
+ // Verify the IkeSaRecord has stored the new fragment.
+ DecodeResultPartial resultPartial =
+ mSpyLocalInitIkeSaRecord.getCollectedFragments(false /*isResp*/);
+ assertEquals(PAYLOAD_TYPE_SA, resultPartial.firstPayloadType);
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDeleteWithRequestOnNewSa() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeRemoteDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyRemoteInitIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Receive an empty (DPD) request on the new IKE SA
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDpdIkeRequest(mSpyRemoteInitIkeSaRecord));
+ mLooper.dispatchAll();
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteCreate() throws Exception {
+ setupIdleStateMachine();
+
+ setupRekeyedIkeSa(mSpyRemoteInitIkeSaRecord);
+
+ // Receive Rekey request
+ ReceivedIkePacket dummyRekeyIkeRequestReceivedPacket = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRequestReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRequestReceivedPacket);
+
+ // Verify SA created with correct parameters
+ ArgumentCaptor<SaRecord.IkeSaRecordConfig> recordConfigCaptor =
+ ArgumentCaptor.forClass(SaRecord.IkeSaRecordConfig.class);
+ verify(mMockSaRecordHelper)
+ .makeRekeyedIkeSaRecord(any(), any(), any(), any(), recordConfigCaptor.capture());
+ assertEquals(IKE_REKEY_SA_INITIATOR_SPI, recordConfigCaptor.getValue().initSpi.getSpi());
+
+ // Verify outbound CREATE_CHILD_SA message
+ IkeMessage rekeyCreateResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader rekeyCreateRespHeader = rekeyCreateResp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyCreateRespHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyCreateRespHeader.exchangeType);
+ assertTrue(rekeyCreateRespHeader.isResponseMsg);
+ assertTrue(rekeyCreateRespHeader.fromIkeInitiator);
+ assertNotNull(
+ rekeyCreateResp.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class));
+ assertNotNull(
+ rekeyCreateResp.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class));
+ assertNotNull(
+ rekeyCreateResp.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class));
+
+ // Verify SA, StateMachine state
+ assertEquals(mSpyCurrentIkeSaRecord, mIkeSessionStateMachine.mIkeSaRecordAwaitingRemoteDel);
+ assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mIkeSaRecordSurviving);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
+ verify(mSpyIkeSocket)
+ .registerIke(
+ eq(mSpyRemoteInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+ }
+
+ @Test
+ public void testRekeyIkeRemoteCreateHandlesInvalidReq() throws Exception {
+ setupIdleStateMachine();
+
+ // Receive Rekey request
+ ReceivedIkePacket request = makeRekeyIkeRequestWithUnacceptableProposal();
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+ mLooper.dispatchAll();
+
+ verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteCreateSaCreationFailure() throws Exception {
+ // Throw error when building new IKE SA
+ throwExceptionWhenMakeRekeyIkeSa(
+ new GeneralSecurityException("testRekeyIkeRemoteCreateSaCreationFailure"));
+ setupIdleStateMachine();
+
+ // Receive Rekey request
+ ReceivedIkePacket request = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+ mLooper.dispatchAll();
+
+ verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+ }
+
+ private void verifyProcessRekeyReqFailure(int expectedErrorCode) {
+ // Verify IKE Session is back to Idle
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ // Verify error notification was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloads.size());
+ IkeNotifyPayload notify = (IkeNotifyPayload) payloads.get(0);
+ assertEquals(expectedErrorCode, notify.notifyType);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDelete() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Rekey Delete request
+ ReceivedIkePacket dummyDeleteIkeRequestReceivedPacket =
+ makeDeleteIkeRequest(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRequestReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket);
+
+ // Verify outbound DELETE_IKE_SA message
+ IkeMessage rekeyDeleteResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader rekeyDeleteRespHeader = rekeyDeleteResp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyDeleteRespHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, rekeyDeleteRespHeader.exchangeType);
+ assertTrue(rekeyDeleteRespHeader.isResponseMsg);
+ assertTrue(rekeyDeleteRespHeader.fromIkeInitiator);
+ assertTrue(rekeyDeleteResp.ikePayloadList.isEmpty());
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
+
+ verify(mMockIkeSessionCallback, never()).onClosed();
+
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDeleteExitAndRenter() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Trigger a timeout, and immediately re-enter remote-delete
+ mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.TIMEOUT_REKEY_REMOTE_DELETE);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Shift time forward, and assert the previous timeout was NOT fired.
+ mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mLooper.dispatchAll();
+
+ // Verify no request received, or response sent.
+ verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify final state has not changed - signal was not sent.
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDeleteTimedOut() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS);
+ mLooper.dispatchAll();
+
+ // Verify no request received, or response sent.
+ verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testSimulRekey() throws Exception {
+ setupIdleStateMachine();
+
+ // Prepare "rekeyed" SA
+ setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+ when(mSpyLocalInitIkeSaRecord.compareTo(mSpyRemoteInitIkeSaRecord)).thenReturn(1);
+
+ // Send Rekey request on mSpyCurrentIkeSaRecord
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+
+ // Receive Rekey request on mSpyCurrentIkeSaRecord
+ ReceivedIkePacket dummyRekeyIkeRequestReceivedPacket = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRequestReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+
+ // Receive Rekey response on mSpyCurrentIkeSaRecord
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+ verify(mSpyIkeSocket)
+ .registerIke(
+ eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+
+ // Receive Delete response on mSpyCurrentIkeSaRecord
+ ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
+ makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+
+ // Verify
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRequestReceivedPacket);
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRespReceivedPacket);
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRespReceivedPacket);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verify(mMockIkeSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testOpenIkeSession() throws Exception {
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.Initial);
+
+ mIkeSessionStateMachine.openSession();
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeInit);
+ }
+
+ @Test
+ public void testIkeInitSchedulesRekey() throws Exception {
+ setupFirstIkeSa();
+
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+
+ // Receive IKE INIT response
+ ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
+
+ // Mock IKE AUTH and transition to Idle
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+ mIkeSessionStateMachine.mSaProposal = buildSaProposal();
+
+ // Move time forward to trigger rekey
+ mLooper.moveTimeForward(SA_SOFT_LIFETIME_MS);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ @Test
+ public void testRekeyCreateIkeSchedulesRekey() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Prepare "rekeyed" SA
+ setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+
+ // Receive Rekey response
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+
+ // Mock rekey delete and transition to Idle
+ mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyLocalInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ // Move time forward to trigger rekey
+ mLooper.moveTimeForward(SA_SOFT_LIFETIME_MS);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ @Test
+ public void testBuildEncryptedInformationalMessage() throws Exception {
+ IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_INVALID_SYNTAX, new byte[0]);
+
+ boolean isResp = false;
+ IkeMessage generated =
+ mIkeSessionStateMachine.buildEncryptedInformationalMessage(
+ mSpyCurrentIkeSaRecord, new IkeInformationalPayload[] {payload}, isResp, 0);
+
+ assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), generated.ikeHeader.ikeInitiatorSpi);
+ assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), generated.ikeHeader.ikeResponderSpi);
+ assertEquals(
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), generated.ikeHeader.messageId);
+ assertEquals(isResp, generated.ikeHeader.isResponseMsg);
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, generated.ikeHeader.nextPayloadType);
+
+ List<IkeNotifyPayload> generatedPayloads =
+ generated.getPayloadListForType(
+ IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
+ assertEquals(1, generatedPayloads.size());
+
+ IkeNotifyPayload generatedPayload = generatedPayloads.get(0);
+ assertArrayEquals(new byte[0], generatedPayload.notifyData);
+ assertEquals(ERROR_TYPE_INVALID_SYNTAX, generatedPayload.notifyType);
+ }
+
+ private void verifyLastSentRespAllPackets(byte[][] expectedPackets, IkeSaRecord saRecord) {
+ if (expectedPackets == null) {
+ assertNull(saRecord.getLastSentRespAllPackets());
+ return;
+ }
+
+ assertEquals(expectedPackets.length, saRecord.getLastSentRespAllPackets().size());
+ for (int i = 0; i < expectedPackets.length; i++) {
+ assertArrayEquals(expectedPackets[i], saRecord.getLastSentRespAllPackets().get(i));
+ }
+ }
+
+ @Test
+ public void testEncryptedRetransmitterImmediatelySendsRequest() throws Exception {
+ setupIdleStateMachine();
+ byte[][] dummyLastRespBytes =
+ new byte[][] {"testRetransmitterSendsRequestLastResp".getBytes()};
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyLastRespBytes));
+
+ IkeMessage spyIkeReqMessage =
+ spy(
+ new IkeMessage(
+ new IkeHeader(
+ mSpyCurrentIkeSaRecord.getInitiatorSpi(),
+ mSpyCurrentIkeSaRecord.getResponderSpi(),
+ IkePayload.PAYLOAD_TYPE_SK,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ mSpyCurrentIkeSaRecord.isLocalInit,
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId()),
+ new LinkedList<>()));
+
+ // Use something unique as a sentinel value
+ byte[][] dummyReqBytesList =
+ new byte[][] {
+ "testRetransmitterSendsReqFrag1".getBytes(),
+ "testRetransmitterSendsReqFrag2".getBytes()
+ };
+
+ doReturn(dummyReqBytesList)
+ .when(spyIkeReqMessage)
+ .encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt());
+
+ IkeSessionStateMachine.EncryptedRetransmitter retransmitter =
+ mIkeSessionStateMachine.new EncryptedRetransmitter(spyIkeReqMessage);
+
+ // Verify message is sent out, and that request does not change cached retransmit-response
+ // mLastSentIkeResp.
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[1]), eq(REMOTE_ADDRESS));
+ verifyLastSentRespAllPackets(dummyLastRespBytes, mSpyCurrentIkeSaRecord);
+ }
+
+ // TODO: b/141275871 Test retransmisstions are fired for correct times within certain time.
+
+ @Test
+ public void testCacheLastRequestAndResponse() throws Exception {
+ setupIdleStateMachine();
+ mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(null /*reqPacket*/);
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(null /*respPacketList*/);
+
+ byte[] dummyIkeReqFirstPacket = "testLastSentRequest".getBytes();
+ byte[][] dummyIkeResp =
+ new byte[][] {
+ "testLastSentRespFrag1".getBytes(), "testLastSentRespFrag2".getBytes()
+ };
+
+ when(mMockIkeMessageHelper.encryptAndEncode(
+ any(),
+ any(),
+ eq(mSpyCurrentIkeSaRecord),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt()))
+ .thenReturn(dummyIkeResp);
+
+ // Receive a DPD request, expect to send dummyIkeResp
+ ReceivedIkePacket dummyDpdRequest =
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId(), dummyIkeReqFirstPacket);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
+ mLooper.dispatchAll();
+
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[1]), eq(REMOTE_ADDRESS));
+
+ verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord);
+ assertTrue(mSpyCurrentIkeSaRecord.isRetransmittedRequest(dummyIkeReqFirstPacket));
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testReplyRetransmittedRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Mock last sent request and response
+ byte[] dummyIkeReqFirstPacket = "testRcvRetransmittedRequestReq".getBytes();
+ byte[][] dummyIkeResp = new byte[][] {"testRcvRetransmittedRequestResp".getBytes()};
+
+ mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(dummyIkeReqFirstPacket);
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyIkeResp));
+ mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId();
+
+ // Build request with last validated message ID
+ ReceivedIkePacket request =
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1,
+ dummyIkeReqFirstPacket);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+
+ mLooper.dispatchAll();
+
+ verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord);
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testDiscardFakeRetransmittedRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Mock last sent request and response
+ byte[] dummyIkeReqFirstPacket = "testDiscardFakeRetransmittedRequestReq".getBytes();
+ byte[][] dummyIkeResp = new byte[][] {"testDiscardFakeRetransmittedRequestResp".getBytes()};
+ mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(dummyIkeReqFirstPacket);
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyIkeResp));
+ mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId();
+
+ // Build request with last validated message ID but different bytes
+ ReceivedIkePacket request =
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1, new byte[0]);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+
+ mLooper.dispatchAll();
+
+ verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord);
+ verify(mSpyIkeSocket, never()).sendIkePacket(any(), any());
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testDiscardRetransmittedResponse() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build and send fake response with last validated message ID to IKE state machine
+ ReceivedIkePacket resp =
+ makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ true /*isResp*/,
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId() - 1,
+ new LinkedList<>(),
+ new byte[0]);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify current state does not change
+ verifyRetransmissionStarted();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteRequest() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Verify outbound message
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = delMsg.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ List<IkeDeletePayload> deletePayloadList =
+ delMsg.getPayloadListForType(
+ IkePayload.PAYLOAD_TYPE_DELETE, IkeDeletePayload.class);
+ assertEquals(1, deletePayloadList.size());
+
+ IkeDeletePayload deletePayload = deletePayloadList.get(0);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(0, deletePayload.numSpi);
+ assertEquals(0, deletePayload.spiSize);
+ assertArrayEquals(new int[0], deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteResponse() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ ReceivedIkePacket received = makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+
+ verifyNotifyUserCloseSession();
+
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteResponseWithParsingError() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving response with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify no more request out
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify state machine quit properly
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteHandlesInvalidResp() throws Exception {
+ setupIdleStateMachine();
+
+ // Send delete request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+
+ // Receive response with wrong exchange type
+ ReceivedIkePacket resp =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify state machine quit properly
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteReceivedNonDeleteRequest() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Verify delete sent out.
+ verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ resetMockIkeMessageHelper(); // Discard value.
+
+ ReceivedIkePacket received = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received);
+
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ verifyIncrementRemoteReqMsgId();
+
+ // Verify outbound response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+
+ List<IkeNotifyPayload> notificationPayloadList =
+ resp.getPayloadListForType(IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
+ assertEquals(1, notificationPayloadList.size());
+
+ IkeNotifyPayload notifyPayload = notificationPayloadList.get(0);
+ assertEquals(IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType);
+ }
+
+ @Test
+ public void testDeleteIkeRemoteDelete() throws Exception {
+ setupIdleStateMachine();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteIkeRequest(mSpyCurrentIkeSaRecord));
+
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+
+ // Verify outbound message
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = delMsg.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+
+ assertTrue(delMsg.ikePayloadList.isEmpty());
+
+ verifyNotifyUserCloseSession();
+
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testReceiveDpd() throws Exception {
+ setupIdleStateMachine();
+
+ // Receive a DPD request, expect to stay in IDLE state
+ ReceivedIkePacket dummyDpdRequest = makeDpdIkeRequest(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest);
+
+ // Verify outbound response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertTrue(resp.ikePayloadList.isEmpty());
+ }
+
+ @Test
+ public void testReceiveDpdNonIdle() throws Exception {
+ setupIdleStateMachine();
+
+ // Move to a non-idle state. Use RekeyIkeRemoteDelete, as it doesn't send out any requests.
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // In a rekey state, receiving (and handling) a DPD should not result in a change of states
+ ReceivedIkePacket dummyDpdRequest = makeDpdIkeRequest(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
+
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest);
+
+ // Verify outbound response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertTrue(resp.ikePayloadList.isEmpty());
+ }
+
+ @Test
+ public void testIdleTriggersNewRequests() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Verify that the command is executed, and the state machine transitions to the right state
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ verifyRetransmissionStarted();
+ }
+
+ @Test
+ public void testNonIdleStateDoesNotTriggerNewRequests() throws Exception {
+ setupIdleStateMachine();
+
+ // Force ourselves into a non-idle state
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving);
+ mLooper.dispatchAll();
+ verifyEncryptAndEncodeNeverCalled();
+
+ // Queue a local request, and expect that it is not run (yet)
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Verify that the state machine is still in the Receiving state
+ verifyEncryptAndEncodeNeverCalled();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.Receiving);
+
+ // Go back to Idle, and expect to immediately transition to RekeyIkeLocalCreate from the
+ // queued request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ }
+
+ @Test
+ public void testOpenChildSessionValidatesArgs() throws Exception {
+ setupIdleStateMachine();
+
+ // Expect failure - no callbacks provided
+ try {
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, null);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Expect failure - callbacks already registered
+ try {
+ mIkeSessionStateMachine.openChildSession(
+ mChildSessionOptions, mMockChildSessionCallback);
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testOpenChildSession() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionCallback cb = mock(ChildSessionCallback.class);
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb);
+
+ // Test that inserting the same cb returns an error, even before the state
+ // machine has a chance to process it.
+ try {
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ verify(mMockChildSessionFactoryHelper)
+ .makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ eq(cb),
+ any());
+
+ // Verify state in IkeSessionStateMachine
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ synchronized (mIkeSessionStateMachine.mChildCbToSessions) {
+ assertTrue(mIkeSessionStateMachine.mChildCbToSessions.containsKey(cb));
+ }
+ }
+
+ @Test
+ public void testCloseChildSessionValidatesArgs() throws Exception {
+ setupIdleStateMachine();
+
+ // Expect failure - callbacks not registered
+ try {
+ mIkeSessionStateMachine.closeChildSession(mock(ChildSessionCallback.class));
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testCloseChildSession() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.closeChildSession(mMockChildSessionCallback);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ }
+
+ @Test
+ public void testCloseImmediatelyAfterOpenChildSession() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionCallback cb = mock(ChildSessionCallback.class);
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb);
+
+ // Verify that closing the session immediately still picks up the child callback
+ // even before the looper has a chance to run.
+ mIkeSessionStateMachine.closeChildSession(mMockChildSessionCallback);
+ }
+
+ @Test
+ public void testOnChildSessionClosed() throws Exception {
+ setupIdleStateMachine();
+
+ mDummyChildSmCallback.onChildSessionClosed(mMockChildSessionCallback);
+
+ synchronized (mIkeSessionStateMachine.mChildCbToSessions) {
+ assertFalse(
+ mIkeSessionStateMachine.mChildCbToSessions.containsKey(
+ mMockChildSessionCallback));
+ }
+ }
+
+ @Test
+ public void testHandleUnexpectedExceptionInEnterState() throws Exception {
+ Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG);
+ IkeManager.setIkeLog(spyIkeLog);
+
+ IkeSessionOptions mockSessionOptions = mock(IkeSessionOptions.class);
+ when(mockSessionOptions.getSaProposals()).thenThrow(mock(RuntimeException.class));
+
+ IkeSessionStateMachine ikeSession =
+ new IkeSessionStateMachine(
+ mLooper.getLooper(),
+ mContext,
+ mIpSecManager,
+ mockSessionOptions,
+ mChildSessionOptions,
+ mSpyUserCbExecutor,
+ mMockIkeSessionCallback,
+ mMockChildSessionCallback,
+ mMockEapAuthenticatorFactory);
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+ mLooper.dispatchAll();
+
+ assertNull(ikeSession.getCurrentState());
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
+ }
+
+ @Test
+ public void testHandleUnexpectedExceptionInProcessStateMsg() throws Exception {
+ Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG);
+ IkeManager.setIkeLog(spyIkeLog);
+
+ setupIdleStateMachine();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, null /*receivedIkePacket*/);
+ mLooper.dispatchAll();
+
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeInitRcvErrorNotify() throws Exception {
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Receive IKE INIT response with erro notification.
+ List<IkePayload> payloads = new LinkedList<>();
+ payloads.add(new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN));
+ ReceivedIkePacket resp =
+ makeDummyUnencryptedReceivedIkePacket(
+ 1L /*initiator SPI*/,
+ 2L /*respodner SPI*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ true /*isResp*/,
+ false /*fromIkeInit*/,
+ payloads);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Fires user error callbacks
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(
+ argThat(err -> err instanceof NoValidProposalChosenException));
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ private void mockSendRekeyChildReq() throws Exception {
+ setupIdleStateMachine();
+
+ ChildLocalRequest childLocalRequest =
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/);
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, childLocalRequest);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).rekeyChildSession();
+
+ // Mocking sending request
+ mDummyChildSmCallback.onOutboundPayloadsReady(
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ false /*isResp*/,
+ new LinkedList<>(),
+ mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+ }
+
+ private void mockRcvTempFail() throws Exception {
+ ReceivedIkePacket resp =
+ makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_TEMPORARY_FAILURE));
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void testTempFailureHandlerScheduleRetry() throws Exception {
+ mockSendRekeyChildReq();
+
+ // Mock sending TEMPORARY_FAILURE response
+ mockRcvTempFail();
+
+ // Move time forward to trigger retry
+ mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+
+ // Verify that rekey is triggered again
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+ }
+
+ @Test
+ public void testTempFailureHandlerTimeout() throws Exception {
+ long currentTime = 0;
+ int retryCnt = 0;
+
+ mockSendRekeyChildReq();
+
+ while (currentTime + RETRY_INTERVAL_MS < TEMP_FAILURE_RETRY_TIMEOUT_MS) {
+ mockRcvTempFail();
+
+ mLooper.moveTimeForward(RETRY_INTERVAL_MS);
+ currentTime += RETRY_INTERVAL_MS;
+ mLooper.dispatchAll();
+
+ retryCnt++;
+ verify(mMockChildSessionStateMachine, times(1 + retryCnt)).rekeyChildSession();
+ }
+
+ mLooper.moveTimeForward(RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ }
+
+ @Test
+ public void testTempFailureHandlerCancelTimer() throws Exception {
+ mockSendRekeyChildReq();
+
+ // Mock sending TEMPORARY_FAILURE response
+ mockRcvTempFail();
+
+ // Move time forward to trigger retry
+ mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+ verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+
+ // Mock sending a valid response
+ ReceivedIkePacket resp =
+ makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ EXCHANGE_TYPE_CREATE_CHILD_SA,
+ true /*isResp*/,
+ new LinkedList<>());
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ // Move time forward
+ mLooper.moveTimeForward(IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS);
+ mLooper.dispatchAll();
+
+ // Validate IKE Session is not closed
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ // Validate no more retry
+ verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+ }
+
+ @Test
+ public void testIdleReceiveRequestWithFatalError() throws Exception {
+ setupIdleStateMachine();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloads.size());
+
+ IkePayload payload = payloads.get(0);
+ assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType);
+ assertEquals(ERROR_TYPE_INVALID_SYNTAX, ((IkeNotifyPayload) payload).notifyType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testHandlesInvalidRequest() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mChildProcedureOngoing);
+
+ // Receive an IKE AUTH request
+ ReceivedIkePacket request =
+ makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ false /*isResp*/,
+ new LinkedList<IkePayload>());
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ assertEquals(
+ ERROR_TYPE_INVALID_SYNTAX, ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType);
+
+ // Verify IKE Session has quit
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testIdleHandlesUnprotectedPacket() throws Exception {
+ setupIdleStateMachine();
+
+ ReceivedIkePacket req =
+ makeDummyReceivedIkePacketWithUnprotectedError(
+ mSpyCurrentIkeSaRecord,
+ false /*isResp*/,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ mock(IkeException.class));
+
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+}