Inject SecureRandom in SPI generation

Create SPI generator for IKE and IPsec SPI that can take a
RandomnessFactory and use return value of #getRandom to allocate
SPI. When the RandomnessFactory returns a DeterministicRandom,
the generator can generate deterministic SPI to support test mode.

Bug: 148689509
Test: atest FrameworksIkeTests
Change-Id: I477a02bc9924dd8c35dda19c53598167f9960075
Merged-In: I477a02bc9924dd8c35dda19c53598167f9960075
(cherry picked from commit 33b694e5f8c53e3f0e561db7793769eb9a7c769f)
diff --git a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
index 2dfdb8f..6cdcd26 100644
--- a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
@@ -93,6 +93,7 @@
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.ChildProposal;
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform;
 import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 import com.android.internal.util.State;
 
 import java.io.IOException;
@@ -154,6 +155,12 @@
     private final AlarmManager mAlarmManager;
     private final IpSecManager mIpSecManager;
 
+    /**
+     * mIpSecSpiGenerator will be used by all Child SA creations in this Child Session to avoid SPI
+     * collision in test mode.
+     */
+    private final IpSecSpiGenerator mIpSecSpiGenerator;
+
     /** User provided configurations. */
     @VisibleForTesting final ChildSessionParams mChildSessionParams;
 
@@ -231,6 +238,7 @@
             int ikeSessionUniqueId,
             AlarmManager alarmManager,
             IpSecManager ipSecManager,
+            IpSecSpiGenerator ipSecSpiGenerator,
             ChildSessionParams sessionParams,
             Executor userCbExecutor,
             ChildSessionCallback userCallback,
@@ -241,6 +249,7 @@
         mIkeSessionId = ikeSessionUniqueId;
         mAlarmManager = alarmManager;
         mIpSecManager = ipSecManager;
+        mIpSecSpiGenerator = ipSecSpiGenerator;
         mChildSessionParams = sessionParams;
 
         mUserCallback = userCallback;
@@ -706,7 +715,7 @@
                             exchangeType,
                             expectedExchangeType,
                             mChildSessionParams.isTransportMode(),
-                            mIpSecManager,
+                            mIpSecSpiGenerator,
                             mRemoteAddress);
             switch (createChildResult.status) {
                 case CREATE_STATUS_OK:
@@ -919,7 +928,7 @@
             try {
                 mRequestPayloads =
                         CreateChildSaHelper.getInitChildCreateReqPayloads(
-                                mIpSecManager,
+                                mIpSecSpiGenerator,
                                 mLocalAddress,
                                 mChildSessionParams,
                                 false /*isFirstChildSa*/);
@@ -937,7 +946,7 @@
                         false /*isResp*/,
                         mRequestPayloads,
                         ChildSessionStateMachine.this);
-            } catch (ResourceUnavailableException e) {
+            } catch (SpiUnavailableException | ResourceUnavailableException e) {
                 // Fail to assign SPI
                 handleChildFatalError(e);
             }
@@ -1253,7 +1262,7 @@
                 // Build request with negotiated proposal and TS.
                 mRequestPayloads =
                         CreateChildSaHelper.getRekeyChildCreateReqPayloads(
-                                mIpSecManager,
+                                mIpSecSpiGenerator,
                                 mLocalAddress,
                                 mSaProposal,
                                 mLocalTs,
@@ -1265,7 +1274,7 @@
                         false /*isResp*/,
                         mRequestPayloads,
                         ChildSessionStateMachine.this);
