blob: 7e8b29cea095019971751813bf2ad6d03720bb62 [file] [log] [blame]
/*
* 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.ike.ikev2;
import static com.android.ike.ikev2.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_OK;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
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.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
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.os.Handler;
import android.os.Looper;
import android.os.test.TestLooper;
import com.android.ike.TestUtils;
import com.android.ike.ikev2.ChildSessionStateMachine.IChildSessionSmCallback;
import com.android.ike.ikev2.ChildSessionStateMachineFactory.ChildSessionFactoryHelper;
import com.android.ike.ikev2.ChildSessionStateMachineFactory.IChildSessionFactoryHelper;
import com.android.ike.ikev2.IkeIdentification.IkeIpv4AddrIdentification;
import com.android.ike.ikev2.IkeLocalRequestScheduler.ChildLocalRequest;
import com.android.ike.ikev2.IkeLocalRequestScheduler.LocalRequest;
import com.android.ike.ikev2.IkeSessionStateMachine.IkeSecurityParameterIndex;
import com.android.ike.ikev2.IkeSessionStateMachine.ReceivedIkePacket;
import com.android.ike.ikev2.SaRecord.ISaRecordHelper;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.SaRecord.IkeSaRecordConfig;
import com.android.ike.ikev2.SaRecord.SaRecordHelper;
import com.android.ike.ikev2.crypto.IkeCipher;
import com.android.ike.ikev2.crypto.IkeMacIntegrity;
import com.android.ike.ikev2.crypto.IkeMacPrf;
import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.message.IkeAuthPskPayload;
import com.android.ike.ikev2.message.IkeDeletePayload;
import com.android.ike.ikev2.message.IkeHeader;
import com.android.ike.ikev2.message.IkeIdPayload;
import com.android.ike.ikev2.message.IkeInformationalPayload;
import com.android.ike.ikev2.message.IkeKePayload;
import com.android.ike.ikev2.message.IkeMessage;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
import com.android.ike.ikev2.message.IkeMessage.IIkeMessageHelper;
import com.android.ike.ikev2.message.IkeMessage.IkeMessageHelper;
import com.android.ike.ikev2.message.IkeNoncePayload;
import com.android.ike.ikev2.message.IkeNotifyPayload;
import com.android.ike.ikev2.message.IkePayload;
import com.android.ike.ikev2.message.IkeSaPayload;
import com.android.ike.ikev2.message.IkeSaPayload.DhGroupTransform;
import com.android.ike.ikev2.message.IkeSaPayload.EncryptionTransform;
import com.android.ike.ikev2.message.IkeSaPayload.EsnTransform;
import com.android.ike.ikev2.message.IkeSaPayload.IntegrityTransform;
import com.android.ike.ikev2.message.IkeSaPayload.PrfTransform;
import com.android.ike.ikev2.message.IkeTestUtils;
import com.android.ike.ikev2.message.IkeTsPayload;
import libcore.net.InetAddressUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.net.Inet4Address;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public final class 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 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 DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000";
private static final String NOTIFY_REKEY_IKE_PAYLOAD_HEX_STRING = "2100000800004009";
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 CHILD_SA_RESP_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 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 DUMMY_CHILD_SPI_RESOURCE_ID_LOCAL = 0x1234;
private static final int CHILD_SPI_LOCAL = 0x2ad4c0a2;
private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234;
private static final int UDP_ENCAP_PORT = 34567;
private static long sIkeInitResponseSpiBase = 1L;
private MockIpSecTestUtils mMockIpSecTestUtils;
private Context mContext;
private IpSecManager mIpSecManager;
private UdpEncapsulationSocket mUdpEncapSocket;
private TestLooper mLooper;
private IkeSessionStateMachine mIkeSessionStateMachine;
private IkeSessionOptions mIkeSessionOptions;
private ChildSessionOptions mChildSessionOptions;
private Handler mUserCbHandler;
private IIkeSessionCallback mMockIkeSessionCallback;
private IChildSessionCallback mMockChildSessionCallback;
private EncryptionTransform mIkeEncryptionTransform;
private IntegrityTransform mIkeIntegrityTransform;
private PrfTransform mIkePrfTransform;
private DhGroupTransform mIkeDhGroupTransform;
private IIkeMessageHelper mMockIkeMessageHelper;
private ISaRecordHelper mMockSaRecordHelper;
private ChildSessionStateMachine mMockChildSessionStateMachine;
private IChildSessionFactoryHelper mMockChildSessionFactoryHelper;
private IkeSaRecord mSpyCurrentIkeSaRecord;
private IkeSaRecord mSpyLocalInitIkeSaRecord;
private IkeSaRecord mSpyRemoteInitIkeSaRecord;
private int mExpectedCurrentSaLocalReqMsgId;
private int mExpectedCurrentSaRemoteReqMsgId;
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);
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 DecodeResult(DECODE_STATUS_OK, dummyIkeMessage, null));
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 {
boolean fromIkeInit = !ikeSaRecord.isLocalInit;
IkeMessage dummyIkeMessage =
makeDummyIkeMessageForTest(
ikeSaRecord.getInitiatorSpi(),
ikeSaRecord.getResponderSpi(),
eType,
isResp,
fromIkeInit,
isResp
? ikeSaRecord.getLocalRequestMessageId()
: ikeSaRecord.getRemoteRequestMessageId(),
true /*isEncyprted*/,
payloadList);
byte[] dummyIkePacketBytes = new byte[0];
when(mMockIkeMessageHelper.decode(
anyInt(),
any(),
any(),
eq(ikeSaRecord),
eq(dummyIkeMessage.ikeHeader),
eq(dummyIkePacketBytes)))
.thenReturn(new DecodeResult(DECODE_STATUS_OK, dummyIkeMessage, null));
return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
}
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));
}
public IkeSessionStateMachineTest() throws Exception {
mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class);
mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class);
mMockChildSessionFactoryHelper = mock(IChildSessionFactoryHelper.class);
when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]);
when(mMockIkeMessageHelper.encryptAndEncode(any(), any(), any(), any()))
.thenReturn(new byte[0]);
}
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));
}
@Before
public void setUp() throws Exception {
if (Looper.myLooper() == null) Looper.prepare();
mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec();
mIpSecManager = mMockIpSecTestUtils.getIpSecManager();
mContext = mMockIpSecTestUtils.getContext();
mUdpEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
mIkeSessionOptions = buildIkeSessionOptions();
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);
mUserCbHandler = new Handler();
mMockIkeSessionCallback = mock(IIkeSessionCallback.class);
mMockChildSessionCallback = mock(IChildSessionCallback.class);
mLooper = new TestLooper();
ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
mMockChildSessionFactoryHelper);
setupChildStateMachineFactory(mMockChildSessionStateMachine);
// Setup state machine
mIkeSessionStateMachine =
new IkeSessionStateMachine(
mLooper.getLooper(),
mContext,
mIpSecManager,
mIkeSessionOptions,
mChildSessionOptions,
mUserCbHandler,
mMockIkeSessionCallback,
mMockChildSessionCallback);
mIkeSessionStateMachine.setDbg(true);
mIkeSessionStateMachine.start();
mLooper.dispatchAll();
mIkeSessionStateMachine.mLocalAddress = LOCAL_ADDRESS;
IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper);
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();
IkeMessage.setIkeMessageHelper(new IkeMessageHelper());
SaRecord.setSaRecordHelper(new SaRecordHelper());
ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
new ChildSessionFactoryHelper());
}
private SaProposal buildSaProposal() throws Exception {
return SaProposal.Builder.newIkeSaProposalBuilder()
.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 buildIkeSessionOptions() throws Exception {
SaProposal saProposal = buildSaProposal();
byte[] psk = TestUtils.hexStringToByteArray(PSK_HEX_STRING);
IkeSessionOptions sessionOptions =
new IkeSessionOptions.Builder(REMOTE_ADDRESS, mUdpEncapSocket)
.addSaProposal(saProposal)
.setLocalIdentification(
new IkeIpv4AddrIdentification((Inet4Address) LOCAL_ADDRESS))
.setRemoteIdentification(
new IkeIpv4AddrIdentification((Inet4Address) REMOTE_ADDRESS))
.setLocalAuthPsk(psk)
.setRemoteAuthPsk(psk)
.build();
return sessionOptions;
}
private ChildSessionOptions buildChildSessionOptions() throws Exception {
SaProposal saProposal =
SaProposal.Builder.newChildSaProposalBuilder()
.addEncryptionAlgorithm(
SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
.build();
return new ChildSessionOptions.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);
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);
// 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 /*initiatorSpi*/,
++sIkeInitResponseSpiBase,
IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
true /*isResp*/,
false /*fromIkeInit*/,
payloadTypeList,
payloadHexStringList);
}
private ReceivedIkePacket makeIkeAuthResponse() throws Exception {
List<Integer> payloadTypeList = new LinkedList<>();
List<String> payloadHexStringList = new LinkedList<>();
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_ID_RESPONDER);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_AUTH);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
payloadHexStringList.add(ID_PAYLOAD_RESPONDER_HEX_STRING);
payloadHexStringList.add(PSK_AUTH_RESP_PAYLOAD_HEX_STRING);
payloadHexStringList.add(CHILD_SA_RESP_PAYLOAD_HEX_STRING);
payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
return makeDummyEncryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
true /*isResp*/,
payloadTypeList,
payloadHexStringList);
}
private ReceivedIkePacket makeCreateChildResponse() 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_RESP_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 makeDummyEncryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
true /*isResp*/,
payloadTypeList,
payloadHexStringList);
}
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_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 makeRekeyIkeRequest() 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_INIT_PAYLOAD_HEX_STRING);
return makeDummyEncryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
false /*isResp*/,
payloadTypeList,
payloadHexStringList);
}
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 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());
}
@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();
}
@Test
public void testCreateIkeLocalIkeInit() throws Exception {
when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any()))
.thenReturn(mSpyCurrentIkeSaRecord);
// 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);
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));
IkeSocket ikeSocket = mIkeSessionStateMachine.mIkeSocket;
assertNotNull(ikeSocket);
assertNotEquals(
-1 /*not found*/, ikeSocket.mSpiToIkeSession.indexOfValue(mIkeSessionStateMachine));
verify(mMockIkeMessageHelper)
.decode(0, dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes);
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
// Validate negotiated SA proposal.
SaProposal negotiatedProposal = mIkeSessionStateMachine.mSaProposal;
assertNotNull(negotiatedProposal);
assertEquals(
new EncryptionTransform[] {mIkeEncryptionTransform},
negotiatedProposal.getEncryptionTransforms());
assertEquals(
new IntegrityTransform[] {mIkeIntegrityTransform},
negotiatedProposal.getIntegrityTransforms());
assertEquals(new PrfTransform[] {mIkePrfTransform}, negotiatedProposal.getPrfTransforms());
assertEquals(new EsnTransform[0], negotiatedProposal.getEsnTransforms());
// 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);
// Validate NAT detection
assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat);
assertFalse(mIkeSessionStateMachine.mIsRemoteBehindNat);
}
/** Initializes the mIkeSessionStateMachine in the IDLE state. */
private void setupIdleStateMachine() 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.addIkeSaRecord(mSpyCurrentIkeSaRecord);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
private void mockIkeSetup() throws Exception {
when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any()))
.thenReturn(mSpyCurrentIkeSaRecord);
// Send IKE INIT request
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
// Receive IKE INIT response
ReceivedIkePacket dummyIkeInitRespReceivedPacket = makeIkeInitResponse();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyIkeInitRespReceivedPacket);
mLooper.dispatchAll();
verifyIncrementLocaReqMsgId();
IkeMessage mockIkeInitResp = mock(IkeMessage.class);
mIkeSessionStateMachine.mIkeInitResponseBytes =
TestUtils.hexStringToByteArray(IKE_INIT_RESP_HEX_STRING);
mIkeSessionStateMachine.mIkeRespNoncePayload =
(IkeNoncePayload)
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_NONCE,
true /*isResp*/,
NONCE_RESP_PAYLOAD_HEX_STRING);
// Receive IKE AUTH response
ReceivedIkePacket dummyIkeAuthRespReceivedPacket = makeIkeAuthResponse();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyIkeAuthRespReceivedPacket);
mLooper.dispatchAll();
verifyIncrementLocaReqMsgId();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
// Mock finishing first Child SA negotiation.
verify(mMockChildSessionFactoryHelper)
.makeChildSessionStateMachine(
eq(mLooper.getLooper()),
eq(mContext),
eq(mChildSessionOptions),
eq(mUserCbHandler),
eq(mMockChildSessionCallback),
mChildSessionSmCbCaptor.capture());
IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue();
cb.onProcedureFinished(mMockChildSessionStateMachine);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
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(mUserCbHandler),
any(IChildSessionCallback.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(
IChildSessionCallback callback, ChildSessionStateMachine sm) {
setupChildStateMachineFactory(sm);
mIkeSessionStateMachine.registerChildSessionCallback(mChildSessionOptions, callback);
}
@Test
public void testCreateAdditionalChild() throws Exception {
setupIdleStateMachine();
IChildSessionCallback childCallback = mock(IChildSessionCallback.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(mUserCbHandler),
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();
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
IkeMessage createChildRequest = mIkeMessageCaptor.getValue();
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 = makeCreateChildResponse();
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);
}
private IChildSessionSmCallback createChildAndGetChildSessionSmCallback(
ChildSessionStateMachine child, int remoteSpi) throws Exception {
return createChildAndGetChildSessionSmCallback(
child, remoteSpi, mock(IChildSessionCallback.class));
}
private IChildSessionSmCallback createChildAndGetChildSessionSmCallback(
ChildSessionStateMachine child, int remoteSpi, IChildSessionCallback childCallback)
throws Exception {
registerChildStateMachine(childCallback, child);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
new ChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD,
childCallback,
mChildSessionOptions));
mLooper.dispatchAll();
verify(mMockChildSessionFactoryHelper)
.makeChildSessionStateMachine(
eq(mLooper.getLooper()),
eq(mContext),
eq(mChildSessionOptions),
eq(mUserCbHandler),
eq(childCallback),
mChildSessionSmCbCaptor.capture());
IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue();
cb.onChildSaCreated(remoteSpi, child);
cb.onProcedureFinished(child);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
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), 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) {
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
IkeMessage deleteChildMessage = mIkeMessageCaptor.getValue();
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 testRemoteDeleteOneChild() throws Exception {
ChildSessionStateMachine child = mock(ChildSessionStateMachine.class);
int childRemoteSpi = 11;
int childLocalSpi = 12;
setupIdleStateMachine();
IChildSessionSmCallback childSmCb =
createChildAndGetChildSessionSmCallback(child, childRemoteSpi);
transitionToChildProcedureOngoing();
// Receive Delete Child Request
IkeDeletePayload[] inboundDelPayloads =
new IkeDeletePayload[] {new IkeDeletePayload(new int[] {childRemoteSpi})};
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
mLooper.dispatchAll();
// Verify received payloads
verifyChildReceiveDeleteRequest(child, inboundDelPayloads);
// Outbound payload list ready
IkeDeletePayload outDelPayload = new IkeDeletePayload(new int[] {childLocalSpi});
outboundDeleteChildPayloadsReady(childSmCb, outDelPayload, true /*isResp*/, child);
// 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
verify(mMockIkeMessageHelper, never())
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
any(IkeMessage.class));
// 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 {
ChildSessionStateMachine child = mock(ChildSessionStateMachine.class);
int childOneRemoteSpi = 11;
int childOneLocalSpi = 12;
int childTwoRemoteSpi = 21;
int childTwoLocalSpi = 22;
setupIdleStateMachine();
IChildSessionSmCallback childSmCb =
createChildAndGetChildSessionSmCallback(child, childOneRemoteSpi);
childSmCb.onChildSaCreated(childTwoRemoteSpi, child);
transitionToChildProcedureOngoing();
// Receive Delete Child Request
IkeDeletePayload[] inboundDelPayloads =
new IkeDeletePayload[] {
new IkeDeletePayload(new int[] {childOneRemoteSpi}),
new IkeDeletePayload(new int[] {childTwoRemoteSpi})
};
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
mLooper.dispatchAll();
// Verify received payloads
verifyChildReceiveDeleteRequest(child, inboundDelPayloads);
// child outbound payload list ready
IkeDeletePayload outDelPayload =
new IkeDeletePayload(new int[] {childOneLocalSpi, childTwoLocalSpi});
outboundDeleteChildPayloadsReady(childSmCb, outDelPayload, true /*isResp*/, child);
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 {
ChildSessionStateMachine child = mock(ChildSessionStateMachine.class);
int childRemoteSpi = 11;
int childLocalSpi = 12;
int unrecognizedSpi = 2;
setupIdleStateMachine();
IChildSessionSmCallback childSmCb =
createChildAndGetChildSessionSmCallback(child, childRemoteSpi);
transitionToChildProcedureOngoing();
// Receive Delete Child Request
IkeDeletePayload[] inboundDelPayloads =
new IkeDeletePayload[] {
new IkeDeletePayload(new int[] {unrecognizedSpi, childRemoteSpi})
};
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
mLooper.dispatchAll();
// Verify received payloads
verifyChildReceiveDeleteRequest(child, inboundDelPayloads);
// child outbound payload list ready
IkeDeletePayload outPayload = new IkeDeletePayload(new int[] {childLocalSpi});
outboundDeleteChildPayloadsReady(childSmCb, outPayload, true /*isResp*/, child);
mLooper.dispatchAll();
// Verify outbound response
List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
assertEquals(1, payloadList.size());
assertEquals(outPayload, ((IkeDeletePayload) payloadList.get(0)));
}
@Test
public void testCreateIkeLocalIkeAuth() throws Exception {
mockIkeSetup();
mLooper.dispatchAll();
// Validate outbound IKE AUTH request
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
IkeMessage ikeAuthReqMessage = mIkeMessageCaptor.getValue();
IkeHeader ikeHeader = ikeAuthReqMessage.ikeHeader;
assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
assertFalse(ikeHeader.isResponseMsg);
assertTrue(ikeHeader.fromIkeInitiator);
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_AUTH, IkeAuthPskPayload.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));
// Validate inbound IKE AUTH response
verify(mMockIkeMessageHelper).decode(anyInt(), any(), any(), any(), any(), any());
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));
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));
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
@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);
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound message
IkeMessage rekeyMsg = mIkeMessageCaptor.getValue();
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();
// Prepare "rekeyed" SA
when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
.thenReturn(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);
assertEquals(mSpyLocalInitIkeSaRecord, mIkeSessionStateMachine.mLocalInitNewIkeSaRecord);
}
@Test
public void testRekeyIkeLocalDeleteSendsRequest() throws Exception {
setupIdleStateMachine();
// Seed fake rekey data and force transition to RekeyIkeLocalDelete
mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mRekeyIkeLocalDelete);
mLooper.dispatchAll();
// Verify Rekey-Delete request
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.RekeyIkeLocalDelete);
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound message
IkeMessage delMsg = mIkeMessageCaptor.getValue();
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);
}
@Test
public void testRekeyIkeLocalDeleteHandlesResponse() throws Exception {
setupIdleStateMachine();
// Seed fake rekey data and force transition to RekeyIkeLocalDelete
mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mRekeyIkeLocalDelete);
mLooper.dispatchAll();
// 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.
verify(mSpyCurrentIkeSaRecord).close();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyLocalInitIkeSaRecord);
}
@Test
public void testRekeyIkeLocalDeleteWithRequestOnNewSa() throws Exception {
setupIdleStateMachine();
// 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();
// 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.
verify(mSpyCurrentIkeSaRecord).close();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyLocalInitIkeSaRecord);
}
@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.
verify(mSpyCurrentIkeSaRecord).close();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyRemoteInitIkeSaRecord);
}
@Test
public void testRekeyIkeRemoteCreate() throws Exception {
setupIdleStateMachine();
when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
.thenReturn(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
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
IkeMessage rekeyCreateResp = mIkeMessageCaptor.getValue();
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);
}
@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
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
IkeMessage rekeyDeleteResp = mIkeMessageCaptor.getValue();
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.
verify(mSpyCurrentIkeSaRecord).close();
verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket);
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mCurrentIkeSaRecord);
}
@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_IKE);
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());
verify(mMockIkeMessageHelper, never())
.encryptAndEncode(
anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
// 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());
verify(mMockIkeMessageHelper, never())
.encryptAndEncode(
anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
// Verify final state - Idle, with new SA, and old SA closed.
verify(mSpyCurrentIkeSaRecord).close();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mCurrentIkeSaRecord);
}
@Test
public void testSimulRekey() throws Exception {
setupIdleStateMachine();
when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
.thenReturn(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();
// 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);
assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyLocalInitIkeSaRecord);
}
@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);
}
@Test
public void testRetransmitterImmediatelySendsRequest() throws Exception {
setupIdleStateMachine();
IkeSocket mockIkeSocket = mock(IkeSocket.class);
mIkeSessionStateMachine.mIkeSocket = mockIkeSocket;
IkeMessage mockIkeMessage = mock(IkeMessage.class);
// Use something unique as a sentinel value
byte[] dummyBytes = "testRetransmitterSendsRequest".getBytes();
when(mockIkeMessage.encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord)))
.thenReturn(dummyBytes);
IkeSessionStateMachine.Retransmitter retransmitter =
mIkeSessionStateMachine.new Retransmitter(mockIkeMessage);
verify(mockIkeSocket).sendIkePacket(eq(dummyBytes), eq(REMOTE_ADDRESS));
}
@Test
public void testDeleteIkeLocalDeleteRequest() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
mLooper.dispatchAll();
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound message
IkeMessage delMsg = mIkeMessageCaptor.getValue();
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();
ReceivedIkePacket received = makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received);
mLooper.dispatchAll();
verifyIncrementLocaReqMsgId();
// TODO: Verify callbacks
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Closed);
}
@Test
public void testDeleteIkeLocalDeleteReceivedNonDeleteRequest() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
mLooper.dispatchAll();
// Verify delete sent out.
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
reset(mMockIkeMessageHelper); // Discard value.
when(mMockIkeMessageHelper.encryptAndEncode(any(), any(), any(), any()))
.thenReturn(new byte[0]);
ReceivedIkePacket received = makeRekeyIkeRequest();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received);
mLooper.dispatchAll();
verifyIncrementRemoteReqMsgId();
verify(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound response
IkeMessage resp = mIkeMessageCaptor.getValue();
IkeHeader ikeHeader = resp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, 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(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound message
IkeMessage delMsg = mIkeMessageCaptor.getValue();
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());
// TODO: Verify callbacks
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Closed);
}
@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(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound response
IkeMessage resp = mIkeMessageCaptor.getValue();
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(mMockIkeMessageHelper)
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture());
// Verify outbound response
IkeMessage resp = mIkeMessageCaptor.getValue();
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);
}
@Test
public void testNonIdleStateDoesNotTriggerNewRequests() throws Exception {
setupIdleStateMachine();
// Force ourselves into a non-idle state
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving);
mLooper.dispatchAll();
verify(mMockIkeMessageHelper, never()).encryptAndEncode(any(), any(), any(), any());
// 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
verify(mMockIkeMessageHelper, never()).encryptAndEncode(any(), any(), any(), any());
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);
verify(mMockIkeMessageHelper, times(1)).encryptAndEncode(any(), any(), any(), any());
}
@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();
IChildSessionCallback cb = mock(IChildSessionCallback.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(mUserCbHandler),
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(IChildSessionCallback.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();
IChildSessionCallback cb = mock(IChildSessionCallback.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();
ChildSessionStateMachine child = mock(ChildSessionStateMachine.class);
IChildSessionCallback childCb = mock(IChildSessionCallback.class);
IChildSessionSmCallback smCb =
createChildAndGetChildSessionSmCallback(child, 0 /* placeholder */, childCb);
smCb.onChildSessionClosed(childCb);
synchronized (mIkeSessionStateMachine.mChildCbToSessions) {
assertFalse(mIkeSessionStateMachine.mChildCbToSessions.containsKey(childCb));
}
}
}