-            } catch (ResourceUnavailableException e) {
+            } catch (SpiUnavailableException | ResourceUnavailableException e) {
                 loge("Fail to assign Child SPI. Schedule a retry for rekey Child");
                 mChildSmCallback.scheduleRetryLocalRequest(
                         (ChildLocalRequest) mCurrentChildSaRecord.getFutureRekeyEvent());
@@ -1286,7 +1295,7 @@
                                     EXCHANGE_TYPE_CREATE_CHILD_SA,
                                     mChildSessionParams.isTransportMode(),
                                     mCurrentChildSaRecord,
-                                    mIpSecManager,
+                                    mIpSecSpiGenerator,
                                     mRemoteAddress);
 
                     switch (createChildResult.status) {
@@ -1437,7 +1446,7 @@
 
                 respPayloads =
                         CreateChildSaHelper.getRekeyChildCreateRespPayloads(
-                                mIpSecManager,
+                                mIpSecSpiGenerator,
                                 mLocalAddress,
                                 respProposalNumber,
                                 mSaProposal,
@@ -1448,7 +1457,7 @@
             } catch (NoValidProposalChosenException e) {
                 handleCreationFailureAndBackToIdle(e);
                 return;
-            } catch (ResourceUnavailableException e) {
+            } catch (SpiUnavailableException | ResourceUnavailableException e) {
                 handleCreationFailureAndBackToIdle(
                         new NoValidProposalChosenException("Fail to assign inbound SPI", e));
                 return;
@@ -1461,7 +1470,7 @@
                             req.exchangeType /*exchangeType*/,
                             EXCHANGE_TYPE_CREATE_CHILD_SA /*expectedExchangeType*/,
                             mChildSessionParams.isTransportMode(),
-                            mIpSecManager,
+                            mIpSecSpiGenerator,
                             mRemoteAddress);
 
             switch (createChildResult.status) {
@@ -1759,11 +1768,11 @@
     static class CreateChildSaHelper {
         /** Create payload list for creating the initial Child SA for this Child Session. */
         public static List<IkePayload> getInitChildCreateReqPayloads(
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress localAddress,
                 ChildSessionParams childSessionParams,
                 boolean isFirstChildSa)
-                throws ResourceUnavailableException {
+                throws SpiUnavailableException, ResourceUnavailableException {
 
             ChildSaProposal[] saProposals = childSessionParams.getSaProposalsInternal();
 
@@ -1778,7 +1787,7 @@
             List<IkePayload> payloadList =
                     getChildCreatePayloads(
                             IkeSaPayload.createChildSaRequestPayload(
-                                    saProposals, ipSecManager, localAddress),
+                                    saProposals, ipSecSpiGenerator, localAddress),
                             childSessionParams.getInboundTrafficSelectorsInternal(),
                             childSessionParams.getOutboundTrafficSelectorsInternal(),
                             childSessionParams.isTransportMode(),
@@ -1796,19 +1805,19 @@
 
         /** Create payload list as a rekey Child Session request. */
         public static List<IkePayload> getRekeyChildCreateReqPayloads(
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress localAddress,
                 ChildSaProposal currentProposal,
                 IkeTrafficSelector[] currentLocalTs,
                 IkeTrafficSelector[] currentRemoteTs,
                 int localSpi,
                 boolean isTransport)
-                throws ResourceUnavailableException {
+                throws SpiUnavailableException, ResourceUnavailableException {
             List<IkePayload> payloads =
                     getChildCreatePayloads(
                             IkeSaPayload.createChildSaRequestPayload(
                                     new ChildSaProposal[] {currentProposal},
-                                    ipSecManager,
+                                    ipSecSpiGenerator,
                                     localAddress),
                             currentLocalTs,
                             currentRemoteTs,
@@ -1823,7 +1832,7 @@
 
         /** Create payload list as a rekey Child Session response. */
         public static List<IkePayload> getRekeyChildCreateRespPayloads(
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress localAddress,
                 byte proposalNumber,
                 ChildSaProposal currentProposal,
@@ -1831,11 +1840,14 @@
                 IkeTrafficSelector[] currentRemoteTs,
                 int localSpi,
                 boolean isTransport)
-                throws ResourceUnavailableException {
+                throws SpiUnavailableException, ResourceUnavailableException {
             List<IkePayload> payloads =
                     getChildCreatePayloads(
                             IkeSaPayload.createChildSaResponsePayload(
-                                    proposalNumber, currentProposal, ipSecManager, localAddress),
+                                    proposalNumber,
+                                    currentProposal,
+                                    ipSecSpiGenerator,
+                                    localAddress),
                             currentRemoteTs /*initTs*/,
                             currentLocalTs /*respTs*/,
                             isTransport,
@@ -1887,7 +1899,7 @@
                 @ExchangeType int exchangeType,
                 @ExchangeType int expectedExchangeType,
                 boolean expectTransport,
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress remoteAddress) {
 
             return validateAndNegotiateChild(
@@ -1897,7 +1909,7 @@
                     expectedExchangeType,
                     true /*isLocalInit*/,
                     expectTransport,
-                    ipSecManager,
+                    ipSecSpiGenerator,
                     remoteAddress);
         }
 
@@ -1911,7 +1923,7 @@
                 @ExchangeType int exchangeType,
                 @ExchangeType int expectedExchangeType,
                 boolean expectTransport,
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress remoteAddress) {
 
             // It is guaranteed that a Rekey-Notify Payload with remote SPI of current Child SA is
@@ -1923,7 +1935,7 @@
                     expectedExchangeType,
                     false /*isLocalInit*/,
                     expectTransport,
-                    ipSecManager,
+                    ipSecSpiGenerator,
                     remoteAddress);
         }
 
@@ -1938,7 +1950,7 @@
                 @ExchangeType int expectedExchangeType,
                 boolean expectTransport,
                 ChildSaRecord expectedChildRecord,
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress remoteAddress) {
             // Validate rest of payloads and negotiate Child SA.
             CreateChildResult childResult =
@@ -1949,7 +1961,7 @@
                             expectedExchangeType,
                             true /*isLocalInit*/,
                             expectTransport,
-                            ipSecManager,
+                            ipSecSpiGenerator,
                             remoteAddress);
 
             // TODO: Validate new Child SA does not have different Traffic Selectors
@@ -1987,7 +1999,7 @@
                 @ExchangeType int expectedExchangeType,
                 boolean isLocalInit,
                 boolean expectTransport,
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress remoteAddress) {
             List<IkePayload> inboundPayloads = isLocalInit ? respPayloads : reqPayloads;
 
@@ -2060,7 +2072,7 @@
                 // inside.
                 childProposalPair =
                         IkeSaPayload.getVerifiedNegotiatedChildProposalPair(
-                                reqSaPayload, respSaPayload, ipSecManager, remoteAddress);
+                                reqSaPayload, respSaPayload, ipSecSpiGenerator, remoteAddress);
                 ChildSaProposal saProposal = childProposalPair.second.saProposal;
 
                 validateKePayloads(inboundPayloads, isLocalInit /*isResp*/, saProposal);
diff --git a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineFactory.java b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineFactory.java
index f8059bd..32a481c 100644
--- a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineFactory.java
+++ b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineFactory.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 
 import java.util.concurrent.Executor;
 
@@ -40,6 +41,7 @@
             Context context,
             int ikeSessionUniqueId,
             AlarmManager alarmManager,
+            IpSecSpiGenerator ipSecSpiGenerator,
             ChildSessionParams sessionParams,
             Executor userCbExecutor,
             ChildSessionCallback userCallbacks,
@@ -49,6 +51,7 @@
                 context,
                 ikeSessionUniqueId,
                 alarmManager,
+                ipSecSpiGenerator,
                 sessionParams,
                 userCbExecutor,
                 userCallbacks,
@@ -72,6 +75,7 @@
                 Context context,
                 int ikeSessionUniqueId,
                 AlarmManager alarmManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 ChildSessionParams sessionParams,
                 Executor userCbExecutor,
                 ChildSessionCallback userCallbacks,
@@ -89,6 +93,7 @@
                 Context context,
                 int ikeSessionUniqueId,
                 AlarmManager alarmManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 ChildSessionParams sessionParams,
                 Executor userCbExecutor,
                 ChildSessionCallback userCallbacks,
@@ -100,6 +105,7 @@
                             ikeSessionUniqueId,
                             alarmManager,
                             (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE),
+                            ipSecSpiGenerator,
                             sessionParams,
                             userCbExecutor,
                             userCallbacks,
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
index 694a1a4..d4257bc 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -53,6 +53,7 @@
 import android.content.IntentFilter;
 import android.net.IpSecManager;
 import android.net.IpSecManager.ResourceUnavailableException;
+import android.net.IpSecManager.SpiUnavailableException;
 import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.Network;
 import android.net.ipsec.ike.ChildSessionCallback;
@@ -128,6 +129,8 @@
 import com.android.internal.net.ipsec.ike.message.IkeVendorPayload;
 import com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver;
 import com.android.internal.net.ipsec.ike.utils.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 import com.android.internal.net.ipsec.ike.utils.RandomnessFactory;
 import com.android.internal.net.ipsec.ike.utils.Retransmitter;
 import com.android.internal.util.State;
@@ -354,6 +357,17 @@
     /** Package private */
     @VisibleForTesting final RandomnessFactory mRandomFactory;
 
+    /**
+     * mIkeSpiGenerator will be used by all IKE SA creations in this IKE Session to avoid SPI
+     * collision in test mode.
+     */
+    private final IkeSpiGenerator mIkeSpiGenerator;
+    /**
+     * mIpSecSpiGenerator will be shared by all Child Sessions under this IKE Session to avoid SPI
+     * collision in test mode.
+     */
+    private final IpSecSpiGenerator mIpSecSpiGenerator;
+
     @VisibleForTesting
     @GuardedBy("mChildCbToSessions")
     final HashMap<ChildSessionCallback, ChildSessionStateMachine> mChildCbToSessions =
@@ -503,6 +517,8 @@
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
 
         mRandomFactory = new RandomnessFactory(mContext, mIkeSessionParams.getNetwork());
+        mIkeSpiGenerator = new IkeSpiGenerator(mRandomFactory);
+        mIpSecSpiGenerator = new IpSecSpiGenerator(mIpSecManager, mRandomFactory);
 
         mIkeSessionCallback = ikeSessionCallback;
 
@@ -594,6 +610,7 @@
                             mContext,
                             mIkeSessionId,
                             mAlarmManager,
+                            mIpSecSpiGenerator,
                             childParams,
                             mUserCbExecutor,
                             callbacks,
@@ -2001,7 +2018,10 @@
 
                         List<IkePayload> payloadList =
                                 CreateIkeSaHelper.getRekeyIkeSaResponsePayloads(
-                                        respProposalNumber, mSaProposal, mLocalAddress);
+                                        respProposalNumber,
+                                        mSaProposal,
+                                        mIkeSpiGenerator,
+                                        mLocalAddress);
 
                         // Build IKE header
                         IkeHeader ikeHeader =
@@ -2725,8 +2745,8 @@
 
         private IkeMessage buildIkeInitReq() throws IOException {
             // Generate IKE SPI
-            mLocalIkeSpiResource =
-                    IkeSecurityParameterIndex.allocateSecurityParameterIndex(mLocalAddress);
+            mLocalIkeSpiResource = mIkeSpiGenerator.allocateSpi(mLocalAddress);
+
             long initSpi = mLocalIkeSpiResource.getSpi();
             long respSpi = 0;
 
@@ -2778,8 +2798,7 @@
                 throws IkeProtocolException, IOException {
             IkeHeader respIkeHeader = respMsg.ikeHeader;
             mRemoteIkeSpiResource =
-                    IkeSecurityParameterIndex.allocateSecurityParameterIndex(
-                            mRemoteAddress, respIkeHeader.ikeResponderSpi);
+                    mIkeSpiGenerator.allocateSpi(mRemoteAddress, respIkeHeader.ikeResponderSpi);
 
             int exchangeType = respIkeHeader.exchangeType;
             if (exchangeType != IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT) {
@@ -2872,7 +2891,7 @@
                     reqMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class);
             mSaProposal =
                     IkeSaPayload.getVerifiedNegotiatedIkeProposalPair(
-                                    reqSaPayload, respSaPayload, mRemoteAddress)
+                                    reqSaPayload, respSaPayload, mIkeSpiGenerator, mRemoteAddress)
                             .second
                             .saProposal;
 
@@ -3157,7 +3176,7 @@
                 mUseEap =
                         (IkeSessionParams.IKE_AUTH_METHOD_EAP
                                 == mIkeSessionParams.getLocalAuthConfig().mAuthMethod);
-            } catch (ResourceUnavailableException e) {
+            } catch (SpiUnavailableException | ResourceUnavailableException e) {
                 // Handle IPsec SPI assigning failure.
                 handleIkeFatalError(e);
             }
@@ -3217,7 +3236,8 @@
             handleIkeFatalError(ikeException);
         }
 
-        private IkeMessage buildIkeAuthReq() throws ResourceUnavailableException {
+        private IkeMessage buildIkeAuthReq()
+                throws SpiUnavailableException, ResourceUnavailableException {
             List<IkePayload> payloadList = new LinkedList<>();
 
             // Build Identification payloads
@@ -3283,7 +3303,7 @@
 
             payloadList.addAll(
                     CreateChildSaHelper.getInitChildCreateReqPayloads(
-                            mIpSecManager,
+                            mIpSecSpiGenerator,
                             mLocalAddress,
                             mFirstChildSessionParams,
                             true /*isFirstChildSa*/));
@@ -3486,6 +3506,7 @@
             IkeSessionParams.IkeAuthEapConfig ikeAuthEapConfig =
                     (IkeSessionParams.IkeAuthEapConfig) mIkeSessionParams.getLocalAuthConfig();
 
+            // TODO(b/148689509): Pass in deterministic random when test mode is enabled
             mEapAuthenticator =
                     mEapAuthenticatorFactory.newEapAuthenticator(
                             getHandler().getLooper(),
@@ -3881,7 +3902,7 @@
                 // Throw exception or return valid negotiated proposal with allocated SPIs
                 negotiatedProposals =
                         IkeSaPayload.getVerifiedNegotiatedIkeProposalPair(
-                                reqSaPayload, respSaPayload, mRemoteAddress);
+                                reqSaPayload, respSaPayload, mIkeSpiGenerator, mRemoteAddress);
                 IkeProposal reqProposal = negotiatedProposals.first;
                 IkeProposal respProposal = negotiatedProposals.second;
 
@@ -3972,7 +3993,8 @@
             // No need to allocate SPIs; they will be allocated as part of the
             // getRekeyIkeSaRequestPayloads
             List<IkePayload> payloadList =
-                    CreateIkeSaHelper.getRekeyIkeSaRequestPayloads(saProposals, mLocalAddress);
+                    CreateIkeSaHelper.getRekeyIkeSaRequestPayloads(
+                            saProposals, mIkeSpiGenerator, mLocalAddress);
 
             // Build IKE header
             IkeHeader ikeHeader =
@@ -4655,7 +4677,8 @@
         }
 
         public static List<IkePayload> getRekeyIkeSaRequestPayloads(
-                IkeSaProposal[] saProposals, InetAddress localAddr) throws IOException {
+                IkeSaProposal[] saProposals, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddr)
+                throws IOException {
             if (localAddr == null) {
                 throw new IllegalArgumentException("Local address was null for rekey");
             }
@@ -4666,11 +4689,15 @@
 
             return getCreateIkeSaPayloads(
                     selectedDhGroup,
-                    IkeSaPayload.createRekeyIkeSaRequestPayload(saProposals, localAddr));
+                    IkeSaPayload.createRekeyIkeSaRequestPayload(
+                            saProposals, ikeSpiGenerator, localAddr));
         }
 
         public static List<IkePayload> getRekeyIkeSaResponsePayloads(
-                byte respProposalNumber, IkeSaProposal saProposal, InetAddress localAddr)
+                byte respProposalNumber,
+                IkeSaProposal saProposal,
+                IkeSpiGenerator ikeSpiGenerator,
+                InetAddress localAddr)
                 throws IOException {
             if (localAddr == null) {
                 throw new IllegalArgumentException("Local address was null for rekey");
@@ -4681,7 +4708,7 @@
             return getCreateIkeSaPayloads(
                     selectedDhGroup,
                     IkeSaPayload.createRekeyIkeSaResponsePayload(
-                            respProposalNumber, saProposal, localAddr));
+                            respProposalNumber, saProposal, ikeSpiGenerator, localAddr));
         }
 
         /**
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java
index 532ee7f..d2dd394 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java
@@ -24,7 +24,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.net.IpSecManager;
 import android.net.IpSecManager.ResourceUnavailableException;
 import android.net.IpSecManager.SecurityParameterIndex;
 import android.net.IpSecManager.SpiUnavailableException;
@@ -39,6 +38,8 @@
 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.utils.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -108,7 +109,11 @@
     /** Package private constructor for building a request for IKE SA initial creation or rekey */
     @VisibleForTesting
     IkeSaPayload(
-            boolean isResp, byte spiSize, IkeSaProposal[] saProposals, InetAddress localAddress)
+            boolean isResp,
+            byte spiSize,
+            IkeSaProposal[] saProposals,
+            IkeSpiGenerator ikeSpiGenerator,
+            InetAddress localAddress)
             throws IOException {
         this(isResp, spiSize, localAddress);
 
@@ -120,7 +125,11 @@
             // Proposal number must start from 1.
             proposalList.add(
                     IkeProposal.createIkeProposal(
-                            (byte) (i + 1) /*number*/, spiSize, saProposals[i], localAddress));
+                            (byte) (i + 1) /* number */,
+                            spiSize,
+                            saProposals[i],
+                            ikeSpiGenerator,
+                            localAddress));
         }
 
         getIkeLog().d(TAG, "Generate " + toString());
@@ -133,13 +142,18 @@
             byte spiSize,
             byte proposalNumber,
             IkeSaProposal saProposal,
+            IkeSpiGenerator ikeSpiGenerator,
             InetAddress localAddress)
             throws IOException {
         this(isResp, spiSize, localAddress);
 
         proposalList.add(
                 IkeProposal.createIkeProposal(
-                        proposalNumber /*number*/, spiSize, saProposal, localAddress));
+                        proposalNumber /* number */,
+                        spiSize,
+                        saProposal,
+                        ikeSpiGenerator,
+                        localAddress));
 
         getIkeLog().d(TAG, "Generate " + toString());
     }
@@ -162,9 +176,12 @@
      * negotiation.
      */
     @VisibleForTesting
-    IkeSaPayload(ChildSaProposal[] saProposals, IpSecManager ipSecManager, InetAddress localAddress)
-            throws ResourceUnavailableException {
-        this(false /*isResp*/, ipSecManager, localAddress);
+    IkeSaPayload(
+            ChildSaProposal[] saProposals,
+            IpSecSpiGenerator ipSecSpiGenerator,
+            InetAddress localAddress)
+            throws SpiUnavailableException, ResourceUnavailableException {
+        this(false /* isResp */, ipSecSpiGenerator, localAddress);
 
         if (saProposals.length < 1) {
             throw new IllegalArgumentException("Invalid SA payload.");
@@ -176,7 +193,10 @@
             // Proposal number must start from 1.
             proposalList.add(
                     ChildProposal.createChildProposal(
-                            (byte) (i + 1) /*number*/, saProposals[i], ipSecManager, localAddress));
+                            (byte) (i + 1) /* number */,
+                            saProposals[i],
+                            ipSecSpiGenerator,
+                            localAddress));
         }
 
         getIkeLog().d(TAG, "Generate " + toString());
@@ -190,20 +210,21 @@
     IkeSaPayload(
             byte proposalNumber,
             ChildSaProposal saProposal,
-            IpSecManager ipSecManager,
+            IpSecSpiGenerator ipSecSpiGenerator,
             InetAddress localAddress)
-            throws ResourceUnavailableException {
-        this(true /*isResp*/, ipSecManager, localAddress);
+            throws SpiUnavailableException, ResourceUnavailableException {
+        this(true /* isResp */, ipSecSpiGenerator, localAddress);
 
         proposalList.add(
                 ChildProposal.createChildProposal(
-                        proposalNumber /*number*/, saProposal, ipSecManager, localAddress));
+                        proposalNumber /* number */, saProposal, ipSecSpiGenerator, localAddress));
 
         getIkeLog().d(TAG, "Generate " + toString());
     }
 
     /** Constructor for building an outbound SA Payload for Child SA negotiation. */
-    private IkeSaPayload(boolean isResp, IpSecManager ipSecManager, InetAddress localAddress) {
+    private IkeSaPayload(
+            boolean isResp, IpSecSpiGenerator ipSecSpiGenerator, InetAddress localAddress) {
         super(IkePayload.PAYLOAD_TYPE_SA, false);
 
         isSaResponse = isResp;
@@ -224,18 +245,26 @@
      */
     public static IkeSaPayload createInitialIkeSaPayload(IkeSaProposal[] saProposals)
             throws IOException {
-        return new IkeSaPayload(false /*isResp*/, SPI_LEN_NOT_INCLUDED, saProposals, null);
+        return new IkeSaPayload(
+                false /* isResp */,
+                SPI_LEN_NOT_INCLUDED,
+                saProposals,
+                null /* ikeSpiGenerator unused */,
+                null /* localAddress unused */);
     }
 
     /**
      * Construct an instance of IkeSaPayload for building an outbound request for Rekey IKE.
      *
      * @param saProposals the array of all IKE SA Proposals.
+     * @param ikeSpiGenerator the IKE SPI generator.
      * @param localAddress the local address assigned on-device.
      */
     public static IkeSaPayload createRekeyIkeSaRequestPayload(
-            IkeSaProposal[] saProposals, InetAddress localAddress) throws IOException {
-        return new IkeSaPayload(false /*isResp*/, SPI_LEN_IKE, saProposals, localAddress);
+            IkeSaProposal[] saProposals, IkeSpiGenerator ikeSpiGenerator, InetAddress localAddress)
+            throws IOException {
+        return new IkeSaPayload(
+                false /* isResp */, SPI_LEN_IKE, saProposals, ikeSpiGenerator, localAddress);
     }
 
     /**
@@ -243,13 +272,22 @@
      *
      * @param respProposalNumber the selected proposal's number.
      * @param saProposal the expected selected IKE SA Proposal.
+     * @param ikeSpiGenerator the IKE SPI generator.
      * @param localAddress the local address assigned on-device.
      */
     public static IkeSaPayload createRekeyIkeSaResponsePayload(
-            byte respProposalNumber, IkeSaProposal saProposal, InetAddress localAddress)
+            byte respProposalNumber,
+            IkeSaProposal saProposal,
+            IkeSpiGenerator ikeSpiGenerator,
+            InetAddress localAddress)
             throws IOException {
         return new IkeSaPayload(
-                true /*isResp*/, SPI_LEN_IKE, respProposalNumber, saProposal, localAddress);
+                true /* isResp */,
+                SPI_LEN_IKE,
+                respProposalNumber,
+                saProposal,
+                ikeSpiGenerator,
+                localAddress);
     }
 
     /**
@@ -257,15 +295,17 @@
      * negotiation.
      *
      * @param saProposals the array of all Child SA Proposals.
-     * @param ipSecManager the IpSecManager for generating IPsec SPIs.
+     * @param ipSecSpiGenerator the IPsec SPI generator.
      * @param localAddress the local address assigned on-device.
      * @throws ResourceUnavailableException if too many SPIs are currently allocated for this user.
      */
     public static IkeSaPayload createChildSaRequestPayload(
-            ChildSaProposal[] saProposals, IpSecManager ipSecManager, InetAddress localAddress)
-            throws ResourceUnavailableException {
+            ChildSaProposal[] saProposals,
+            IpSecSpiGenerator ipSecSpiGenerator,
+            InetAddress localAddress)
+            throws SpiUnavailableException, ResourceUnavailableException {
 
-        return new IkeSaPayload(saProposals, ipSecManager, localAddress);
+        return new IkeSaPayload(saProposals, ipSecSpiGenerator, localAddress);
     }
 
     /**
@@ -274,16 +314,16 @@
      *
      * @param respProposalNumber the selected proposal's number.
      * @param saProposal the expected selected Child SA Proposal.
-     * @param ipSecManager the IpSecManager for generating IPsec SPIs.
+     * @param ipSecSpiGenerator the IPsec SPI generator.
      * @param localAddress the local address assigned on-device.
      */
     public static IkeSaPayload createChildSaResponsePayload(
             byte respProposalNumber,
             ChildSaProposal saProposal,
-            IpSecManager ipSecManager,
+            IpSecSpiGenerator ipSecSpiGenerator,
             InetAddress localAddress)
-            throws ResourceUnavailableException {
-        return new IkeSaPayload(respProposalNumber, saProposal, ipSecManager, localAddress);
+            throws SpiUnavailableException, ResourceUnavailableException {
+        return new IkeSaPayload(respProposalNumber, saProposal, ipSecSpiGenerator, localAddress);
     }
 
     /**
@@ -335,7 +375,10 @@
      *     the request SA Payload.
      */
     public static Pair<IkeProposal, IkeProposal> getVerifiedNegotiatedIkeProposalPair(
-            IkeSaPayload reqSaPayload, IkeSaPayload respSaPayload, InetAddress remoteAddress)
+            IkeSaPayload reqSaPayload,
+            IkeSaPayload respSaPayload,
+            IkeSpiGenerator ikeSpiGenerator,
+            InetAddress remoteAddress)
             throws NoValidProposalChosenException, IOException {
         Pair<Proposal, Proposal> proposalPair =
                 getVerifiedNegotiatedProposalPair(reqSaPayload, respSaPayload);
@@ -346,12 +389,12 @@
             // Allocate initiator's inbound SPI as needed for remotely initiated IKE SA creation
             if (reqProposal.spiSize != SPI_NOT_INCLUDED
                     && reqProposal.getIkeSpiResource() == null) {
-                reqProposal.allocateResourceForRemoteIkeSpi(remoteAddress);
+                reqProposal.allocateResourceForRemoteIkeSpi(ikeSpiGenerator, remoteAddress);
             }
             // Allocate responder's inbound SPI as needed for locally initiated IKE SA creation
             if (respProposal.spiSize != SPI_NOT_INCLUDED
                     && respProposal.getIkeSpiResource() == null) {
-                respProposal.allocateResourceForRemoteIkeSpi(remoteAddress);
+                respProposal.allocateResourceForRemoteIkeSpi(ikeSpiGenerator, remoteAddress);
             }
 
             return new Pair(reqProposal, respProposal);
@@ -384,7 +427,7 @@
      *
      * @param reqSaPayload the request payload.
      * @param respSaPayload the response payload.
-     * @param ipSecManager the IpSecManager to allocate SPI resource for the Proposal in this
+     * @param ipSecSpiGenerator the SPI generator to allocate SPI resource for the Proposal in this
      *     inbound SA Payload.
      * @param remoteAddress the address of the remote IKE peer.
      * @return the Pair of selected ChildProposal in the locally generated request and the
@@ -397,7 +440,7 @@
     public static Pair<ChildProposal, ChildProposal> getVerifiedNegotiatedChildProposalPair(
             IkeSaPayload reqSaPayload,
             IkeSaPayload respSaPayload,
-            IpSecManager ipSecManager,
+            IpSecSpiGenerator ipSecSpiGenerator,
             InetAddress remoteAddress)
             throws NoValidProposalChosenException, ResourceUnavailableException,
                     SpiUnavailableException {
@@ -409,11 +452,11 @@
         try {
             // Allocate initiator's inbound SPI as needed for remotely initiated Child SA creation
             if (reqProposal.getChildSpiResource() == null) {
-                reqProposal.allocateResourceForRemoteChildSpi(ipSecManager, remoteAddress);
+                reqProposal.allocateResourceForRemoteChildSpi(ipSecSpiGenerator, remoteAddress);
             }
             // Allocate responder's inbound SPI as needed for locally initiated Child SA creation
             if (respProposal.getChildSpiResource() == null) {
-                respProposal.allocateResourceForRemoteChildSpi(ipSecManager, remoteAddress);
+                respProposal.allocateResourceForRemoteChildSpi(ipSecSpiGenerator, remoteAddress);
             }
 
             return new Pair(reqProposal, respProposal);
@@ -719,7 +762,7 @@
                     PROTOCOL_ID_IKE,
                     spiSize,
                     ikeSpiResource == null ? SPI_NOT_INCLUDED : ikeSpiResource.getSpi(),
-                    false /*hasUnrecognizedTransform*/);
+                    false /* hasUnrecognizedTransform */);
             mIkeSpiResource = ikeSpiResource;
             this.saProposal = saProposal;
         }
@@ -731,14 +774,17 @@
          */
         @VisibleForTesting
         static IkeProposal createIkeProposal(
-                byte number, byte spiSize, IkeSaProposal saProposal, InetAddress localAddress)
+                byte number,
+                byte spiSize,
+                IkeSaProposal saProposal,
+                IkeSpiGenerator ikeSpiGenerator,
+                InetAddress localAddress)
                 throws IOException {
             // IKE_INIT uses SPI_LEN_NOT_INCLUDED, while rekeys use SPI_LEN_IKE
             IkeSecurityParameterIndex spiResource =
                     (spiSize == SPI_LEN_NOT_INCLUDED
                             ? null
-                            : IkeSecurityParameterIndex.allocateSecurityParameterIndex(
-                                    localAddress));
+                            : ikeSpiGenerator.allocateSpi(localAddress));
             return new IkeProposal(number, spiSize, spiResource, saProposal);
         }
 
@@ -754,9 +800,9 @@
          * Package private method for allocating SPI resource for a validated remotely generated IKE
          * SA proposal.
          */
-        void allocateResourceForRemoteIkeSpi(InetAddress remoteAddress) throws IOException {
-            mIkeSpiResource =
-                    IkeSecurityParameterIndex.allocateSecurityParameterIndex(remoteAddress, spi);
+        void allocateResourceForRemoteIkeSpi(
+                IkeSpiGenerator ikeSpiGenerator, InetAddress remoteAddress) throws IOException {
+            mIkeSpiResource = ikeSpiGenerator.allocateSpi(remoteAddress, spi);
         }
 
         @Override
@@ -807,7 +853,7 @@
                     PROTOCOL_ID_ESP,
                     SPI_LEN_IPSEC,
                     (long) childSpiResource.getSpi(),
-                    false /*hasUnrecognizedTransform*/);
+                    false /* hasUnrecognizedTransform */);
             mChildSpiResource = childSpiResource;
             this.saProposal = saProposal;
         }
@@ -821,11 +867,11 @@
         static ChildProposal createChildProposal(
                 byte number,
                 ChildSaProposal saProposal,
-                IpSecManager ipSecManager,
+                IpSecSpiGenerator ipSecSpiGenerator,
                 InetAddress localAddress)
-                throws ResourceUnavailableException {
+                throws SpiUnavailableException, ResourceUnavailableException {
             return new ChildProposal(
-                    number, ipSecManager.allocateSecurityParameterIndex(localAddress), saProposal);
+                    number, ipSecSpiGenerator.allocateSpi(localAddress), saProposal);
         }
 
         /** Package private method for releasing SPI resource in this unselected Proposal. */
@@ -840,10 +886,10 @@
          * Package private method for allocating SPI resource for a validated remotely generated
          * Child SA proposal.
          */
-        void allocateResourceForRemoteChildSpi(IpSecManager ipSecManager, InetAddress remoteAddress)
+        void allocateResourceForRemoteChildSpi(
+                IpSecSpiGenerator ipSecSpiGenerator, InetAddress remoteAddress)
                 throws ResourceUnavailableException, SpiUnavailableException {
-            mChildSpiResource =
-                    ipSecManager.allocateSecurityParameterIndex(remoteAddress, (int) spi);
+            mChildSpiResource = ipSecSpiGenerator.allocateSpi(remoteAddress, (int) spi);
         }
 
         @Override
diff --git a/src/java/com/android/internal/net/ipsec/ike/utils/IkeSecurityParameterIndex.java b/src/java/com/android/internal/net/ipsec/ike/utils/IkeSecurityParameterIndex.java
index 7f72e11..c5863be 100644
--- a/src/java/com/android/internal/net/ipsec/ike/utils/IkeSecurityParameterIndex.java
+++ b/src/java/com/android/internal/net/ipsec/ike/utils/IkeSecurityParameterIndex.java
@@ -13,15 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.internal.net.ipsec.ike.utils;
 
 import android.util.CloseGuard;
 import android.util.Pair;
 
-import java.io.IOException;
 import java.net.InetAddress;
-import java.security.SecureRandom;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -40,62 +37,22 @@
  * <p>This class follows the pattern of {@link IpSecManager.SecurityParameterIndex}.
  */
 public final class IkeSecurityParameterIndex implements AutoCloseable {
-    // Remember assigned IKE SPIs to avoid SPI collision.
-    private static final Set<Pair<InetAddress, Long>> sAssignedIkeSpis = new HashSet<>();
-    private static final int MAX_ASSIGN_IKE_SPI_ATTEMPTS = 100;
-    private static final SecureRandom IKE_SPI_RANDOM = new SecureRandom();
+    // Package private Set to remember assigned IKE SPIs to avoid SPI collision. MUST be
+    // accessed only by IkeSecurityParameterIndex and IkeSpiGenerator
+    static final Set<Pair<InetAddress, Long>> sAssignedIkeSpis = new HashSet<>();
 
     private final InetAddress mSourceAddress;
     private final long mSpi;
     private final CloseGuard mCloseGuard = new CloseGuard();
 
-    private IkeSecurityParameterIndex(InetAddress sourceAddress, long spi) {
+    // Package private constructor that MUST only be called from IkeSpiGenerator
+    IkeSecurityParameterIndex(InetAddress sourceAddress, long spi) {
         mSourceAddress = sourceAddress;
         mSpi = spi;
         mCloseGuard.open("close");
     }
 
     /**
-     * Get a new IKE SPI and maintain the reservation.
-     *
-     * @return an instance of IkeSecurityParameterIndex.
-     */
-    public static IkeSecurityParameterIndex allocateSecurityParameterIndex(
-            InetAddress sourceAddress) throws IOException {
-        // TODO: Create specific Exception for SPI assigning error.
-
-        for (int i = 0; i < MAX_ASSIGN_IKE_SPI_ATTEMPTS; i++) {
-            long spi = IKE_SPI_RANDOM.nextLong();
-            // Zero value can only be used in the IKE responder SPI field of an IKE INIT
-            // request.
-            if (spi != 0L
-                    && sAssignedIkeSpis.add(new Pair<InetAddress, Long>(sourceAddress, spi))) {
-                return new IkeSecurityParameterIndex(sourceAddress, spi);
-            }
-        }
-
-        throw new IOException("Failed to generate IKE SPI.");
-    }
-
-    /**
-     * Get a new IKE SPI and maintain the reservation.
-     *
-     * @return an instance of IkeSecurityParameterIndex.
-     */
-    public static IkeSecurityParameterIndex allocateSecurityParameterIndex(
-            InetAddress sourceAddress, long requestedSpi) throws IOException {
-        if (sAssignedIkeSpis.add(new Pair<InetAddress, Long>(sourceAddress, requestedSpi))) {
-            return new IkeSecurityParameterIndex(sourceAddress, requestedSpi);
-        }
-
-        throw new IOException(
-                "Failed to generate IKE SPI for "
-                        + requestedSpi
-                        + " with source address "
-                        + sourceAddress.getHostAddress());
-    }
-
-    /**
      * Get the underlying SPI held by this object.
      *
      * @return the underlying IKE SPI.
diff --git a/src/java/com/android/internal/net/ipsec/ike/utils/IkeSpiGenerator.java b/src/java/com/android/internal/net/ipsec/ike/utils/IkeSpiGenerator.java
new file mode 100644
index 0000000..902d304
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/utils/IkeSpiGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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.utils;
+
+import android.util.Pair;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.SecureRandom;
+
+/** This class provides method to allocate IKE SPI */
+public class IkeSpiGenerator {
+    private final SecureRandom mRandom;
+
+    /**
+     * Constructor of IkeSpiGenerator
+     *
+     * @param secureRandom the seed to generate SPI
+     */
+    public IkeSpiGenerator(RandomnessFactory randomnessFactory) {
+        SecureRandom random = randomnessFactory.getRandom();
+        mRandom = random == null ? new SecureRandom() : random;
+    }
+
+    /**
+     * Get a new IKE SPI and maintain the reservation.
+     *
+     * <p>If this instance is constructed by a spiGenerator, this method will try to allocate SPI
+     * once for the return value of spiGenerator#nextLong. Otherwise, this methods will try multiple
+     * times until it finds an avalaible IKE SPI.
+     *
+     * @return an instance of IkeSecurityParameterIndex.
+     */
+    public IkeSecurityParameterIndex allocateSpi(InetAddress sourceAddress) throws IOException {
+        long spi;
+        do {
+            spi = mRandom.nextLong();
+        } while (spi == 0L
+                || !IkeSecurityParameterIndex.sAssignedIkeSpis.add(
+                        new Pair<InetAddress, Long>(sourceAddress, spi)));
+        return new IkeSecurityParameterIndex(sourceAddress, spi);
+    }
+
+    /**
+     * Get a new IKE SPI and maintain the reservation.
+     *
+     * @return an instance of IkeSecurityParameterIndex.
+     */
+    public IkeSecurityParameterIndex allocateSpi(InetAddress sourceAddress, long requestedSpi)
+            throws IOException {
+        if (IkeSecurityParameterIndex.sAssignedIkeSpis.add(
+                new Pair<InetAddress, Long>(sourceAddress, requestedSpi))) {
+            return new IkeSecurityParameterIndex(sourceAddress, requestedSpi);
+        }
+
+        throw new IOException(
+                "Failed to generate IKE SPI for "
+                        + requestedSpi
+                        + " with source address "
+                        + sourceAddress.getHostAddress());
+    }
+}
diff --git a/src/java/com/android/internal/net/ipsec/ike/utils/IpSecSpiGenerator.java b/src/java/com/android/internal/net/ipsec/ike/utils/IpSecSpiGenerator.java
new file mode 100644
index 0000000..8802898
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/utils/IpSecSpiGenerator.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.utils;
+
+import android.annotation.NonNull;
+import android.net.IpSecManager;
+import android.net.IpSecManager.ResourceUnavailableException;
+import android.net.IpSecManager.SecurityParameterIndex;
+import android.net.IpSecManager.SpiUnavailableException;
+
+import java.net.InetAddress;
+import java.security.SecureRandom;
+import java.util.Objects;
+
+/** This class provides method to allocate IPsec SPI */
+public class IpSecSpiGenerator {
+    private final IpSecManager mIpSecManager;
+    private final SecureRandom mRandom;
+
+    /**
+     * Constructor of IpSecSpiGenerator
+     *
+     * @param secureRandom the seed to generate SPI
+     */
+    public IpSecSpiGenerator(
+            @NonNull IpSecManager ipSecManager, RandomnessFactory randomnessFactory) {
+        Objects.requireNonNull(ipSecManager);
+        mIpSecManager = ipSecManager;
+        mRandom = randomnessFactory.getRandom();
+    }
+
+    /**
+     * Get a new IPsec SPI and maintain the reservation.
+     *
+     * <p>If this instance is constructed by a spiGenerator, this method will try to allocate SPI
+     * once for the return value of spiGenerator#nextInt. Otherwise the SPI will be allocated by the
+     * Kernel.
+     *
+     * @return an instance of SecurityParameterIndex.
+     */
+    public SecurityParameterIndex allocateSpi(InetAddress sourceAddress)
+            throws SpiUnavailableException, ResourceUnavailableException {
+        if (mRandom == null) {
+            return mIpSecManager.allocateSecurityParameterIndex(sourceAddress);
+        } else {
+            return mIpSecManager.allocateSecurityParameterIndex(sourceAddress, mRandom.nextInt());
+        }
+    }
+
+    /**
+     * Get a new IPsec SPI and maintain the reservation.
+     *
+     * @return an instance of SecurityParameterIndex.
+     */
+    public SecurityParameterIndex allocateSpi(InetAddress sourceAddress, int requestedSpi)
+            throws SpiUnavailableException, ResourceUnavailableException {
+        return mIpSecManager.allocateSecurityParameterIndex(sourceAddress, requestedSpi);
+    }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/TestUtils.java b/tests/iketests/src/java/com/android/internal/net/TestUtils.java
index 6dd5ce5..0b344cd 100644
--- a/tests/iketests/src/java/com/android/internal/net/TestUtils.java
+++ b/tests/iketests/src/java/com/android/internal/net/TestUtils.java
@@ -19,13 +19,16 @@
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
+import com.android.internal.net.ipsec.ike.utils.RandomnessFactory;
 import com.android.internal.net.utils.Log;
 
 import java.nio.ByteBuffer;
 
-/** TestUtils provides utility methods for parsing Hex String and constructing testing Logger. */
+/** TestUtils provides utility methods to facilitate IKE unit tests */
 public class TestUtils {
     public static byte[] hexStringToByteArray(String hexString) {
         int len = hexString.length();
@@ -110,4 +113,10 @@
 
         return spyLog;
     }
+
+    public static RandomnessFactory createMockRandomFactory() {
+        RandomnessFactory rFactory = mock(RandomnessFactory.class);
+        doReturn(null).when(rFactory).getRandom();
+        return rFactory;
+    }
 }
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
index d0fccfd..7ce4c3f 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
@@ -21,6 +21,7 @@
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
 import static android.system.OsConstants.AF_INET;
 
+import static com.android.internal.net.TestUtils.createMockRandomFactory;
 import static com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CMD_FORCE_TRANSITION;
 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD;
 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
@@ -117,6 +118,7 @@
 import com.android.internal.net.ipsec.ike.message.IkeTestUtils;
 import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
 import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 import com.android.internal.net.utils.Log;
 import com.android.server.IpSecService;
 
@@ -186,6 +188,7 @@
     private UdpEncapsulationSocket mMockUdpEncapSocket;
 
     private TestLooper mLooper;
+    private IpSecSpiGenerator mIpSecSpiGenerator;
     private ChildSessionStateMachine mChildSessionStateMachine;
 
     private List<IkePayload> mFirstSaReqPayloads = new LinkedList<>();
@@ -250,6 +253,8 @@
         mMockIpSecManager = new IpSecManager(mContext, mMockIpSecService);
         mMockUdpEncapSocket = mock(UdpEncapsulationSocket.class);
 
+        mIpSecSpiGenerator = new IpSecSpiGenerator(mMockIpSecManager, createMockRandomFactory());
+
         mMockNegotiatedProposal = mock(ChildSaProposal.class);
 
         mSpyUserCbExecutor =
@@ -270,6 +275,7 @@
                         IKE_SESSION_UNIQUE_ID,
                         mMockAlarmManager,
                         mMockIpSecManager,
+                        mIpSecSpiGenerator,
                         mChildSessionParams,
                         mSpyUserCbExecutor,
                         mMockChildSessionCallback,
@@ -329,7 +335,7 @@
         IkeSaPayload reqSaPayload =
                 IkeSaPayload.createChildSaRequestPayload(
                         mChildSessionParams.getSaProposalsInternal(),
-                        mMockIpSecManager,
+                        mIpSecSpiGenerator,
                         LOCAL_ADDRESS);
         mFirstSaReqPayloads.add(reqSaPayload);
 
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
index a638d04..48d5f2b 100644
--- 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
@@ -27,6 +27,7 @@
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 
+import static com.android.internal.net.TestUtils.createMockRandomFactory;
 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;
@@ -151,6 +152,8 @@
 import com.android.internal.net.ipsec.ike.testmode.DeterministicSecureRandom;
 import com.android.internal.net.ipsec.ike.testutils.CertUtils;
 import com.android.internal.net.ipsec.ike.utils.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 import com.android.internal.net.ipsec.ike.utils.RandomnessFactory;
 import com.android.internal.net.ipsec.ike.utils.State;
 import com.android.internal.net.utils.Log;
@@ -280,6 +283,11 @@
 
     private static final int PAYLOAD_TYPE_UNSUPPORTED = 127;
 
+    private static final long RETRANSMIT_BACKOFF_TIMEOUT_MS = 5000L;
+
+    private static final IkeSpiGenerator IKE_SPI_GENERATOR =
+            new IkeSpiGenerator(createMockRandomFactory());
+
     private IkeUdpEncapSocket mSpyIkeUdpEncapSocket;
     private IkeUdp4Socket mSpyIkeUdp4Socket;
     private IkeUdp6Socket mSpyIkeUdp6Socket;
@@ -619,8 +627,8 @@
         Inet4Address respAddress = isLocalInit ? REMOTE_ADDRESS : LOCAL_ADDRESS;
 
         return new IkeSaRecord(
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(initAddress, initSpi),
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(respAddress, respSpi),
+                IKE_SPI_GENERATOR.allocateSpi(initAddress, initSpi),
+                IKE_SPI_GENERATOR.allocateSpi(respAddress, respSpi),
                 isLocalInit,
                 TestUtils.hexStringToByteArray(NONCE_INIT_HEX_STRING),
                 TestUtils.hexStringToByteArray(NONCE_RESP_HEX_STRING),
@@ -1173,10 +1181,8 @@
     @Test
     public void testAllocateIkeSpi() throws Exception {
         // Test randomness.
-        IkeSecurityParameterIndex ikeSpiOne =
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS);
-        IkeSecurityParameterIndex ikeSpiTwo =
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+        IkeSecurityParameterIndex ikeSpiOne = IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS);
+        IkeSecurityParameterIndex ikeSpiTwo = IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS);
 
         assertNotEquals(ikeSpiOne.getSpi(), ikeSpiTwo.getSpi());
         ikeSpiTwo.close();
@@ -1184,7 +1190,7 @@
         // Test duplicate SPIs.
         long spiValue = ikeSpiOne.getSpi();
         try {
-            IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue);
+            IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS, spiValue);
             fail("Expected to fail because duplicate SPI was assigned to the same address.");
         } catch (IOException expected) {
 
@@ -1192,7 +1198,7 @@
 
         ikeSpiOne.close();
         IkeSecurityParameterIndex ikeSpiThree =
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue);
+                IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS, spiValue);
         ikeSpiThree.close();
     }
 
@@ -1467,6 +1473,7 @@
                         eq(mSpyContext),
                         anyInt(),
                         any(AlarmManager.class),
+                        any(IpSecSpiGenerator.class),
                         eq(mChildSessionParams),
                         eq(mSpyUserCbExecutor),
                         any(ChildSessionCallback.class),
@@ -1524,6 +1531,7 @@
                         eq(mSpyContext),
                         anyInt(),
                         any(AlarmManager.class),
+                        any(IpSecSpiGenerator.class),
                         eq(mChildSessionParams),
                         eq(mSpyUserCbExecutor),
                         eq(childCallback),
@@ -2285,6 +2293,7 @@
                         eq(mSpyContext),
                         anyInt(),
                         any(AlarmManager.class),
+                        any(IpSecSpiGenerator.class),
                         eq(mChildSessionParams),
                         eq(mSpyUserCbExecutor),
                         eq(mMockChildSessionCallback),
@@ -4187,6 +4196,7 @@
                         eq(mSpyContext),
                         anyInt(),
                         any(AlarmManager.class),
+                        any(IpSecSpiGenerator.class),
                         eq(mChildSessionParams),
                         eq(mSpyUserCbExecutor),
                         eq(cb),
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java
index 0f6bc42..644ba06 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.net.ipsec.ike;
 
+import static com.android.internal.net.TestUtils.createMockRandomFactory;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -54,6 +56,7 @@
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
 import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
 import com.android.internal.net.ipsec.ike.utils.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
 import com.android.server.IpSecService;
 
 import org.junit.Before;
@@ -70,6 +73,9 @@
     private static final Inet4Address REMOTE_ADDRESS =
             (Inet4Address) (InetAddresses.parseNumericAddress("192.0.2.100"));
 
+    private static final IkeSpiGenerator IKE_SPI_GENERATOR =
+            new IkeSpiGenerator(createMockRandomFactory());
+
     private static final String PRF_KEY_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485";
     private static final String DATA_TO_SIGN_HEX_STRING = "010000000a50500d";
     private static final String CALCULATED_MAC_HEX_STRING =
@@ -175,11 +181,9 @@
         byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING);
 
         IkeSecurityParameterIndex ikeInitSpi =
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(
-                        LOCAL_ADDRESS, IKE_INIT_SPI);
+                IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS, IKE_INIT_SPI);
         IkeSecurityParameterIndex ikeRespSpi =
-                IkeSecurityParameterIndex.allocateSecurityParameterIndex(
-                        REMOTE_ADDRESS, IKE_RESP_SPI);
+                IKE_SPI_GENERATOR.allocateSpi(REMOTE_ADDRESS, IKE_RESP_SPI);
         IkeSaRecordConfig ikeSaRecordConfig =
                 new IkeSaRecordConfig(
                         ikeInitSpi,
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java
index bf21ad8..4ec6d3b 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.net.ipsec.ike.message;
 
+import static com.android.internal.net.TestUtils.createMockRandomFactory;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -60,6 +62,8 @@
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.UnrecognizedAttribute;
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.UnrecognizedTransform;
 import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.internal.net.ipsec.ike.utils.IkeSpiGenerator;
+import com.android.internal.net.ipsec.ike.utils.IpSecSpiGenerator;
 import com.android.server.IpSecService;
 
 import org.junit.Before;
@@ -160,6 +164,9 @@
     private IpSecService mMockIpSecService;
     private IpSecManager mIpSecManager;
 
+    private IkeSpiGenerator mIkeSpiGenerator;
+    private IpSecSpiGenerator mIpSecSpiGenerator;
+
     private IpSecSpiResponse mDummyIpSecSpiResponseLocalOne;
     private IpSecSpiResponse mDummyIpSecSpiResponseLocalTwo;
     private IpSecSpiResponse mDummyIpSecSpiResponseRemote;
@@ -240,6 +247,9 @@
         when(mMockIpSecService.allocateSecurityParameterIndex(
                         eq(REMOTE_ADDRESS.getHostAddress()), anyInt(), anyObject()))
                 .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_REMOTE));
+
+        mIkeSpiGenerator = new IkeSpiGenerator(createMockRandomFactory());
+        mIpSecSpiGenerator = new IpSecSpiGenerator(mIpSecManager, createMockRandomFactory());
     }
 
     // TODO: Add tearDown() to reset Proposal.sTransformDecoder and Transform.sAttributeDecoder.
@@ -665,6 +675,7 @@
                         (byte) PROPOSAL_NUMBER,
                         IkePayload.SPI_LEN_NOT_INCLUDED,
                         mIkeSaProposalOne,
+                        mIkeSpiGenerator,
                         LOCAL_ADDRESS);
 
         ByteBuffer byteBuffer = ByteBuffer.allocate(proposal.getProposalLength());
@@ -691,7 +702,7 @@
     public void testBuildOutboundIkeRekeySaResponsePayload() throws Exception {
         IkeSaPayload saPayload =
                 IkeSaPayload.createRekeyIkeSaResponsePayload(
-                        (byte) 1, mIkeSaProposalOne, LOCAL_ADDRESS);
+                        (byte) 1, mIkeSaProposalOne, mIkeSpiGenerator, LOCAL_ADDRESS);
 
         assertTrue(saPayload.isSaResponse);
         assertEquals(1, saPayload.proposalList.size());
@@ -727,7 +738,7 @@
     public void testBuildOutboundChildSaRequest() throws Exception {
         IkeSaPayload saPayload =
                 IkeSaPayload.createChildSaRequestPayload(
-                        mTwoChildSaProposalsArray, mIpSecManager, LOCAL_ADDRESS);
+                        mTwoChildSaProposalsArray, mIpSecSpiGenerator, LOCAL_ADDRESS);
 
         assertFalse(saPayload.isSaResponse);
         assertEquals(PROPOSAL_NUMBER_LIST.length, saPayload.proposalList.size());
@@ -769,7 +780,7 @@
 
         Pair<IkeProposal, IkeProposal> negotiatedProposalPair =
                 IkeSaPayload.getVerifiedNegotiatedIkeProposalPair(
-                        reqPayload, respPayload, REMOTE_ADDRESS);
+                        reqPayload, respPayload, mIkeSpiGenerator, REMOTE_ADDRESS);
         IkeProposal reqProposal = negotiatedProposalPair.first;
         IkeProposal respProposal = negotiatedProposalPair.second;
 
@@ -790,14 +801,14 @@
     private void verifyChildSaNegotiation(
             IkeSaPayload reqPayload,
             IkeSaPayload respPayload,
-            IpSecManager ipSecManager,
+            IpSecSpiGenerator ipSecSpiGenerator,
             InetAddress remoteAddress,
             boolean isLocalInit)
             throws Exception {
         // SA negotiation
         Pair<ChildProposal, ChildProposal> negotiatedProposalPair =
                 IkeSaPayload.getVerifiedNegotiatedChildProposalPair(
-                        reqPayload, respPayload, ipSecManager, remoteAddress);
+                        reqPayload, respPayload, ipSecSpiGenerator, remoteAddress);
         ChildProposal reqProposal = negotiatedProposalPair.first;
         ChildProposal respProposal = negotiatedProposalPair.second;
 
@@ -822,7 +833,7 @@
         // Build local request
         IkeSaPayload reqPayload =
                 IkeSaPayload.createChildSaRequestPayload(
-                        mTwoChildSaProposalsArray, mIpSecManager, LOCAL_ADDRESS);
+                        mTwoChildSaProposalsArray, mIpSecSpiGenerator, LOCAL_ADDRESS);
 
         // Build remote response
         Proposal.sTransformDecoder =
@@ -834,7 +845,7 @@
                         TestUtils.hexStringToByteArray(INBOUND_CHILD_PROPOSAL_RAW_PACKET));
 
         verifyChildSaNegotiation(
-                reqPayload, respPayload, mIpSecManager, REMOTE_ADDRESS, true /*isLocalInit*/);
+                reqPayload, respPayload, mIpSecSpiGenerator, REMOTE_ADDRESS, true /*isLocalInit*/);
     }
 
     @Test
@@ -857,10 +868,10 @@
         // Build local response
         IkeSaPayload respPayload =
                 IkeSaPayload.createChildSaResponsePayload(
-                        (byte) 1, mChildSaProposalOne, mIpSecManager, LOCAL_ADDRESS);
+                        (byte) 1, mChildSaProposalOne, mIpSecSpiGenerator, LOCAL_ADDRESS);
 
         verifyChildSaNegotiation(
-                reqPayload, respPayload, mIpSecManager, REMOTE_ADDRESS, false /*isLocalInit*/);
+                reqPayload, respPayload, mIpSecSpiGenerator, REMOTE_ADDRESS, false /*isLocalInit*/);
     }
 
     // Test throwing when negotiated proposal in SA response payload has unrecognized Transform.