Snap for 6533464 from 0ae59f085b7e5f718bc033a5621eee5a99a48bf9 to sdk-release

Change-Id: Ibe51bbf114f3364a2a75c0d5f433da84db7dd5b2
diff --git a/Android.bp b/Android.bp
index 8e7717a..01c0762 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,8 +14,11 @@
 
 java_sdk_library {
     name: "android.net.ipsec.ike",
+    defaults: ["framework-module-defaults"],
     installable: true,
-    sdk_version: "module_current",
+
+    // ike is used as a shared library.
+    shared_library: true,
 
     aidl: {
         local_include_dirs: ["src/java"],
@@ -28,7 +31,6 @@
 
     libs: [
         "unsupportedappusage",
-        "framework-annotations-lib",
     ],
 
     api_packages: [
@@ -41,13 +43,13 @@
     // being overwritten by the frameworks class copies.
     jarjar_rules: "jarjar-rules-shared.txt",
 
-    plugins: ["java_api_finder"],
-
     hostdex: true, // for hiddenapi check
     apex_available: [
         "com.android.ipsec",
         "test_com.android.ipsec",
     ],
+
+    stubs_library_visibility: ["//visibility:public"],
 }
 
 filegroup {
@@ -86,30 +88,3 @@
     // being overwritten by the frameworks class copies.
     jarjar_rules: "jarjar-rules-shared.txt",
 }
-
-stubs_defaults {
-    name: "ike-stubs-defaults",
-    srcs: [":ike-api-srcs"],
-}
-
-droidstubs {
-    name: "android.net.ipsec.ike.api.sources.module_libs_api",
-    defaults: [
-        "framework-module-api-defaults-module_libs_api",
-        "ike-stubs-defaults",
-    ],
-}
-
-droidstubs {
-    name: "android.net.ipsec.ike.stubs.sources.module_libs_api",
-    defaults: [
-        "framework-module-stubs-defaults-module_libs_api",
-        "ike-stubs-defaults",
-    ],
-}
-
-java_library {
-    name: "android.net.ipsec.ike.stubs.module_libs_api",
-    srcs: [":android.net.ipsec.ike.stubs.sources.module_libs_api"],
-    defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
-}
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
new file mode 100644
index 0000000..bec7683
--- /dev/null
+++ b/api/system-lint-baseline.txt
@@ -0,0 +1,75 @@
+// Baseline format: 1.0
+BuilderSetStyle: android.net.ipsec.ike.IkeSessionParams.Builder#removeIkeOption(int):
+    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.ipsec.ike.IkeSessionParams.Builder.removeIkeOption(int)
+
+
+CallbackInterface: android.net.ipsec.ike.ChildSessionCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: ChildSessionCallback
+CallbackInterface: android.net.ipsec.ike.IkeSessionCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: IkeSessionCallback
+
+
+MethodNameUnits: android.net.ipsec.ike.IkeSessionParams#getDpdDelaySeconds():
+    Returned time values must be in milliseconds, was `getDpdDelaySeconds`
+
+
+MissingGetterMatchingBuilder: android.net.eap.EapSessionConfig.Builder#setEapMsChapV2Config(String, String):
+    android.net.eap.EapSessionConfig does not declare a `getEapMsChapV2Config()` method matching method android.net.eap.EapSessionConfig.Builder.setEapMsChapV2Config(String,String)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.ChildSaProposal.Builder#addDhGroup(int):
+    android.net.ipsec.ike.ChildSaProposal does not declare a `getDhGroups()` method matching method android.net.ipsec.ike.ChildSaProposal.Builder.addDhGroup(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.ChildSaProposal.Builder#addEncryptionAlgorithm(int, int):
+    android.net.ipsec.ike.ChildSaProposal does not declare a `getEncryptionAlgorithms()` method matching method android.net.ipsec.ike.ChildSaProposal.Builder.addEncryptionAlgorithm(int,int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.ChildSaProposal.Builder#addIntegrityAlgorithm(int):
+    android.net.ipsec.ike.ChildSaProposal does not declare a `getIntegrityAlgorithms()` method matching method android.net.ipsec.ike.ChildSaProposal.Builder.addIntegrityAlgorithm(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSaProposal.Builder#addDhGroup(int):
+    android.net.ipsec.ike.IkeSaProposal does not declare a `getDhGroups()` method matching method android.net.ipsec.ike.IkeSaProposal.Builder.addDhGroup(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSaProposal.Builder#addEncryptionAlgorithm(int, int):
+    android.net.ipsec.ike.IkeSaProposal does not declare a `getEncryptionAlgorithms()` method matching method android.net.ipsec.ike.IkeSaProposal.Builder.addEncryptionAlgorithm(int,int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSaProposal.Builder#addIntegrityAlgorithm(int):
+    android.net.ipsec.ike.IkeSaProposal does not declare a `getIntegrityAlgorithms()` method matching method android.net.ipsec.ike.IkeSaProposal.Builder.addIntegrityAlgorithm(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#addIkeOption(int):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getIkeOptions()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.addIkeOption(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#addPcscfServerRequest(int):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getPcscfServerRequests()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.addPcscfServerRequest(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#addPcscfServerRequest(java.net.InetAddress):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getPcscfServerRequests()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.addPcscfServerRequest(java.net.InetAddress)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#setAuthDigitalSignature(java.security.cert.X509Certificate, java.security.cert.X509Certificate, java.security.PrivateKey):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getAuthDigitalSignature()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.setAuthDigitalSignature(java.security.cert.X509Certificate,java.security.cert.X509Certificate,java.security.PrivateKey)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#setAuthDigitalSignature(java.security.cert.X509Certificate, java.security.cert.X509Certificate, java.util.List<java.security.cert.X509Certificate>, java.security.PrivateKey):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getAuthDigitalSignature()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.setAuthDigitalSignature(java.security.cert.X509Certificate,java.security.cert.X509Certificate,java.util.List<java.security.cert.X509Certificate>,java.security.PrivateKey)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#setAuthEap(java.security.cert.X509Certificate, android.net.eap.EapSessionConfig):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getAuthEap()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.setAuthEap(java.security.cert.X509Certificate,android.net.eap.EapSessionConfig)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#setAuthPsk(byte[]):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getAuthPsk()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.setAuthPsk(byte[])
+MissingGetterMatchingBuilder: android.net.ipsec.ike.IkeSessionParams.Builder#setLifetimeSeconds(int, int):
+    android.net.ipsec.ike.IkeSessionParams does not declare a `getLifetimeSeconds()` method matching method android.net.ipsec.ike.IkeSessionParams.Builder.setLifetimeSeconds(int,int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TransportModeChildSessionParams.Builder#addInboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector):
+    android.net.ipsec.ike.TransportModeChildSessionParams does not declare a `getInboundTrafficSelectorss()` method matching method android.net.ipsec.ike.TransportModeChildSessionParams.Builder.addInboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TransportModeChildSessionParams.Builder#addOutboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector):
+    android.net.ipsec.ike.TransportModeChildSessionParams does not declare a `getOutboundTrafficSelectorss()` method matching method android.net.ipsec.ike.TransportModeChildSessionParams.Builder.addOutboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TransportModeChildSessionParams.Builder#addSaProposal(android.net.ipsec.ike.ChildSaProposal):
+    android.net.ipsec.ike.TransportModeChildSessionParams does not declare a `getSaProposals()` method matching method android.net.ipsec.ike.TransportModeChildSessionParams.Builder.addSaProposal(android.net.ipsec.ike.ChildSaProposal)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TransportModeChildSessionParams.Builder#setLifetimeSeconds(int, int):
+    android.net.ipsec.ike.TransportModeChildSessionParams does not declare a `getLifetimeSeconds()` method matching method android.net.ipsec.ike.TransportModeChildSessionParams.Builder.setLifetimeSeconds(int,int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addInboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getInboundTrafficSelectorss()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addInboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addInternalAddressRequest(int):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getInternalAddressRequests()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addInternalAddressRequest(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addInternalAddressRequest(java.net.Inet4Address):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getInternalAddressRequests()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addInternalAddressRequest(java.net.Inet4Address)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addInternalAddressRequest(java.net.Inet6Address, int):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getInternalAddressRequests()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addInternalAddressRequest(java.net.Inet6Address,int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addInternalDhcpServerRequest(int):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getInternalDhcpServerRequests()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addInternalDhcpServerRequest(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addInternalDnsServerRequest(int):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getInternalDnsServerRequests()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addInternalDnsServerRequest(int)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addOutboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getOutboundTrafficSelectorss()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addOutboundTrafficSelectors(android.net.ipsec.ike.IkeTrafficSelector)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#addSaProposal(android.net.ipsec.ike.ChildSaProposal):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getSaProposals()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.addSaProposal(android.net.ipsec.ike.ChildSaProposal)
+MissingGetterMatchingBuilder: android.net.ipsec.ike.TunnelModeChildSessionParams.Builder#setLifetimeSeconds(int, int):
+    android.net.ipsec.ike.TunnelModeChildSessionParams does not declare a `getLifetimeSeconds()` method matching method android.net.ipsec.ike.TunnelModeChildSessionParams.Builder.setLifetimeSeconds(int,int)
+
+
+MissingNullability: android.net.ipsec.ike.IkeSessionParams#getRetransmissionTimeoutsMillis():
+    Missing nullability on method `getRetransmissionTimeoutsMillis` return
diff --git a/src/java/android/net/ipsec/ike/ChildSaProposal.java b/src/java/android/net/ipsec/ike/ChildSaProposal.java
index 4d3ea28..7318659 100644
--- a/src/java/android/net/ipsec/ike/ChildSaProposal.java
+++ b/src/java/android/net/ipsec/ike/ChildSaProposal.java
@@ -107,6 +107,25 @@
                 && isTransformSelectedFrom(mEsns, ((ChildSaProposal) reqProposal).mEsns);
     }
 
+    /** @hide */
+    public boolean isNegotiatedFromExceptDhGroup(SaProposal saProposal) {
+        return getProtocolId() == saProposal.getProtocolId()
+                && isTransformSelectedFrom(
+                        getEncryptionTransforms(), saProposal.getEncryptionTransforms())
+                && isTransformSelectedFrom(
+                        getIntegrityTransforms(), saProposal.getIntegrityTransforms())
+                && isTransformSelectedFrom(mEsns, ((ChildSaProposal) saProposal).mEsns);
+    }
+
+    /** @hide */
+    public ChildSaProposal getCopyWithAdditionalDhTransform(int dhGroup) {
+        return new ChildSaProposal(
+                getEncryptionTransforms(),
+                getIntegrityTransforms(),
+                new DhGroupTransform[] {new DhGroupTransform(dhGroup)},
+                getEsnTransforms());
+    }
+
     /**
      * This class is used to incrementally construct a ChildSaProposal. ChildSaProposal instances
      * are immutable once built.
diff --git a/src/java/android/net/ipsec/ike/ChildSessionParams.java b/src/java/android/net/ipsec/ike/ChildSessionParams.java
index e34d1d6..0221491 100644
--- a/src/java/android/net/ipsec/ike/ChildSessionParams.java
+++ b/src/java/android/net/ipsec/ike/ChildSessionParams.java
@@ -35,6 +35,17 @@
  * <p>Note that references to negotiated configurations will be held, and the same parameters will
  * be reused during rekey. This includes SA Proposals, lifetimes and traffic selectors.
  *
+ * <p>IKE library will send out KE payload only if user has configured one or more DH groups. The KE
+ * payload in a request will use the first DH group from the first user provided SA proposal (or the
+ * peer selected SA proposal if it's a rekey request). The KE payload in a response will depend on
+ * the SA proposal negotiation result.
+ *
+ * <p>When requesting the first Child Session in IKE AUTH, IKE library will not propose any DH group
+ * even if user has configured it, as per RFC 7296. When rekeying this child session, IKE library
+ * will accept DH groups that are configured in its ChildSessionParams. If after rekeying user needs
+ * to have the same DH group as that of the IKE Session, then they need to explicitly set the same
+ * DH Group in ChildSessionParams.
+ *
  * @see {@link TunnelModeChildSessionParams} and {@link TransportModeChildSessionParams}
  * @hide
  */
@@ -238,8 +249,9 @@
                 break;
             case IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE:
                 startAddress = InetAddresses.parseNumericAddress("::");
-                endAddress = InetAddresses.parseNumericAddress(
-                        "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+                endAddress =
+                        InetAddresses.parseNumericAddress(
+                                "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
                 break;
             default:
                 throw new IllegalArgumentException("Invalid Traffic Selector type: " + tsType);
diff --git a/src/java/android/net/ipsec/ike/SaProposal.java b/src/java/android/net/ipsec/ike/SaProposal.java
index fa6fbf1..d5eb8c8 100644
--- a/src/java/android/net/ipsec/ike/SaProposal.java
+++ b/src/java/android/net/ipsec/ike/SaProposal.java
@@ -228,6 +228,11 @@
      * Check if the current SaProposal from the SA responder is consistent with the selected
      * reqProposal from the SA initiator.
      *
+     * <p>As per RFC 7296, The accepted cryptographic suite MUST contain exactly one transform of
+     * each type included in the proposal. But for interoperability reason, IKE library allows
+     * exceptions when the accepted suite or the request proposal has a NONE value transform.
+     * Currently only IntegrityTransform and DhGroupTransform have NONE value transform ID defined.
+     *
      * @param reqProposal selected SaProposal from SA initiator
      * @return if current SaProposal from SA responder is consistent with the selected reqProposal
      *     from SA initiator.
@@ -236,11 +241,16 @@
     public boolean isNegotiatedFrom(SaProposal reqProposal) {
         return this.mProtocolId == reqProposal.mProtocolId
                 && isTransformSelectedFrom(mEncryptionAlgorithms, reqProposal.mEncryptionAlgorithms)
-                && isTransformSelectedFrom(mIntegrityAlgorithms, reqProposal.mIntegrityAlgorithms)
-                && isTransformSelectedFrom(mDhGroups, reqProposal.mDhGroups);
+                && isIntegrityTransformSelectedFrom(
+                        mIntegrityAlgorithms, reqProposal.mIntegrityAlgorithms)
+                && isDhGroupTransformSelectedFrom(mDhGroups, reqProposal.mDhGroups);
     }
 
-    /** Package private */
+    /**
+     * Check if the response transform can be selected from the request transforms
+     *
+     * <p>Package private
+     */
     static boolean isTransformSelectedFrom(Transform[] selected, Transform[] selectFrom) {
         // If the selected proposal has multiple transforms with the same type, the responder MUST
         // choose a single one.
@@ -253,6 +263,45 @@
         return Arrays.asList(selectFrom).contains(selected[0]);
     }
 
+    /**
+     * Check if the response integrity transform can be selected from the request integrity
+     * transforms.
+     *
+     * <p>For interoperability reason, it is allowed to do not include integrity transform in the
+     * response proposal when the request proposal has a NONE value integrity transform; and it is
+     * also allowed to have a NONE value integrity transform when the request proposal does not have
+     * integrity transforms.
+     */
+    private static boolean isIntegrityTransformSelectedFrom(
+            IntegrityTransform[] selected, IntegrityTransform[] selectFrom) {
+        if (selected.length == 0) {
+            selected = new IntegrityTransform[] {new IntegrityTransform(INTEGRITY_ALGORITHM_NONE)};
+        }
+        if (selectFrom.length == 0) {
+            selectFrom =
+                    new IntegrityTransform[] {new IntegrityTransform(INTEGRITY_ALGORITHM_NONE)};
+        }
+        return isTransformSelectedFrom(selected, selectFrom);
+    }
+
+    /**
+     * Check if the response DH group can be selected from the request DH groups
+     *
+     * <p>For interoperability reason, it is allowed to do not include DH group in the response
+     * proposal when the request proposal has a NONE value DH group; and it is also allowed to have
+     * a NONE value DH group when the request proposal does not have DH groups.
+     */
+    private static boolean isDhGroupTransformSelectedFrom(
+            DhGroupTransform[] selected, DhGroupTransform[] selectFrom) {
+        if (selected.length == 0) {
+            selected = new DhGroupTransform[] {new DhGroupTransform(DH_GROUP_NONE)};
+        }
+        if (selectFrom.length == 0) {
+            selectFrom = new DhGroupTransform[] {new DhGroupTransform(DH_GROUP_NONE)};
+        }
+        return isTransformSelectedFrom(selected, selectFrom);
+    }
+
     /** @hide */
     @IkePayload.ProtocolId
     public int getProtocolId() {
diff --git a/src/java/android/net/ipsec/ike/TunnelModeChildSessionParams.java b/src/java/android/net/ipsec/ike/TunnelModeChildSessionParams.java
index 10a23db..c8d31f6 100644
--- a/src/java/android/net/ipsec/ike/TunnelModeChildSessionParams.java
+++ b/src/java/android/net/ipsec/ike/TunnelModeChildSessionParams.java
@@ -95,29 +95,10 @@
     }
 
     /** Represents an IPv4 DHCP server request */
-    public interface ConfigRequestIpv4DhcpServer extends TunnelModeChildConfigRequest {
-        /**
-         * Retrieves the requested IPv4 DHCP server address
-         *
-         * @return The requested DHCP server address, or null if no specific DHCP server was
-         *     requested
-         * @hide
-         */
-        @Nullable
-        Inet4Address getAddress();
-    }
+    public interface ConfigRequestIpv4DhcpServer extends TunnelModeChildConfigRequest {}
 
     /** Represents an IPv4 DNS Server request */
-    public interface ConfigRequestIpv4DnsServer extends TunnelModeChildConfigRequest {
-        /**
-         * Retrieves the requested IPv4 DNS server address
-         *
-         * @return The requested DNS server address, or null if no specific DNS server was requested
-         * @hide
-         */
-        @Nullable
-        Inet4Address getAddress();
-    }
+    public interface ConfigRequestIpv4DnsServer extends TunnelModeChildConfigRequest {}
 
     /** Represents an IPv4 Netmask request */
     public interface ConfigRequestIpv4Netmask extends TunnelModeChildConfigRequest {}
@@ -141,16 +122,7 @@
     }
 
     /** Represents an IPv6 DNS Server request */
-    public interface ConfigRequestIpv6DnsServer extends TunnelModeChildConfigRequest {
-        /**
-         * Retrieves the requested IPv6 DNS server address
-         *
-         * @return The requested DNS server address, or null if no specific DNS server was requested
-         * @hide
-         */
-        @Nullable
-        Inet6Address getAddress();
-    }
+    public interface ConfigRequestIpv6DnsServer extends TunnelModeChildConfigRequest {}
 
     /** This class can be used to incrementally construct a {@link TunnelModeChildSessionParams}. */
     public static final class Builder extends ChildSessionParams.Builder {
diff --git a/src/java/com/android/internal/net/ipsec/ike/AbstractSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/AbstractSessionStateMachine.java
index 458f261..1ed89b9 100644
--- a/src/java/com/android/internal/net/ipsec/ike/AbstractSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/AbstractSessionStateMachine.java
@@ -78,6 +78,9 @@
     // Use a value greater than the retransmit-failure timeout.
     static final long REKEY_DELETE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(180L);
 
+    // Default delay time for retrying a request
+    static final long RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(15L);
+
     protected final Executor mUserCbExecutor;
     private final String mLogTag;
 
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 8697ab5..fd500b8 100644
--- a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
@@ -79,7 +79,6 @@
 import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException;
 import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
 import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
-import com.android.internal.net.ipsec.ike.exceptions.TemporaryFailureException;
 import com.android.internal.net.ipsec.ike.exceptions.TsUnacceptableException;
 import com.android.internal.net.ipsec.ike.message.IkeConfigPayload;
 import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute;
@@ -104,8 +103,10 @@
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -221,6 +222,7 @@
     @VisibleForTesting final State mRekeyChildRemoteCreate = new RekeyChildRemoteCreate();
     @VisibleForTesting final State mRekeyChildLocalDelete = new RekeyChildLocalDelete();
     @VisibleForTesting final State mRekeyChildRemoteDelete = new RekeyChildRemoteDelete();
+    @VisibleForTesting boolean mIsFirstChild;
 
     /**
      * Builds a new uninitialized ChildSessionStateMachine
@@ -361,6 +363,7 @@
         this.mUdpEncapSocket = udpEncapSocket;
         this.mIkePrf = ikePrf;
         this.mSkD = skD;
+        mIsFirstChild = true;
 
         int spi = registerProvisionalChildAndGetSpi(respPayloads);
         sendMessage(
@@ -391,6 +394,7 @@
         this.mUdpEncapSocket = udpEncapSocket;
         this.mIkePrf = ikePrf;
         this.mSkD = skD;
+        mIsFirstChild = false;
 
         sendMessage(CMD_LOCAL_REQUEST_CREATE_CHILD);
     }
@@ -744,12 +748,9 @@
                                         mSkD,
                                         mChildSessionParams.isTransportMode(),
                                         true /*isLocalInit*/,
-                                        rekeyLocalRequest,
                                         buildSaLifetimeAlarmSched(
                                                 createChildResult.respSpi.getSpi()));
 
-                        mChildSmCallback.scheduleLocalRequest(rekeyLocalRequest, getRekeyTimeout());
-
                         ChildSessionConfiguration sessionConfig =
                                 buildChildSessionConfigFromResp(createChildResult, respPayloads);
                         executeUserCallback(
@@ -772,9 +773,12 @@
 
                         // TODO: Initiate deletion
                         mChildSmCallback.onChildSaDeleted(createChildResult.respSpi.getSpi());
+                        handleChildFatalError(e);
+                    } finally {
+                        // In the successful case the transform in ChildSaRecord has taken ownership
+                        // of the SPI (in IpSecService), and will keep it alive.
                         createChildResult.initSpi.close();
                         createChildResult.respSpi.close();
-                        handleChildFatalError(e);
                     }
                     break;
                 case CREATE_STATUS_CHILD_ERROR_INVALID_MSG:
@@ -881,19 +885,21 @@
 
     /** Initial state of ChildSessionStateMachine. */
     class Initial extends CreateChildLocalCreateBase {
+        List<IkePayload> mRequestPayloads;
+
         @Override
         public boolean processStateMessage(Message message) {
             switch (message.what) {
                 case CMD_HANDLE_FIRST_CHILD_EXCHANGE:
                     FirstChildNegotiationData childNegotiationData =
                             (FirstChildNegotiationData) message.obj;
-                    List<IkePayload> reqPayloads = childNegotiationData.requestPayloads;
+                    mRequestPayloads = childNegotiationData.requestPayloads;
                     List<IkePayload> respPayloads = childNegotiationData.responsePayloads;
 
                     // Negotiate Child SA. The exchangeType has been validated in
                     // IkeSessionStateMachine. Won't validate it again here.
                     validateAndBuildChild(
-                            reqPayloads,
+                            mRequestPayloads,
                             respPayloads,
                             EXCHANGE_TYPE_IKE_AUTH,
                             EXCHANGE_TYPE_IKE_AUTH,
@@ -918,6 +924,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     /**
@@ -973,6 +984,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     /**
@@ -1264,13 +1280,18 @@
         @Override
         public void enterState() {
             try {
+                ChildSaProposal saProposal = mSaProposal;
+                if (mIsFirstChild) {
+                    saProposal = addDhGroupsFromChildSessionParamsIfAbsent();
+                }
+
                 // Build request with negotiated proposal and TS.
                 mRequestPayloads =
                         CreateChildSaHelper.getRekeyChildCreateReqPayloads(
                                 mRandomFactory,
                                 mIpSecSpiGenerator,
                                 mLocalAddress,
-                                mSaProposal,
+                                saProposal,
                                 mLocalTs,
                                 mRemoteTs,
                                 mCurrentChildSaRecord.getLocalSpi(),
@@ -1282,8 +1303,7 @@
                         ChildSessionStateMachine.this);
             } catch (SpiUnavailableException | ResourceUnavailableException e) {
                 loge("Fail to assign Child SPI. Schedule a retry for rekey Child");
-                mChildSmCallback.scheduleRetryLocalRequest(
-                        (ChildLocalRequest) mCurrentChildSaRecord.getFutureRekeyEvent());
+                mCurrentChildSaRecord.rescheduleRekey(RETRY_INTERVAL_MS);
                 transitionTo(mIdle);
             }
         }
@@ -1307,8 +1327,8 @@
                     switch (createChildResult.status) {
                         case CREATE_STATUS_OK:
                             try {
-                                // Do not need to update the negotiated proposal and TS because they
-                                // are not changed.
+                                // Do not need to update TS because they are not changed.
+                                mSaProposal = createChildResult.negotiatedProposal;
 
                                 ChildLocalRequest rekeyLocalRequest = makeRekeyLocalRequest();
 
@@ -1328,13 +1348,9 @@
                                                 mSkD,
                                                 mChildSessionParams.isTransportMode(),
                                                 true /*isLocalInit*/,
-                                                rekeyLocalRequest,
                                                 buildSaLifetimeAlarmSched(
                                                         createChildResult.respSpi.getSpi()));
 
-                                mChildSmCallback.scheduleLocalRequest(
-                                        rekeyLocalRequest, getRekeyTimeout());
-
                                 executeUserCallback(
                                         () -> {
                                             mUserCallback.onIpSecTransformCreated(
@@ -1354,6 +1370,9 @@
                                     | IOException e) {
                                 // #makeChildSaRecord failed
                                 handleProcessRespOrSaCreationFailAndQuit(resp.registeredSpi, e);
+                            } finally {
+                                // In the successful case the transform in ChildSaRecord has taken
+                                // ownership of the SPI (in IpSecService), and will keep it alive.
                                 createChildResult.initSpi.close();
                                 createChildResult.respSpi.close();
                             }
@@ -1363,18 +1382,10 @@
                                     resp.registeredSpi, createChildResult.exception);
                             break;
                         case CREATE_STATUS_CHILD_ERROR_RCV_NOTIFY:
-                            if (createChildResult.exception instanceof TemporaryFailureException) {
-                                loge(
-                                        "Received TEMPORARY_FAILURE for rekey Child. Retry has"
-                                                + "already been scheduled by IKE Session.");
-                            } else {
-                                loge(
-                                        "Received error notification for rekey Child. Schedule a"
-                                                + " retry");
-                                mChildSmCallback.scheduleRetryLocalRequest(
-                                        (ChildLocalRequest)
-                                                mCurrentChildSaRecord.getFutureRekeyEvent());
-                            }
+                            loge(
+                                    "Received error notification for rekey Child. Schedule a"
+                                            + " retry");
+                            mCurrentChildSaRecord.rescheduleRekey(RETRY_INTERVAL_MS);
 
                             transitionTo(mIdle);
                             break;
@@ -1403,6 +1414,34 @@
             }
             handleChildFatalError(exception);
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
+    }
+
+    private ChildSaProposal addDhGroupsFromChildSessionParamsIfAbsent() {
+        // DH groups are excluded for the first child. Add dh groups from child session params in
+        // this case.
+        if (mSaProposal.getDhGroups().size() != 0) {
+            return mSaProposal;
+        }
+
+        Set<DhGroupTransform> dhGroupSet = new LinkedHashSet<>();
+        for (SaProposal saProposal : mChildSessionParams.getSaProposals()) {
+            if (!mSaProposal.isNegotiatedFromExceptDhGroup(saProposal)) continue;
+            dhGroupSet.addAll(Arrays.asList(saProposal.getDhGroupTransforms()));
+        }
+
+        DhGroupTransform[] dhGroups = new DhGroupTransform[dhGroupSet.size()];
+        dhGroupSet.toArray(dhGroups);
+
+        return new ChildSaProposal(
+                mSaProposal.getEncryptionTransforms(),
+                mSaProposal.getIntegrityTransforms(),
+                dhGroups,
+                mSaProposal.getEsnTransforms());
     }
 
     /**
@@ -1448,7 +1487,21 @@
                 IkeSaPayload reqSaPayload =
                         IkePayload.getPayloadForTypeInProvidedList(
                                 PAYLOAD_TYPE_SA, IkeSaPayload.class, reqPayloads);
-                byte respProposalNumber = reqSaPayload.getNegotiatedProposalNumber(mSaProposal);
+
+                IkeKePayload reqKePayload =
+                        IkePayload.getPayloadForTypeInProvidedList(
+                                PAYLOAD_TYPE_KE, IkeKePayload.class, reqPayloads);
+
+                ChildSaProposal saProposal = mSaProposal;
+
+                // Try accepting a DH group requested during remote rekey for both first and
+                // additional Child Sessions even if it is different from the previously negotiated
+                // proposal.
+                if (reqKePayload != null && isKePayloadAcceptable(reqKePayload)) {
+                    saProposal = mSaProposal.getCopyWithAdditionalDhTransform(reqKePayload.dhGroup);
+                }
+
+                byte respProposalNumber = reqSaPayload.getNegotiatedProposalNumber(saProposal);
 
                 respPayloads =
                         CreateChildSaHelper.getRekeyChildCreateRespPayloads(
@@ -1456,7 +1509,7 @@
                                 mIpSecSpiGenerator,
                                 mLocalAddress,
                                 respProposalNumber,
-                                mSaProposal,
+                                saProposal,
                                 mLocalTs,
                                 mRemoteTs,
                                 mCurrentChildSaRecord.getLocalSpi(),
@@ -1483,8 +1536,8 @@
             switch (createChildResult.status) {
                 case CREATE_STATUS_OK:
                     try {
-                        // Do not need to update the negotiated proposal and TS
-                        // because they are not changed.
+                        // Do not need to update TS because they are not changed.
+                        mSaProposal = createChildResult.negotiatedProposal;
 
                         ChildLocalRequest rekeyLocalRequest = makeRekeyLocalRequest();
 
@@ -1504,12 +1557,9 @@
                                         mSkD,
                                         mChildSessionParams.isTransportMode(),
                                         false /*isLocalInit*/,
-                                        rekeyLocalRequest,
                                         buildSaLifetimeAlarmSched(
                                                 createChildResult.initSpi.getSpi()));
 
-                        mChildSmCallback.scheduleLocalRequest(rekeyLocalRequest, getRekeyTimeout());
-
                         mChildSmCallback.onChildSaCreated(
                                 mRemoteInitNewChildSaRecord.getRemoteSpi(),
                                 ChildSessionStateMachine.this);
@@ -1537,12 +1587,14 @@
                             | SpiUnavailableException
                             | IOException e) {
                         // #makeChildSaRecord failed.
-                        createChildResult.initSpi.close();
-                        createChildResult.respSpi.close();
-
                         handleCreationFailureAndBackToIdle(
                                 new NoValidProposalChosenException(
                                         "Error in Child SA creation", e));
+                    } finally {
+                        // In the successful case the transform in ChildSaRecord has taken ownership
+                        // of the SPI (in IpSecService), and will keep it alive.
+                        createChildResult.initSpi.close();
+                        createChildResult.respSpi.close();
                     }
                     break;
                 case CREATE_STATUS_CHILD_ERROR_INVALID_MSG:
@@ -1568,6 +1620,20 @@
             }
         }
 
+        private boolean isKePayloadAcceptable(IkeKePayload reqKePayload) {
+            ChildSaProposal proposal =
+                    mSaProposal.getCopyWithAdditionalDhTransform(reqKePayload.dhGroup);
+
+            // Verify if this proposal is accepted by user
+            for (SaProposal saProposal : mChildSessionParams.getSaProposals()) {
+                if (proposal.isNegotiatedFrom(saProposal)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         private void handleCreationFailureAndBackToIdle(IkeProtocolException e) {
             loge("Received invalid Rekey Child request. Reject with error notification", e);
 
@@ -2005,6 +2071,19 @@
             return hasExpectedRekeyNotify;
         }
 
+        public static void releaseSpiResources(List<IkePayload> reqPayloads) {
+            if (reqPayloads == null) {
+                return;
+            }
+
+            IkeSaPayload saPayload =
+                    IkePayload.getPayloadForTypeInProvidedList(
+                            IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, reqPayloads);
+            if (saPayload != null) {
+                saPayload.releaseChildSpiResourcesIfExists();
+            }
+        }
+
         /** Validate the received payload list and negotiate Child SA. */
         private static CreateChildResult validateAndNegotiateChild(
                 List<IkePayload> reqPayloads,
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestScheduler.java b/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestScheduler.java
index e86df23..f8008fb 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestScheduler.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestScheduler.java
@@ -64,15 +64,18 @@
      * Notifies the scheduler that the caller is ready for a new procedure
      *
      * <p>Synchronously triggers the call to onNewProcedureReady.
+     *
+     * @return whether or not a new procedure was scheduled.
      */
-    public void readyForNextProcedure() {
+    public boolean readyForNextProcedure() {
         while (!mRequestQueue.isEmpty()) {
             LocalRequest request = mRequestQueue.poll();
             if (!request.isCancelled()) {
                 mConsumer.onNewProcedureReady(request);
-                return;
+                return true;
             }
         }
+        return false;
     }
 
     /**
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 87eab42..1a3bf18 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -22,6 +22,7 @@
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ErrorType;
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 
 import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_TYPE_REPLY;
 import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
@@ -34,9 +35,14 @@
 import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
 import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
 import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_DELETE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_EAP;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_INITIATOR;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_RESPONDER;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_VENDOR;
 import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DELETE_CHILD;
 import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DELETE_IKE;
@@ -74,6 +80,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -125,7 +132,6 @@
 import com.android.internal.net.ipsec.ike.message.IkePayload;
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload;
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IkeProposal;
-import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
 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;
@@ -214,9 +220,6 @@
     // Default fragment size in bytes.
     @VisibleForTesting static final int DEFAULT_FRAGMENT_SIZE = 1280;
 
-    // Default delay time for retrying a request
-    @VisibleForTesting static final long RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(15L);
-
     // Close IKE Session when all responses during this time were TEMPORARY_FAILURE(s). This
     // indicates that something has gone wrong, and we are out of sync.
     @VisibleForTesting
@@ -368,6 +371,9 @@
      */
     private final IpSecSpiGenerator mIpSecSpiGenerator;
 
+    /** Ensures that the system does not go to sleep in the middle of an exchange. */
+    private final PowerManager.WakeLock mBusyWakeLock;
+
     @VisibleForTesting
     @GuardedBy("mChildCbToSessions")
     final HashMap<ChildSessionCallback, ChildSessionStateMachine> mChildCbToSessions =
@@ -495,6 +501,10 @@
             // callback instance in the future
         }
 
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        mBusyWakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, TAG + "mBusyWakeLock");
+        mBusyWakeLock.setReferenceCounted(false);
+
         mIkeSessionId = sIkeSessionIdGenerator.getAndIncrement();
         sIkeAlarmReceiver.registerIkeSession(mIkeSessionId, getHandler());
 
@@ -551,6 +561,7 @@
                             sendMessageAtFrontOfQueue(CMD_EXECUTE_LOCAL_REQ, localReq);
                         });
 
+        mBusyWakeLock.acquire();
         start();
     }
 
@@ -731,15 +742,13 @@
             }
         }
 
-        /** Schedule retry when a request got rejected by TEMPORARY_FAILURE. */
-        public void handleTempFailure(LocalRequest localRequest) {
-            logd(
-                    "TempFailureHandler: Receive TEMPORARY FAILURE. Reschedule request: "
-                            + localRequest.procedureType);
-
-            // TODO: Support customized delay time when this is a rekey request and SA is going to
-            // expire soon.
-            scheduleRetry(localRequest);
+        /**
+         * Schedule temporary failure timeout.
+         *
+         * <p>Caller of this method is responsible for scheduling retry of the rejected request.
+         */
+        public void handleTempFailure() {
+            logd("TempFailureHandler: Receive TEMPORARY FAILURE");
 
             if (!mTempFailureReceived) {
                 sendEmptyMessageDelayed(TEMP_FAILURE_RETRY_TIMEOUT, TEMP_FAILURE_RETRY_TIMEOUT_MS);
@@ -773,8 +782,6 @@
         // IkeSaRecord is created. Calling this method at the end of exchange will double-register
         // the SPI but it is safe because the key and value are not changed.
         mIkeSocket.registerIke(record.getLocalSpi(), this);
-
-        scheduleRekeySession(record.getFutureRekeyEvent());
     }
 
     @VisibleForTesting
@@ -944,6 +951,10 @@
             }
         }
 
+        // Release IPsec SPIs if IKE Session is terminated before receiving the IKE AUTH response
+        // that contains the first child SA proposal
+        CreateChildSaHelper.releaseSpiResources(mFirstChildReqList);
+
         if (mIkeNattKeepalive != null) {
             mIkeNattKeepalive.stop();
         }
@@ -963,6 +974,8 @@
             }
             // TODO: Remove the stored ikeSessionCallback
         }
+
+        mBusyWakeLock.release();
     }
 
     private void closeAllSaRecords(boolean expectSaClosed) {
@@ -1065,7 +1078,9 @@
 
         @Override
         public void enterState() {
-            mScheduler.readyForNextProcedure();
+            if (!mScheduler.readyForNextProcedure()) {
+                mBusyWakeLock.release();
+            }
 
             if (mDpdIntent == null) {
                 long remoteIkeSpi = mCurrentIkeSaRecord.getRemoteSpi();
@@ -1097,6 +1112,8 @@
             // #exitState is guaranteed to be invoked when quit() or quitNow() is called
             mAlarmManager.cancel(mDpdIntent);
             logd("DPD Alarm canceled");
+
+            mBusyWakeLock.acquire();
         }
 
         @Override
@@ -1200,7 +1217,8 @@
         return obtainMessage(CMD_ALARM_FIRED, mIkeSessionId, localRequestType, spiBundle);
     }
 
-    private SaLifetimeAlarmScheduler buildSaLifetimeAlarmScheduler(long remoteSpi) {
+    @VisibleForTesting
+    SaLifetimeAlarmScheduler buildSaLifetimeAlarmScheduler(long remoteSpi) {
         PendingIntent deleteSaIntent =
                 buildIkeAlarmIntent(
                         mContext,
@@ -1470,14 +1488,12 @@
                     // Software keepalive alarm is fired
                     mIkeNattKeepalive.onAlarmFired();
                     return;
-                case CMD_LOCAL_REQUEST_DELETE_CHILD:
-                    // Child SA (identified by remoteChildSpi) has hit its hard lifetime
+                case CMD_LOCAL_REQUEST_DELETE_CHILD: // Hits hard lifetime; fall through
+                case CMD_LOCAL_REQUEST_REKEY_CHILD: // Hits soft lifetime
                     enqueueChildLocalRequest(message);
                     return;
-                case CMD_LOCAL_REQUEST_DELETE_IKE:
-                    // IKE SA hits its hard lifetime
-                    enqueueIkeLocalRequest(message);
-                    return;
+                case CMD_LOCAL_REQUEST_DELETE_IKE: // Hits hard lifetime; fall through
+                case CMD_LOCAL_REQUEST_REKEY_IKE: // Hits soft lifetime; fall through
                 case CMD_LOCAL_REQUEST_DPD:
                     // IKE Session has not received any protectd IKE packet for the whole DPD delay
                     enqueueIkeLocalRequest(message);
@@ -2215,7 +2231,9 @@
 
         @Override
         protected void handleTempFailure() {
-            mTempFailHandler.handleTempFailure(mLocalRequestOngoing);
+            // The ChildSessionStateMachine will be responsible for rescheduling the rejected
+            // request.
+            mTempFailHandler.handleTempFailure();
         }
 
         private void transitionToIdleIfAllProceduresDone() {
@@ -2694,7 +2712,6 @@
                                 mIkePrf,
                                 mIkeIntegrity == null ? 0 : mIkeIntegrity.getKeyLength(),
                                 mIkeCipher.getKeyLength(),
-                                new IkeLocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE),
                                 buildSaLifetimeAlarmScheduler(mRemoteIkeSpiResource.getSpi()));
 
                 addIkeSaRecord(mCurrentIkeSaRecord);
@@ -2882,12 +2899,8 @@
 
             if (respSaPayload == null
                     || respKePayload == null
-                    || natSourcePayloads.isEmpty()
-                    || natDestPayload == null
                     || !hasNoncePayload) {
-                throw new InvalidSyntaxException(
-                        "SA, KE, Nonce, Notify-NAT-Detection-Source, or"
-                                + " Notify-NAT-Detection-Destination payload missing.");
+                throw new InvalidSyntaxException("SA, KE, or Nonce payload missing.");
             }
 
             IkeSaPayload reqSaPayload =
@@ -2915,6 +2928,20 @@
                 throw new InvalidSyntaxException("Received KE payload with mismatched DH group.");
             }
 
+            if (mRemoteAddress instanceof Inet4Address) {
+                handleNatDetection(respMsg, natSourcePayloads, natDestPayload);
+            }
+        }
+
+        private void handleNatDetection(
+                IkeMessage respMsg,
+                List<IkeNotifyPayload> natSourcePayloads,
+                IkeNotifyPayload natDestPayload)
+                throws InvalidSyntaxException, IOException {
+            if (natSourcePayloads.isEmpty() || natDestPayload == null) {
+                throw new InvalidSyntaxException("NAT detection notifications missing.");
+            }
+
             // NAT detection
             long initIkeSpi = respMsg.ikeHeader.ikeInitiatorSpi;
             long respIkeSpi = respMsg.ikeHeader.ikeResponderSpi;
@@ -2952,7 +2979,8 @@
                             IkeUdpEncapSocket.getIkeUdpEncapSocket(
                                     mIkeSessionParams.getNetwork(),
                                     mIpSecManager,
-                                    IkeSessionStateMachine.this);
+                                    IkeSessionStateMachine.this,
+                                    getHandler().getLooper());
                     switchToIkeSocket(initIkeSpi, newSocket);
                 } catch (ErrnoException | IOException | ResourceUnavailableException e) {
                     handleIkeFatalError(e);
@@ -3070,50 +3098,26 @@
 
         protected List<IkePayload> extractChildPayloadsFromMessage(IkeMessage ikeMessage)
                 throws InvalidSyntaxException {
-            IkeSaPayload saPayload =
-                    ikeMessage.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class);
-            IkeTsPayload tsInitPayload =
-                    ikeMessage.getPayloadForType(
-                            IkePayload.PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class);
-            IkeTsPayload tsRespPayload =
-                    ikeMessage.getPayloadForType(
-                            IkePayload.PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class);
-
-            List<IkeNotifyPayload> notifyPayloads =
-                    ikeMessage.getPayloadListForType(
-                            IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
-
-            IkeConfigPayload configPayload =
-                    ikeMessage.getPayloadForType(
-                            IkePayload.PAYLOAD_TYPE_CP, IkeConfigPayload.class);
-
-            boolean hasErrorNotify = false;
             List<IkePayload> list = new LinkedList<>();
-            for (IkeNotifyPayload payload : notifyPayloads) {
-                if (payload.isNewChildSaNotify()) {
-                    list.add(payload);
-                    if (payload.isErrorNotify()) {
-                        hasErrorNotify = true;
-                    }
+            for (IkePayload payload : ikeMessage.ikePayloadList) {
+                switch (payload.payloadType) {
+                    case PAYLOAD_TYPE_SA: // fall through
+                    case PAYLOAD_TYPE_TS_INITIATOR: // fall through
+                    case PAYLOAD_TYPE_TS_RESPONDER: // fall through
+                    case PAYLOAD_TYPE_CP:
+                        list.add(payload);
+                        break;
+                    case PAYLOAD_TYPE_NOTIFY:
+                        if (((IkeNotifyPayload) payload).isNewChildSaNotify()) {
+                            list.add(payload);
+                        }
+                        break;
+                    default:
+                        // Ignore payloads unrelated with Child negotiation
                 }
             }
 
-            // If there is no error notification, SA, TS-initiator and TS-responder MUST all be
-            // included in this message.
-            if (!hasErrorNotify
-                    && (saPayload == null || tsInitPayload == null || tsRespPayload == null)) {
-                throw new InvalidSyntaxException(
-                        "SA, TS-Initiator or TS-Responder payload is missing.");
-            }
-
-            list.add(saPayload);
-            list.add(tsInitPayload);
-            list.add(tsRespPayload);
-
-            if (configPayload != null) {
-                list.add(configPayload);
-            }
-
+            // Payload validation is done in ChildSessionStateMachine
             return list;
         }
 
@@ -3194,12 +3198,11 @@
                             "Expected EXCHANGE_TYPE_IKE_AUTH but received: " + exchangeType);
                 }
 
+                validateIkeAuthResp(ikeMessage);
+
                 List<IkePayload> childReqList =
                         extractChildPayloadsFromMessage(mRetransmitter.getMessage());
-
                 if (mUseEap) {
-                    validateIkeAuthRespWithEapPayload(ikeMessage);
-
                     // childReqList needed after EAP completed, so persist to IkeSessionStateMachine
                     // state.
                     mFirstChildReqList = childReqList;
@@ -3207,12 +3210,12 @@
                     IkeEapPayload ikeEapPayload =
                             ikeMessage.getPayloadForType(
                                     IkePayload.PAYLOAD_TYPE_EAP, IkeEapPayload.class);
-
+                    if (ikeEapPayload == null) {
+                        throw new AuthenticationFailedException("Missing EAP payload");
+                    }
                     deferMessage(obtainMessage(CMD_EAP_START_EAP_AUTH, ikeEapPayload));
                     transitionTo(mCreateIkeLocalIkeAuthInEap);
                 } else {
-                    validateIkeAuthRespWithChildPayloads(ikeMessage);
-
                     notifyIkeSessionSetup(ikeMessage);
 
                     performFirstChildNegotiation(
@@ -3325,40 +3328,12 @@
             return buildIkeAuthReqMessage(payloadList);
         }
 
-        private void validateIkeAuthRespWithEapPayload(IkeMessage respMsg)
-                throws IkeProtocolException {
-            IkeEapPayload ikeEapPayload =
-                    respMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_EAP, IkeEapPayload.class);
-            if (ikeEapPayload == null) {
-                throw new AuthenticationFailedException("Missing EAP payload");
-            }
-
-            // TODO: check that we don't receive any ChildSaRespPayloads here
-
-            List<IkePayload> nonEapPayloads = new LinkedList<>();
-            nonEapPayloads.addAll(respMsg.ikePayloadList);
-            nonEapPayloads.remove(ikeEapPayload);
-            validateIkeAuthResp(nonEapPayloads);
-        }
-
-        private void validateIkeAuthRespWithChildPayloads(IkeMessage respMsg)
-                throws IkeProtocolException {
-            // Extract and validate existence of payloads for first Child SA setup.
-            List<IkePayload> childSaRespPayloads = extractChildPayloadsFromMessage(respMsg);
-
-            List<IkePayload> nonChildPayloads = new LinkedList<>();
-            nonChildPayloads.addAll(respMsg.ikePayloadList);
-            nonChildPayloads.removeAll(childSaRespPayloads);
-
-            validateIkeAuthResp(nonChildPayloads);
-        }
-
-        private void validateIkeAuthResp(List<IkePayload> payloadList) throws IkeProtocolException {
+        private void validateIkeAuthResp(IkeMessage authResp) throws IkeProtocolException {
             // Validate IKE Authentication
             IkeAuthPayload authPayload = null;
             List<IkeCertPayload> certPayloads = new LinkedList<>();
 
-            for (IkePayload payload : payloadList) {
+            for (IkePayload payload : authResp.ikePayloadList) {
                 switch (payload.payloadType) {
                     case IkePayload.PAYLOAD_TYPE_ID_RESPONDER:
                         mRespIdPayload = (IkeIdPayload) payload;
@@ -3380,7 +3355,18 @@
                     case IkePayload.PAYLOAD_TYPE_NOTIFY:
                         IkeNotifyPayload notifyPayload = (IkeNotifyPayload) payload;
                         if (notifyPayload.isErrorNotify()) {
-                            throw notifyPayload.validateAndBuildIkeException();
+                            if (notifyPayload.isNewChildSaNotify()
+                                    && authResp.getPayloadForType(
+                                                    PAYLOAD_TYPE_AUTH, IkeAuthPayload.class)
+                                            != null) {
+                                // If error is for creating Child and Auth payload is included, try
+                                // to do authentication first and let ChildSessionStateMachine
+                                // handle the error later.
+                                continue;
+                            } else {
+                                throw notifyPayload.validateAndBuildIkeException();
+                            }
+
                         } else {
                             // Unknown and unexpected status notifications are ignored as per
                             // RFC7296.
@@ -3390,6 +3376,12 @@
                                             + notifyPayload.notifyType);
                         }
                         break;
+                    case PAYLOAD_TYPE_SA: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_CP: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_INITIATOR: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_RESPONDER: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_EAP: // Will be handled separately
+                        break;
                     default:
                         logw(
                                 "Received unexpected payload in IKE AUTH response. Payload"
@@ -3675,18 +3667,11 @@
                             "Expected EXCHANGE_TYPE_IKE_AUTH but received: " + exchangeType);
                 }
 
-                // Extract and validate existence of payloads for first Child SA setup.
-                List<IkePayload> childSaRespPayloads = extractChildPayloadsFromMessage(ikeMessage);
-
-                List<IkePayload> nonChildPayloads = new LinkedList<>();
-                nonChildPayloads.addAll(ikeMessage.ikePayloadList);
-                nonChildPayloads.removeAll(childSaRespPayloads);
-
-                validateIkeAuthRespPostEap(nonChildPayloads);
-
+                validateIkeAuthRespPostEap(ikeMessage);
                 notifyIkeSessionSetup(ikeMessage);
 
-                performFirstChildNegotiation(mFirstChildReqList, childSaRespPayloads);
+                performFirstChildNegotiation(
+                        mFirstChildReqList, extractChildPayloadsFromMessage(ikeMessage));
             } catch (IkeProtocolException e) {
                 // Notify the remote because they may have set up the IKE SA.
                 sendEncryptedIkeMessage(buildIkeDeleteReq(mCurrentIkeSaRecord));
@@ -3703,11 +3688,10 @@
             handleIkeFatalError(ikeException);
         }
 
-        private void validateIkeAuthRespPostEap(List<IkePayload> payloadList)
-                throws IkeProtocolException {
+        private void validateIkeAuthRespPostEap(IkeMessage authResp) throws IkeProtocolException {
             IkeAuthPayload authPayload = null;
 
-            for (IkePayload payload : payloadList) {
+            for (IkePayload payload : authResp.ikePayloadList) {
                 switch (payload.payloadType) {
                     case IkePayload.PAYLOAD_TYPE_AUTH:
                         authPayload = (IkeAuthPayload) payload;
@@ -3715,7 +3699,18 @@
                     case IkePayload.PAYLOAD_TYPE_NOTIFY:
                         IkeNotifyPayload notifyPayload = (IkeNotifyPayload) payload;
                         if (notifyPayload.isErrorNotify()) {
-                            throw notifyPayload.validateAndBuildIkeException();
+                            if (notifyPayload.isNewChildSaNotify()
+                                    && authResp.getPayloadForType(
+                                                    PAYLOAD_TYPE_AUTH, IkeAuthPayload.class)
+                                            != null) {
+                                // If error is for creating Child and Auth payload is included, try
+                                // to do authentication first and let ChildSessionStateMachine
+                                // handle the error later.
+                                continue;
+                            } else {
+                                throw notifyPayload.validateAndBuildIkeException();
+                            }
+
                         } else {
                             // Unknown and unexpected status notifications are ignored as per
                             // RFC7296.
@@ -3725,6 +3720,11 @@
                                             + notifyPayload.notifyType);
                         }
                         break;
+                    case PAYLOAD_TYPE_SA: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_CP: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_INITIATOR: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_RESPONDER: // Will be handled separately; fall through
+                        break;
                     default:
                         logw(
                                 "Received unexpected payload in IKE AUTH response. Payload"
@@ -3878,7 +3878,7 @@
                                 + firstErrorNotifyPayload.notifyType
                                 + " for rekey IKE. Schedule a retry");
                 if (!isSimulRekey) {
-                    scheduleRetry(mCurrentIkeSaRecord.getFutureRekeyEvent());
+                    mCurrentIkeSaRecord.rescheduleRekey(RETRY_INTERVAL_MS);
                 }
             }
 
@@ -3940,7 +3940,6 @@
                                 newIntegrity == null ? 0 : newIntegrity.getKeyLength(),
                                 newCipher.getKeyLength(),
                                 isLocalInit,
-                                new IkeLocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE),
                                 buildSaLifetimeAlarmScheduler(remoteSpi));
                 addIkeSaRecord(newSaRecord);
 
@@ -3969,7 +3968,7 @@
                 mRetransmitter = new EncryptedRetransmitter(buildIkeRekeyReq());
             } catch (IOException e) {
                 loge("Fail to assign IKE SPI for rekey. Schedule a retry.", e);
-                scheduleRetry(mCurrentIkeSaRecord.getFutureRekeyEvent());
+                mCurrentIkeSaRecord.rescheduleRekey(RETRY_INTERVAL_MS);
                 transitionTo(mIdle);
             }
         }
@@ -3981,7 +3980,8 @@
 
         @Override
         protected void handleTempFailure() {
-            mTempFailHandler.handleTempFailure(mCurrentIkeSaRecord.getFutureRekeyEvent());
+            mTempFailHandler.handleTempFailure();
+            mCurrentIkeSaRecord.rescheduleRekey(RETRY_INTERVAL_MS);
         }
 
         /**
@@ -4666,21 +4666,23 @@
                             selectedDhGroup,
                             IkeSaPayload.createInitialIkeSaPayload(saProposals),
                             randomFactory);
+            if (localAddr instanceof Inet4Address) {
+                // Though RFC says Notify-NAT payload is "just after the Ni and Nr payloads (before
+                // the optional CERTREQ payload)", it also says recipient MUST NOT reject " messages
+                // in which the payloads were not in the "right" order" due to the lack of clarity
+                // of the payload order.
+                payloadList.add(
+                        new IkeNotifyPayload(
+                                NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP,
+                                IkeNotifyPayload.generateNatDetectionData(
+                                        initIkeSpi, respIkeSpi, localAddr, localPort)));
+                payloadList.add(
+                        new IkeNotifyPayload(
+                                NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP,
+                                IkeNotifyPayload.generateNatDetectionData(
+                                        initIkeSpi, respIkeSpi, remoteAddr, remotePort)));
+            }
 
-            // Though RFC says Notify-NAT payload is "just after the Ni and Nr payloads (before the
-            // optional CERTREQ payload)", it also says recipient MUST NOT reject " messages in
-            // which the payloads were not in the "right" order" due to the lack of clarity of the
-            // payload order.
-            payloadList.add(
-                    new IkeNotifyPayload(
-                            NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP,
-                            IkeNotifyPayload.generateNatDetectionData(
-                                    initIkeSpi, respIkeSpi, localAddr, localPort)));
-            payloadList.add(
-                    new IkeNotifyPayload(
-                            NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP,
-                            IkeNotifyPayload.generateNatDetectionData(
-                                    initIkeSpi, respIkeSpi, remoteAddr, remotePort)));
             return payloadList;
         }
 
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeSocket.java b/src/java/com/android/internal/net/ipsec/ike/IkeSocket.java
index 8553f30..1dbc9c1 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSocket.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSocket.java
@@ -27,11 +27,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.ipsec.ike.message.IkeHeader;
-import com.android.internal.net.ipsec.ike.utils.PacketReader;
 
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -53,7 +55,7 @@
  * IkeSessionStateMachine}. Since all {@link IkeSessionStateMachine}s run on the same working
  * thread, there will not be concurrent modification problems.
  */
-public abstract class IkeSocket extends PacketReader implements AutoCloseable {
+public abstract class IkeSocket implements AutoCloseable {
     private static final String TAG = "IkeSocket";
 
     /** Non-udp-encapsulated IKE packets MUST be sent to 500. */
@@ -63,6 +65,7 @@
 
     // Network this socket bound to.
     private final Network mNetwork;
+    private final Handler mHandler;
 
     // Map from locally generated IKE SPI to IkeSessionStateMachine instances.
     @VisibleForTesting
@@ -74,7 +77,7 @@
     protected final Set<IkeSessionStateMachine> mAliveIkeSessions = new HashSet<>();
 
     protected IkeSocket(Network network, Handler handler) {
-        super(handler);
+        mHandler = handler;
         mNetwork = network;
     }
 
@@ -107,6 +110,53 @@
         }
     }
 
+    /** Starts the packet reading poll-loop. */
+    public void start() {
+        // Start background reader thread
+        new Thread(
+                () -> {
+                    try {
+                        // Loop will exit and thread will quit when the retrieved fd is closed.
+                        // Receiving either EOF or an exception will exit this reader loop.
+                        // FileInputStream in uninterruptable, so there's no good way to ensure that
+                        // that this thread shuts down except upon FD closure.
+                        while (true) {
+                            byte[] intercepted = receiveFromFd();
+                            if (intercepted == null) {
+                                // Exit once we've hit EOF
+                                return;
+                            } else if (intercepted.length > 0) {
+                                // Only save packet if we've received any bytes.
+                                getIkeLog()
+                                        .d(
+                                                this.getClass().getSimpleName(),
+                                                "Received packet");
+                                mHandler.post(
+                                        () -> {
+                                            handlePacket(intercepted, intercepted.length);
+                                        });
+                            }
+                        }
+                    } catch (IOException ignored) {
+                        // Simply exit this reader thread
+                        return;
+                    }
+                }).start();
+    }
+
+    private byte[] receiveFromFd() throws IOException {
+        FileInputStream in = new FileInputStream(getFd());
+        byte[] inBytes = new byte[4096];
+        int bytesRead = in.read(inBytes);
+
+        if (bytesRead < 0) {
+            return null; // return null for EOF
+        } else if (bytesRead >= 4096) {
+            throw new IllegalStateException("Too big packet. Fragmentation unsupported");
+        }
+        return Arrays.copyOf(inBytes, bytesRead);
+    }
+
     /**
      * Returns the port that this IKE socket is listening on (bound to).
      */
@@ -117,6 +167,12 @@
 
     protected abstract FileDescriptor getFd();
 
+    protected FileDescriptor createFd() {
+        return getFd();
+    }
+
+    protected abstract void handlePacket(byte[] recvbuf, int length);
+
     /**
      * Return Network this socket bound to
      *
@@ -176,6 +232,12 @@
         stop();
     }
 
+    /** Stops the packet reading loop */
+    public void stop() {
+        // No additional cleanup at this time. Subclasses are in charge of closing their sockets,
+        // which will result in the packet reading poll loop exiting.
+    }
+
     /**
      * IPacketReceiver provides a package private interface for handling received packet.
      *
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocket.java b/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocket.java
index 2eb1010..11bb934 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocket.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocket.java
@@ -26,6 +26,7 @@
 import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.Network;
 import android.os.Handler;
+import android.os.Looper;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.LongSparseArray;
@@ -81,7 +82,10 @@
      * @return an IkeUdpEncapSocket instance
      */
     public static IkeUdpEncapSocket getIkeUdpEncapSocket(
-            Network network, IpSecManager ipsecManager, IkeSessionStateMachine ikeSession)
+            Network network,
+            IpSecManager ipsecManager,
+            IkeSessionStateMachine ikeSession,
+            Looper looper)
             throws ErrnoException, IOException, ResourceUnavailableException {
         IkeUdpEncapSocket ikeSocket = sNetworkToIkeSocketMap.get(network);
         if (ikeSocket == null) {
@@ -92,7 +96,7 @@
             Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM | SOCK_NONBLOCK);
             network.bindSocket(fd);
 
-            ikeSocket = new IkeUdpEncapSocket(udpEncapSocket, network, new Handler());
+            ikeSocket = new IkeUdpEncapSocket(udpEncapSocket, network, new Handler(looper));
 
             // Create and register FileDescriptor for receiving IKE packet on current thread.
             ikeSocket.start();
@@ -112,21 +116,10 @@
         return mUdpEncapSocket;
     }
 
+    /** Get FileDescriptor for use with the packet reader polling loop */
     @Override
     protected FileDescriptor getFd() {
-        return createFd(); // Returns cached FD
-    }
-
-    /**
-     * Get FileDescriptor of mUdpEncapSocket.
-     *
-     * <p>PacketReader registers a listener for this file descriptor on the thread where
-     * IkeUdpEncapSocket is constructed. When there is a read event, this listener is invoked and
-     * then calls {@link handlePacket} to handle the received packet.
-     */
-    @Override
-    protected FileDescriptor createFd() {
-        return mUdpEncapSocket.getFileDescriptor();
+        return mUdpEncapSocket.getFileDescriptor(); // Returns cached FD
     }
 
     /** Package private */
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeUdpSocket.java b/src/java/com/android/internal/net/ipsec/ike/IkeUdpSocket.java
index a0bb29a..b6130ad 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeUdpSocket.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeUdpSocket.java
@@ -46,20 +46,9 @@
         mSocket = socket;
     }
 
+    /** Get FileDescriptor for use with the packet reader polling loop */
     @Override
     protected FileDescriptor getFd() {
-        return createFd(); // Returns cached FD
-    }
-
-    /**
-     * Get FileDescriptor for use with the Packet Receiver.
-     *
-     * <p>PacketReader registers a listener for this file descriptor on the thread where
-     * IkeUdpSocket is constructed. When there is a read event, this listener is invoked and then
-     * calls {@link handlePacket} to handle the received packet.
-     */
-    @Override
-    protected FileDescriptor createFd() {
         return mSocket;
     }
 
diff --git a/src/java/com/android/internal/net/ipsec/ike/SaRecord.java b/src/java/com/android/internal/net/ipsec/ike/SaRecord.java
index a0b585f..c2d86dc 100644
--- a/src/java/com/android/internal/net/ipsec/ike/SaRecord.java
+++ b/src/java/com/android/internal/net/ipsec/ike/SaRecord.java
@@ -31,8 +31,6 @@
 import android.util.CloseGuard;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
-import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest;
 import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
 import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
 import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
@@ -82,10 +80,7 @@
     private final byte[] mSkEi;
     private final byte[] mSkEr;
 
-    private final SaLifetimeAlarmScheduler mSaLifetimeAlarmScheduler;
-
-    // TODO(b/149058810): Use AlarmManager and PendingIntent to schedule rekey
-    private final LocalRequest mFutureRekeyEvent;
+    @VisibleForTesting final SaLifetimeAlarmScheduler mSaLifetimeAlarmScheduler;
 
     private final CloseGuard mCloseGuard = new CloseGuard();
 
@@ -98,7 +93,6 @@
             byte[] skAr,
             byte[] skEi,
             byte[] skEr,
-            LocalRequest futureRekeyEvent,
             SaLifetimeAlarmScheduler saLifetimeAlarmScheduler) {
         isLocalInit = localInit;
         nonceInitiator = nonceInit;
@@ -117,9 +111,6 @@
         mSaLifetimeAlarmScheduler = saLifetimeAlarmScheduler;
         mSaLifetimeAlarmScheduler.scheduleLifetimeExpiryAlarm(getTag());
 
-        // TODO(b/149058810): Use alarmManager to schedule rekey event and remove mFutureRekeyEvent
-        mFutureRekeyEvent = futureRekeyEvent;
-
         mCloseGuard.open("close");
     }
 
@@ -167,6 +158,11 @@
         return isLocalInit ? mSkEr : mSkEi;
     }
 
+    /** Reschedule rekey */
+    public void rescheduleRekey(long retryDelayMs) {
+        mSaLifetimeAlarmScheduler.rescheduleRekey(retryDelayMs);
+    }
+
     /** Check that the SaRecord was closed properly. */
     @Override
     protected void finalize() throws Throwable {
@@ -178,17 +174,10 @@
 
     @Override
     public void close() {
-        mFutureRekeyEvent.cancel();
-
         mSaLifetimeAlarmScheduler.cancelLifetimeExpiryAlarm(getTag());
     }
 
     /** Package private */
-    LocalRequest getFutureRekeyEvent() {
-        return mFutureRekeyEvent;
-    }
-
-    /** Package private */
     @VisibleForTesting
     static void setSaRecordHelper(ISaRecordHelper helper) {
         sSaRecordHelper = helper;
@@ -336,7 +325,6 @@
                     skEr,
                     skPi,
                     skPr,
-                    ikeSaRecordConfig.futureRekeyEvent,
                     ikeSaRecordConfig.saLifetimeAlarmScheduler);
         }
 
@@ -471,8 +459,7 @@
                         skEr,
                         inTransform,
                         outTransform,
-                        childSaRecordConfig.futureRekeyEvent,
-                        childSaRecordConfig.saLifetimeAlarmSched);
+                        childSaRecordConfig.saLifetimeAlarmScheduler);
 
             } catch (Exception e) {
                 if (initTransform != null) initTransform.close();
@@ -560,9 +547,26 @@
                     AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + mDeleteDelayMs,
                     mDeleteSaIntent);
-            getIkeLog().d(tag, "Hard lifetime expiry alarm set for " + mDeleteDelayMs + "ms");
+            mAlarmManager.setExactAndAllowWhileIdle(
+                    AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() + mRekeyDelayMs,
+                    mRekeySaIntent);
 
-            // TODO: Schedule alarm for rekey
+            getIkeLog()
+                    .d(
+                            tag,
+                            "Lifetime alarm set: Hard lifetime ("
+                                    + mDeleteDelayMs
+                                    + "ms) Soft lifetime ("
+                                    + mRekeyDelayMs
+                                    + "ms)");
+        }
+
+        public void rescheduleRekey(long retryDelayMs) {
+            mAlarmManager.setExactAndAllowWhileIdle(
+                    AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() + retryDelayMs,
+                    mRekeySaIntent);
         }
 
         public void cancelLifetimeExpiryAlarm(String tag) {
@@ -591,8 +595,7 @@
         public final boolean isTransport;
         public final boolean isLocalInit;
         public final boolean hasIntegrityAlgo;
-        public final ChildLocalRequest futureRekeyEvent;
-        public final SaLifetimeAlarmScheduler saLifetimeAlarmSched;
+        public final SaLifetimeAlarmScheduler saLifetimeAlarmScheduler;
 
         ChildSaRecordConfig(
                 Context context,
@@ -607,8 +610,7 @@
                 byte[] skD,
                 boolean isTransport,
                 boolean isLocalInit,
-                ChildLocalRequest futureRekeyEvent,
-                SaLifetimeAlarmScheduler saLifetimeAlarmSched) {
+                SaLifetimeAlarmScheduler saLifetimeAlarmScheduler) {
             this.context = context;
             this.initSpi = initSpi;
             this.respSpi = respSpi;
@@ -622,8 +624,7 @@
             this.isTransport = isTransport;
             this.isLocalInit = isLocalInit;
             hasIntegrityAlgo = (integrityAlgo != null);
-            this.futureRekeyEvent = futureRekeyEvent;
-            this.saLifetimeAlarmSched = saLifetimeAlarmSched;
+            this.saLifetimeAlarmScheduler = saLifetimeAlarmScheduler;
         }
     }
 
@@ -663,7 +664,6 @@
                 byte[] skEr,
                 byte[] skPi,
                 byte[] skPr,
-                LocalRequest futureRekeyEvent,
                 SaLifetimeAlarmScheduler saLifetimeAlarmScheduler) {
             super(
                     localInit,
@@ -673,7 +673,6 @@
                     skAr,
                     skEi,
                     skEr,
-                    futureRekeyEvent,
                     saLifetimeAlarmScheduler);
 
             mInitiatorSpiResource = initSpi;
@@ -706,7 +705,6 @@
                 IkeMacPrf prf,
                 int integrityKeyLength,
                 int encryptionKeyLength,
-                LocalRequest futureRekeyEvent,
                 SaLifetimeAlarmScheduler saLifetimeAlarmScheduler)
                 throws GeneralSecurityException {
             return sSaRecordHelper.makeFirstIkeSaRecord(
@@ -719,7 +717,6 @@
                             integrityKeyLength,
                             encryptionKeyLength,
                             true /*isLocalInit*/,
-                            futureRekeyEvent,
                             saLifetimeAlarmScheduler));
         }
 
@@ -735,7 +732,6 @@
                 int integrityKeyLength,
                 int encryptionKeyLength,
                 boolean isLocalInit,
-                LocalRequest futureRekeyEvent,
                 SaLifetimeAlarmScheduler saLifetimeAlarmScheduler)
                 throws GeneralSecurityException {
             return sSaRecordHelper.makeRekeyedIkeSaRecord(
@@ -750,7 +746,6 @@
                             integrityKeyLength,
                             encryptionKeyLength,
                             isLocalInit,
-                            futureRekeyEvent,
                             saLifetimeAlarmScheduler));
         }
 
@@ -929,7 +924,6 @@
         public final int integrityKeyLength;
         public final int encryptionKeyLength;
         public final boolean isLocalInit;
-        public final LocalRequest futureRekeyEvent;
         public final SaLifetimeAlarmScheduler saLifetimeAlarmScheduler;
 
         IkeSaRecordConfig(
@@ -939,7 +933,6 @@
                 int integrityKeyLength,
                 int encryptionKeyLength,
                 boolean isLocalInit,
-                LocalRequest futureRekeyEvent,
                 SaLifetimeAlarmScheduler saLifetimeAlarmScheduler) {
             this.initSpi = initSpi;
             this.respSpi = respSpi;
@@ -947,7 +940,6 @@
             this.integrityKeyLength = integrityKeyLength;
             this.encryptionKeyLength = encryptionKeyLength;
             this.isLocalInit = isLocalInit;
-            this.futureRekeyEvent = futureRekeyEvent;
             this.saLifetimeAlarmScheduler = saLifetimeAlarmScheduler;
         }
     }
@@ -979,7 +971,6 @@
                 byte[] skEr,
                 IpSecTransform inTransform,
                 IpSecTransform outTransform,
-                ChildLocalRequest futureRekeyEvent,
                 SaLifetimeAlarmScheduler saLifetimeAlarmScheduler) {
             super(
                     localInit,
@@ -989,7 +980,6 @@
                     skAr,
                     skEi,
                     skEr,
-                    futureRekeyEvent,
                     saLifetimeAlarmScheduler);
 
             mInboundSpi = inSpi;
@@ -1017,7 +1007,6 @@
                 byte[] skD,
                 boolean isTransport,
                 boolean isLocalInit,
-                ChildLocalRequest futureRekeyEvent,
                 SaLifetimeAlarmScheduler saLifetimeAlarmScheduler)
                 throws GeneralSecurityException, ResourceUnavailableException,
                         SpiUnavailableException, IOException {
@@ -1037,7 +1026,6 @@
                             skD,
                             isTransport,
                             isLocalInit,
-                            futureRekeyEvent,
                             saLifetimeAlarmScheduler));
         }
 
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeCertReqPayload.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeCertReqPayload.java
new file mode 100644
index 0000000..92f3207
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeCertReqPayload.java
@@ -0,0 +1,97 @@
+/*
+ * 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkeCertPayload.CERT_ENCODING_LEN;
+import static com.android.internal.net.ipsec.ike.message.IkeCertPayload.CertificateEncoding;
+
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This class represents a Certificate Request Payload
+ *
+ * <p>A Certificate Request Payload provides suggestion for an end certificate to select. Receiver
+ * of this payload is allowed to send an alternate. It is possible that there is a preferred CA sent
+ * in the IkeCertReqPayload, but an alternate is still acceptable.
+ *
+ * <p>IKE library will always ignore this payload since only one end certificate can be configured
+ * by users.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.8">RFC 7296, Internet Key Exchange
+ *     Protocol Version 2 (IKEv2)</a>
+ */
+public class IkeCertReqPayload extends IkePayload {
+    /** Certificate encoding type */
+    @CertificateEncoding public final int certEncodingType;
+    /** Concatenated list of SHA-1 hashes of CAs' Subject Public Key Info */
+    public final byte[] caSubjectPublicKeyInforHashes;
+
+    /**
+     * Construct an instance of IkeCertReqPayload from decoding an inbound IKE packet.
+     *
+     * <p>NegativeArraySizeException and BufferUnderflowException will be caught in {@link
+     * IkeMessage}
+     *
+     * @param critical indicates if this payload is critical. Ignored in supported payload as
+     *     instructed by the RFC 7296.
+     * @param payloadBody payload body in byte array
+     * @throws IkeProtocolException if there is any error
+     */
+    public IkeCertReqPayload(boolean critical, byte[] payloadBody) throws IkeProtocolException {
+        super(PAYLOAD_TYPE_CERT_REQUEST, critical);
+
+        ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
+        certEncodingType = Byte.toUnsignedInt(inputBuffer.get());
+        caSubjectPublicKeyInforHashes = new byte[inputBuffer.remaining()];
+        inputBuffer.get(caSubjectPublicKeyInforHashes);
+    }
+
+    /**
+     * Encode Certificate Request Payload to ByteBuffer.
+     *
+     * @param nextPayload type of payload that follows this payload.
+     * @param byteBuffer destination ByteBuffer that stores encoded payload.
+     */
+    @Override
+    protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
+        encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
+
+        byteBuffer.put((byte) certEncodingType).put(caSubjectPublicKeyInforHashes);
+    }
+
+    /**
+     * Get entire payload length.
+     *
+     * @return entire payload length.
+     */
+    @Override
+    protected int getPayloadLength() {
+        return GENERIC_HEADER_LENGTH + CERT_ENCODING_LEN + caSubjectPublicKeyInforHashes.length;
+    }
+
+    /**
+     * Return the payload type as a String.
+     *
+     * @return the payload type as a String.
+     */
+    @Override
+    public String getTypeString() {
+        return "CertReq";
+    }
+}
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayload.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayload.java
index bb5f9a3..778faa4 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayload.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayload.java
@@ -538,7 +538,6 @@
             super(CONFIG_ATTR_INTERNAL_IP4_DHCP, value);
         }
 
-        @Override
         public Inet4Address getAddress() {
             return address;
         }
@@ -572,7 +571,6 @@
             super(CONFIG_ATTR_INTERNAL_IP4_DNS, value);
         }
 
-        @Override
         public Inet4Address getAddress() {
             return address;
         }
@@ -979,7 +977,6 @@
             super(CONFIG_ATTR_INTERNAL_IP6_DNS, value);
         }
 
-        @Override
         public Inet6Address getAddress() {
             return address;
         }
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
index 1c5f1f6..3da649a 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
@@ -503,10 +503,12 @@
 
                 int fragNum = i + 1; // 1-based
 
+                int fragFirstInnerPayload =
+                        i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT;
                 IkeSkfPayload skfPayload =
                         new IkeSkfPayload(
-                                ikeHeader,
-                                firstInnerPayload,
+                                skfHeader,
+                                fragFirstInnerPayload,
                                 unencryptedData,
                                 integrityMac,
                                 encryptCipher,
@@ -515,11 +517,7 @@
                                 fragNum,
                                 totalFragments);
 
-                packetList[i] =
-                        encodeHeaderAndBody(
-                                skfHeader,
-                                skfPayload,
-                                i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT);
+                packetList[i] = encodeHeaderAndBody(skfHeader, skfPayload, fragFirstInnerPayload);
                 getIkeLog()
                         .d(
                                 "IkeMessage",
@@ -528,7 +526,7 @@
                                         + "/"
                                         + totalFragments
                                         + "): "
-                                        + getIkeLog().pii(packetList[0]));
+                                        + getIkeLog().pii(packetList[i]));
             }
 
             return packetList;
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayload.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayload.java
index b908777..3014533 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayload.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayload.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.net.ipsec.ike.message;
 
+import static android.net.ipsec.ike.IkeManager.getIkeLog;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED;
@@ -74,6 +75,8 @@
  *     Version 2 (IKEv2)</a>
  */
 public final class IkeNotifyPayload extends IkeInformationalPayload {
+    private static final String TAG = IkeNotifyPayload.class.getSimpleName();
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
         NOTIFY_TYPE_ADDITIONAL_TS_POSSIBLE,
@@ -267,8 +270,7 @@
 
     private void validateNotifyPayloadForIkeAndNewChild() throws InvalidSyntaxException {
         if (protocolId != PROTOCOL_ID_UNSET) {
-            throw new InvalidSyntaxException(
-                    "Expected Procotol ID unset: Protocol ID is " + protocolId);
+            getIkeLog().w(TAG, "Expected Procotol ID unset: Protocol ID is " + protocolId);
         }
 
         if (notifyType == ERROR_TYPE_INVALID_SELECTORS
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkePayloadFactory.java b/src/java/com/android/internal/net/ipsec/ike/message/IkePayloadFactory.java
index 2f191d4..b50f093 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkePayloadFactory.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkePayloadFactory.java
@@ -60,7 +60,6 @@
                 int payloadType, boolean isCritical, boolean isResp, byte[] payloadBody)
                 throws IkeProtocolException {
             switch (payloadType) {
-                    // TODO: Add cases for creating supported payloads.
                 case IkePayload.PAYLOAD_TYPE_SA:
                     return new IkeSaPayload(isCritical, isResp, payloadBody);
                 case IkePayload.PAYLOAD_TYPE_KE:
@@ -71,6 +70,8 @@
                     return new IkeIdPayload(isCritical, payloadBody, false);
                 case IkePayload.PAYLOAD_TYPE_CERT:
                     return IkeCertPayload.getIkeCertPayload(isCritical, payloadBody);
+                case IkeCertReqPayload.PAYLOAD_TYPE_CERT_REQUEST:
+                    return new IkeCertReqPayload(isCritical, payloadBody);
                 case IkePayload.PAYLOAD_TYPE_AUTH:
                     return IkeAuthPayload.getIkeAuthPayload(isCritical, payloadBody);
                 case IkePayload.PAYLOAD_TYPE_NONCE:
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 d2dd394..40eecf0 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
@@ -514,7 +514,20 @@
         Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) throws IkeProtocolException;
     }
 
-    // TODO: Add another constructor for building outbound message.
+    /**
+     * Release IPsec SPI resources in the outbound Create Child request
+     *
+     * <p>This method is usually called when an IKE library fails to receive a Create Child response
+     * before it is terminated. It is also safe to call after the Create Child exchange has
+     * succeeded because the newly created IpSecTransform pair will hold the IPsec SPI resource.
+     */
+    public void releaseChildSpiResourcesIfExists() {
+        for (Proposal proposal : proposalList) {
+            if (proposal instanceof ChildProposal) {
+                proposal.releaseSpiResourceIfExists();
+            }
+        }
+    }
 
     /**
      * This class represents the common information of an IKE Proposal and a Child Proposal.
diff --git a/src/java/com/android/internal/net/ipsec/ike/utils/RandomnessFactory.java b/src/java/com/android/internal/net/ipsec/ike/utils/RandomnessFactory.java
index 9ebb057..2e5d170 100644
--- a/src/java/com/android/internal/net/ipsec/ike/utils/RandomnessFactory.java
+++ b/src/java/com/android/internal/net/ipsec/ike/utils/RandomnessFactory.java
@@ -31,7 +31,7 @@
 public class RandomnessFactory implements EapRandomFactory {
     // This constant is mirrored of android.net.NetworkCapabilities.TRANSPORT_TEST due to lack of
     // @TestApi guarantees in mainline modules
-    public static final int NETWORK_CAPABILITY_TRANSPORT_TEST = 7;
+    public static final int TRANSPORT_TEST = 7;
 
     private final boolean mIsTestModeEnabled;
 
@@ -41,8 +41,7 @@
         NetworkCapabilities networkCapabilities = connectManager.getNetworkCapabilities(network);
 
         mIsTestModeEnabled =
-                networkCapabilities != null
-                        && networkCapabilities.hasCapability(NETWORK_CAPABILITY_TRANSPORT_TEST);
+                networkCapabilities != null && networkCapabilities.hasTransport(TRANSPORT_TEST);
     }
 
     /**
diff --git a/tests/iketests/AndroidManifest.xml b/tests/iketests/AndroidManifest.xml
index 0abddba..bdcfdb7 100644
--- a/tests/iketests/AndroidManifest.xml
+++ b/tests/iketests/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.INTERNET"/>
     <!--Allow tests to call ConnectivityManager#getActiveNetwork()-->
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <!--Allow tests to use wake locks-->
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
 
     <!--
        'debuggable=true' is required to properly load mockito jvmti dependencies,
diff --git a/tests/iketests/src/java/android/net/ipsec/ike/SaProposalTest.java b/tests/iketests/src/java/android/net/ipsec/ike/SaProposalTest.java
index d4efb0c..0c98bfa 100644
--- a/tests/iketests/src/java/android/net/ipsec/ike/SaProposalTest.java
+++ b/tests/iketests/src/java/android/net/ipsec/ike/SaProposalTest.java
@@ -16,8 +16,15 @@
 
 package android.net.ipsec.ike;
 
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_NONE;
 import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
 import static android.net.ipsec.ike.SaProposal.KEY_LEN_UNUSED;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -318,4 +325,30 @@
                         new Transform[] {mIntegrityNoneTransform},
                         new Transform[] {mIntegrityHmacSha1Transform}));
     }
+
+    @Test
+    public void testIsNegotiatedFromProposalWithIntegrityNone() throws Exception {
+        SaProposal respProposal =
+                new IkeSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .addDhGroup(DH_GROUP_2048_BIT_MODP)
+                        .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC)
+                        .build();
+
+        SaProposal reqProposal =
+                new IkeSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_128)
+                        .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_NONE)
+                        .addDhGroup(DH_GROUP_1024_BIT_MODP)
+                        .addDhGroup(DH_GROUP_2048_BIT_MODP)
+                        .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC)
+                        .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_SHA2_256)
+                        .build();
+
+        assertTrue(respProposal.isNegotiatedFrom(reqProposal));
+    }
 }
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 20e4b76..e20d0b1 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
@@ -22,6 +22,7 @@
 import static android.system.OsConstants.AF_INET;
 
 import static com.android.internal.net.TestUtils.createMockRandomFactory;
+import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.RETRY_INTERVAL_MS;
 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;
@@ -47,11 +48,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
@@ -77,6 +76,7 @@
 import android.net.ipsec.ike.ChildSessionConfiguration;
 import android.net.ipsec.ike.ChildSessionParams;
 import android.net.ipsec.ike.IkeManager;
+import android.net.ipsec.ike.IkeSaProposal;
 import android.net.ipsec.ike.IkeTrafficSelector;
 import android.net.ipsec.ike.SaProposal;
 import android.net.ipsec.ike.TunnelModeChildSessionParams;
@@ -224,8 +224,9 @@
 
     private ArgumentMatcher<ChildLocalRequest> mRekeyChildLocalReqMatcher =
             (argument) -> {
-                return CMD_LOCAL_REQUEST_REKEY_CHILD == argument.procedureType
-                        && mMockChildSessionCallback == argument.childSessionCallback;
+                return (CMD_LOCAL_REQUEST_REKEY_CHILD == argument.procedureType
+                                && mMockChildSessionCallback == argument.childSessionCallback
+                        || CURRENT_CHILD_SA_SPI_OUT == argument.remoteSpi);
             };
 
     public ChildSessionStateMachineTest() {
@@ -270,19 +271,7 @@
 
         // Setup thread and looper
         mLooper = new TestLooper();
-        mChildSessionStateMachine =
-                new ChildSessionStateMachine(
-                        mLooper.getLooper(),
-                        mContext,
-                        IKE_SESSION_UNIQUE_ID,
-                        mMockAlarmManager,
-                        createMockRandomFactory(),
-                        mMockIpSecManager,
-                        mIpSecSpiGenerator,
-                        mChildSessionParams,
-                        mSpyUserCbExecutor,
-                        mMockChildSessionCallback,
-                        mMockChildSessionSmCallback);
+        mChildSessionStateMachine = buildChildSession(mChildSessionParams);
         mChildSessionStateMachine.setDbg(true);
         SaRecord.setSaRecordHelper(mMockSaRecordHelper);
 
@@ -396,7 +385,6 @@
                                 null,
                                 mock(IpSecTransform.class),
                                 mock(IpSecTransform.class),
-                                mock(ChildLocalRequest.class),
                                 mock(SaLifetimeAlarmScheduler.class)));
         doNothing().when(child).close();
         return child;
@@ -438,11 +426,7 @@
         assertFalse(childSaRecordConfig.isTransport);
         assertEquals(isLocalInit, childSaRecordConfig.isLocalInit);
         assertTrue(childSaRecordConfig.hasIntegrityAlgo);
-        assertEquals(
-                CMD_LOCAL_REQUEST_REKEY_CHILD, childSaRecordConfig.futureRekeyEvent.procedureType);
-        assertEquals(
-                mMockChildSessionCallback,
-                childSaRecordConfig.futureRekeyEvent.childSessionCallback);
+        assertNotNull(childSaRecordConfig.saLifetimeAlarmScheduler);
     }
 
     private void verifyNotifyUsersCreateIpSecSa(
@@ -492,8 +476,6 @@
 
         verify(mMockChildSessionSmCallback)
                 .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
-        verify(mMockChildSessionSmCallback)
-                .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
         verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
         assertTrue(
                 mChildSessionStateMachine.getCurrentState()
@@ -951,6 +933,14 @@
                 (IkeSaPayload)
                         (IkeTestUtils.hexStringToIkePayload(
                                 IkePayload.PAYLOAD_TYPE_SA, true, inboundSaHexString));
+
+        return makeInboundRekeyChildPayloads(remoteSpi, saPayload, isLocalInitRekey);
+    }
+
+    private List<IkePayload> makeInboundRekeyChildPayloads(
+            int remoteSpi, IkeSaPayload saPayload, boolean isLocalInitRekey) throws Exception {
+        List<IkePayload> inboundPayloads = new LinkedList<>();
+
         inboundPayloads.add(saPayload);
 
         // Build TS Payloads
@@ -1023,8 +1013,6 @@
                 .onChildSaCreated(
                         eq(mSpyLocalInitNewChildSaRecord.getRemoteSpi()),
                         eq(mChildSessionStateMachine));
-        verify(mMockChildSessionSmCallback)
-                .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
 
         verify(mMockSaRecordHelper)
                 .makeChildSaRecord(
@@ -1061,9 +1049,7 @@
         mLooper.dispatchAll();
 
         // Verify rekey has been rescheduled and Child Session is alive
-        verify(mMockChildSessionSmCallback)
-                .scheduleRetryLocalRequest(
-                        (ChildLocalRequest) mSpyCurrentChildSaRecord.getFutureRekeyEvent());
+        verify(mSpyCurrentChildSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
         assertTrue(
                 mChildSessionStateMachine.getCurrentState()
                         instanceof ChildSessionStateMachine.Idle);
@@ -1261,6 +1247,8 @@
                 IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
         mLooper.dispatchAll();
 
+        assertEquals(0, mChildSessionStateMachine.mSaProposal.getDhGroups().size());
+
         assertTrue(
                 mChildSessionStateMachine.getCurrentState()
                         instanceof ChildSessionStateMachine.RekeyChildRemoteDelete);
@@ -1287,8 +1275,6 @@
                 .onChildSaCreated(
                         eq(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()),
                         eq(mChildSessionStateMachine));
-        verify(mMockChildSessionSmCallback)
-                .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
 
         verify(mMockSaRecordHelper)
                 .makeChildSaRecord(
@@ -1674,4 +1660,123 @@
         verifyHandleFatalErrorAndQuit(IkeInternalException.class);
         verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
     }
+
+    @Test
+    public void testFirstChildLocalRekey() throws Exception {
+        ChildSaProposal saProposal = buildSaProposalWithDhGroup(SaProposal.DH_GROUP_2048_BIT_MODP);
+        ChildSessionParams childSessionParams =
+                new TunnelModeChildSessionParams.Builder()
+                        .addSaProposal(saProposal)
+                        .addInternalAddressRequest(AF_INET)
+                        .addInternalAddressRequest(INTERNAL_ADDRESS)
+                        .build();
+        mChildSessionStateMachine = buildChildSession(childSessionParams);
+        mChildSessionStateMachine.mIsFirstChild = true;
+        mChildSessionStateMachine.setDbg(true);
+        mChildSessionStateMachine.start();
+
+        setupIdleStateMachine();
+
+        assertEquals(0, mChildSessionStateMachine.mSaProposal.getDhGroups().size());
+
+        // Send Rekey-Create request
+        mChildSessionStateMachine.rekeyChildSession();
+        mLooper.dispatchAll();
+
+        assertTrue(
+                mChildSessionStateMachine.getCurrentState()
+                        instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+
+        verifyOutboundRekeyKePayload(false /*isResp*/);
+    }
+
+    private void verifyOutboundRekeyKePayload(boolean isResp) {
+        verify(mMockChildSessionSmCallback)
+                .onOutboundPayloadsReady(
+                        eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+                        eq(isResp),
+                        mPayloadListCaptor.capture(),
+                        eq(mChildSessionStateMachine));
+
+        // Verify outbound payload list
+        List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue();
+
+        assertNotNull(
+                IkePayload.getPayloadForTypeInProvidedList(
+                        PAYLOAD_TYPE_KE, IkeKePayload.class, reqPayloadList));
+    }
+
+    private ChildSessionStateMachine buildChildSession(ChildSessionParams childSessionParams) {
+        return new ChildSessionStateMachine(
+                mLooper.getLooper(),
+                mContext,
+                IKE_SESSION_UNIQUE_ID,
+                mMockAlarmManager,
+                createMockRandomFactory(),
+                mMockIpSecManager,
+                mIpSecSpiGenerator,
+                childSessionParams,
+                mSpyUserCbExecutor,
+                mMockChildSessionCallback,
+                mMockChildSessionSmCallback);
+    }
+
+    private ChildSaProposal buildSaProposalWithDhGroup(int dhGroup) {
+        return new ChildSaProposal.Builder()
+                .addEncryptionAlgorithm(
+                        SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+                .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+                .addDhGroup(dhGroup)
+                .build();
+    }
+
+    @Test
+    public void testRemoteRekeyWithKePayload() throws Exception {
+        // Use child session params with dh group to initiate the state machine
+        ChildSaProposal saProposal = buildSaProposalWithDhGroup(SaProposal.DH_GROUP_2048_BIT_MODP);
+        ChildSessionParams childSessionParams =
+                new TunnelModeChildSessionParams.Builder()
+                        .addSaProposal(saProposal)
+                        .addInternalAddressRequest(AF_INET)
+                        .addInternalAddressRequest(INTERNAL_ADDRESS)
+                        .build();
+        mChildSessionStateMachine = buildChildSession(childSessionParams);
+        mChildSessionStateMachine.setDbg(true);
+        mChildSessionStateMachine.start();
+
+        setupIdleStateMachine();
+
+        // Setup for new Child SA negotiation.
+        setUpSpiResource(LOCAL_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_IN);
+        setUpSpiResource(REMOTE_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT);
+
+        IkeSaPayload saPayload =
+                IkeSaPayload.createChildSaRequestPayload(
+                        new ChildSaProposal[] {saProposal}, mIpSecSpiGenerator, LOCAL_ADDRESS);
+        List<IkePayload> rekeyReqPayloads =
+                makeInboundRekeyChildPayloads(
+                        REMOTE_INIT_NEW_CHILD_SA_SPI_OUT, saPayload, false /*isLocalInitRekey*/);
+
+        rekeyReqPayloads.add(
+                new IkeKePayload(IkeSaProposal.DH_GROUP_2048_BIT_MODP, createMockRandomFactory()));
+
+        when(mMockSaRecordHelper.makeChildSaRecord(
+                        eq(rekeyReqPayloads), any(List.class), any(ChildSaRecordConfig.class)))
+                .thenReturn(mSpyRemoteInitNewChildSaRecord);
+
+        assertEquals(0, mChildSessionStateMachine.mSaProposal.getDhGroups().size());
+
+        // Receive rekey Child request
+        mChildSessionStateMachine.receiveRequest(
+                IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+        mLooper.dispatchAll();
+
+        assertTrue(
+                mChildSessionStateMachine.getCurrentState()
+                        instanceof ChildSessionStateMachine.RekeyChildRemoteDelete);
+
+        verifyOutboundRekeyKePayload(true /*isResp*/);
+
+        assertEquals(1, mChildSessionStateMachine.mSaProposal.getDhGroups().size());
+    }
 }
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 8fb4ebb..6fe4aad 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
@@ -18,7 +18,9 @@
 
 import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
@@ -28,7 +30,7 @@
 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.AbstractSessionStateMachine.RETRY_INTERVAL_MS;
 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET;
 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
 import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD;
@@ -67,6 +69,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -641,10 +644,25 @@
                 new byte[KEY_LEN_IKE_ENCR],
                 TestUtils.hexStringToByteArray(PRF_KEY_INIT_HEX_STRING),
                 TestUtils.hexStringToByteArray(PRF_KEY_RESP_HEX_STRING),
-                new IkeLocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE),
                 mock(SaLifetimeAlarmScheduler.class));
     }
 
+    private void mockScheduleRekey(SaLifetimeAlarmScheduler mockSaLifetimeAlarmScheduler) {
+        IkeLocalRequest rekeyReq =
+                new IkeLocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE);
+        doAnswer(
+                (invocation) -> {
+                        mIkeSessionStateMachine.sendMessageDelayed(
+                                IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE,
+                                rekeyReq,
+                                mIkeSessionStateMachine.mIkeSessionParams
+                                        .getSoftLifetimeMsInternal());
+                        return null;
+                })
+                .when(mockSaLifetimeAlarmScheduler)
+                .scheduleLifetimeExpiryAlarm(anyString());
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -755,13 +773,11 @@
         ikeSession.mLocalAddress = LOCAL_ADDRESS;
         assertEquals(REMOTE_ADDRESS, ikeSession.mRemoteAddress);
 
-        // Get socket instances used by the IkeSessionStateMachine by virtue of the caching.
-        mSpyIkeUdp4Socket = spy(IkeUdp4Socket.getInstance(mMockDefaultNetwork, ikeSession));
-        mSpyIkeUdp6Socket = spy(IkeUdp6Socket.getInstance(mMockDefaultNetwork, ikeSession));
-        mSpyIkeUdpEncapSocket =
-                spy(
-                        IkeUdpEncapSocket.getIkeUdpEncapSocket(
-                                mMockDefaultNetwork, mIpSecManager, ikeSession));
+        // Setup socket instances used by the IkeSessionStateMachine
+        // TODO: rename these from spy to mock.
+        mSpyIkeUdp4Socket = mock(IkeUdp4Socket.class);
+        mSpyIkeUdp6Socket = mock(IkeUdp6Socket.class);
+        mSpyIkeUdpEncapSocket = mock(IkeUdpEncapSocket.class);
 
         doNothing().when(mSpyIkeUdp4Socket).sendIkePacket(any(), any());
         doNothing().when(mSpyIkeUdp6Socket).sendIkePacket(any(), any());
@@ -1182,7 +1198,7 @@
         mLooper.dispatchAll();
 
         verify(mSpyCurrentIkeSocket).releaseReference(eq(mIkeSessionStateMachine));
-        verify(mSpyCurrentIkeSocket).close();
+        verify(mMockBusyWakelock).release();
     }
 
     @Test
@@ -1216,6 +1232,9 @@
                 .thenAnswer(
                         (invocation) -> {
                             captureAndReleaseIkeSpiResource(invocation, 2);
+                            mockScheduleRekey(mSpyCurrentIkeSaRecord.mSaLifetimeAlarmScheduler);
+                            mSpyCurrentIkeSaRecord.mSaLifetimeAlarmScheduler
+                                    .scheduleLifetimeExpiryAlarm(anyString());
                             return mSpyCurrentIkeSaRecord;
                         });
     }
@@ -1228,6 +1247,9 @@
                 .thenAnswer(
                         (invocation) -> {
                             captureAndReleaseIkeSpiResource(invocation, 4);
+                            mockScheduleRekey(rekeySaRecord.mSaLifetimeAlarmScheduler);
+                            rekeySaRecord.mSaLifetimeAlarmScheduler.scheduleLifetimeExpiryAlarm(
+                                    anyString());
                             return rekeySaRecord;
                         });
     }
@@ -1268,7 +1290,7 @@
     public void testEnableTestMode() throws Exception {
         doReturn(true)
                 .when(mMockNetworkCapabilities)
-                .hasCapability(RandomnessFactory.NETWORK_CAPABILITY_TRANSPORT_TEST);
+                .hasTransport(RandomnessFactory.TRANSPORT_TEST);
 
         IkeSessionStateMachine ikeSession = makeAndStartIkeSession(buildIkeSessionParams());
 
@@ -1281,7 +1303,7 @@
     public void testDisableTestMode() throws Exception {
         doReturn(false)
                 .when(mMockNetworkCapabilities)
-                .hasCapability(RandomnessFactory.NETWORK_CAPABILITY_TRANSPORT_TEST);
+                .hasTransport(RandomnessFactory.TRANSPORT_TEST);
 
         IkeSessionStateMachine ikeSession = makeAndStartIkeSession(buildIkeSessionParams());
 
@@ -1331,9 +1353,6 @@
         // Validate socket switched
         assertTrue(mIkeSessionStateMachine.mIkeSocket instanceof IkeUdpEncapSocket);
         verify(mSpyIkeUdp4Socket).unregisterIke(anyLong());
-
-        // Validate keepalive has started
-        verify(mMockSocketKeepalive).start(anyInt());
     }
 
     @Ignore
@@ -1403,7 +1422,7 @@
         assertEquals(KEY_LEN_IKE_PRF, ikeSaRecordConfig.prf.getKeyLength());
         assertEquals(KEY_LEN_IKE_INTE, ikeSaRecordConfig.integrityKeyLength);
         assertEquals(KEY_LEN_IKE_ENCR, ikeSaRecordConfig.encryptionKeyLength);
-        assertEquals(CMD_LOCAL_REQUEST_REKEY_IKE, ikeSaRecordConfig.futureRekeyEvent.procedureType);
+        assertNotNull(ikeSaRecordConfig.saLifetimeAlarmScheduler);
 
         // Validate NAT detection
         assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat);
@@ -1441,6 +1460,8 @@
 
     /** Initializes the mIkeSessionStateMachine in the IDLE state. */
     private void setupIdleStateMachine() throws Exception {
+        verify(mMockBusyWakelock).acquire();
+
         setIkeInitResults();
 
         mIkeSessionStateMachine.sendMessage(
@@ -1453,6 +1474,11 @@
 
         assertTrue(
                 mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+        verify(mMockBusyWakelock).release();
+
+        // For convenience to verify wakelocks in all other places.
+        reset(mMockBusyWakelock);
     }
 
     private void mockIkeInitAndTransitionToIkeAuth(State authState) throws Exception {
@@ -1614,6 +1640,7 @@
                 mIkeSessionStateMachine.getCurrentState()
                         instanceof IkeSessionStateMachine.ChildProcedureOngoing);
         verify(mMockChildSessionStateMachine).deleteChildSession();
+        verify(mMockBusyWakelock).acquire();
     }
 
     @Test
@@ -1648,6 +1675,7 @@
                 mIkeSessionStateMachine.getCurrentState()
                         instanceof IkeSessionStateMachine.ChildProcedureOngoing);
         verify(mMockChildSessionStateMachine).rekeyChildSession();
+        verify(mMockBusyWakelock).acquire();
     }
 
     @Test
@@ -1787,6 +1815,7 @@
         List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
         assertEquals(1, payloadList.size());
         assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0)));
+        verify(mMockBusyWakelock).acquire();
     }
 
     @Test
@@ -2501,6 +2530,48 @@
     }
 
     @Test
+    public void testAuthHandlesIkeErrorNotify() throws Exception {
+        mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+        verifyRetransmissionStarted();
+        resetMockIkeMessageHelper();
+
+        // Mock rejecting IKE AUTH with Authenticatio Failure notification
+        ReceivedIkePacket mockAuthFailPacket =
+                makeIkeAuthRespWithoutChildPayloads(
+                        Arrays.asList(new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED)));
+        mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockAuthFailPacket);
+        mLooper.dispatchAll();
+
+        // Verify IKE Session is closed properly
+        assertNull(mIkeSessionStateMachine.getCurrentState());
+        verify(mMockIkeSessionCallback)
+                .onClosedExceptionally(any(AuthenticationFailedException.class));
+    }
+
+    @Test
+    public void testAuthHandlesCreateChildErrorNotify() throws Exception {
+        mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+        verifyRetransmissionStarted();
+        resetMockIkeMessageHelper();
+
+        // Mock rejecting IKE AUTH with a Create Child error notification
+        ReceivedIkePacket mockAuthFailPacket =
+                makeIkeAuthRespWithoutChildPayloads(
+                        Arrays.asList(new IkeNotifyPayload(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE)));
+        mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockAuthFailPacket);
+        mLooper.dispatchAll();
+
+        // Verify IKE Session is closed properly
+        assertNull(mIkeSessionStateMachine.getCurrentState());
+
+        ArgumentCaptor<IkeProtocolException> captor =
+                ArgumentCaptor.forClass(IkeProtocolException.class);
+        verify(mMockIkeSessionCallback).onClosedExceptionally(captor.capture());
+        IkeProtocolException exception = captor.getValue();
+        assertEquals(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE, exception.getErrorType());
+    }
+
+    @Test
     public void testAuthPskHandleRespWithParsingError() throws Exception {
         mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
         verifyRetransmissionStarted();
@@ -3077,17 +3148,10 @@
         mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
         mLooper.dispatchAll();
 
-        // Verify IKE Session goes back to Idle
+        // Verify IKE Session goes back to Idle and retry is scheduled
+        verify(mSpyCurrentIkeSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
         assertTrue(
                 mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
-
-        // Move time forward to trigger retry
-        mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
-        mLooper.dispatchAll();
-
-        assertTrue(
-                mIkeSessionStateMachine.getCurrentState()
-                        instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
     }
 
     @Test
@@ -3189,6 +3253,40 @@
                         instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
     }
 
+    private void mockRescheduleRekey(IkeSaRecord spySaRecord) {
+        IkeLocalRequest rekeyReq =
+                new IkeLocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE);
+        doAnswer(
+                (invocation) -> {
+                        mIkeSessionStateMachine.sendMessageDelayed(
+                                IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE,
+                                rekeyReq,
+                                RETRY_INTERVAL_MS);
+                        return null;
+                })
+                .when(spySaRecord)
+                .rescheduleRekey(eq(RETRY_INTERVAL_MS));
+    }
+
+    @Test
+    public void testRekeyIkeLocalCreateHandleRespWithTempFailure() throws Exception {
+        setupIdleStateMachine();
+
+        // Send Rekey-Create request
+        mIkeSessionStateMachine.sendMessage(
+                IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+                new IkeLocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+        mLooper.dispatchAll();
+
+        // Mock sending TEMPORARY_FAILURE response
+        mockRcvTempFail();
+        mLooper.dispatchAll();
+
+        verify(mSpyCurrentIkeSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
+        assertTrue(
+                mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+    }
+
     private void mockCreateAndTransitionToRekeyDeleteLocal() {
         // Seed fake rekey data and force transition to RekeyIkeLocalDelete
         mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
@@ -4065,6 +4163,7 @@
 
         // Verify state machine quit properly
         assertNull(mIkeSessionStateMachine.getCurrentState());
+        verify(mMockBusyWakelock).release();
     }
 
     @Test
@@ -4392,24 +4491,6 @@
         mLooper.dispatchAll();
     }
 
-    @Test
-    public void testTempFailureHandlerScheduleRetry() throws Exception {
-        mockSendRekeyChildReq();
-
-        // Mock sending TEMPORARY_FAILURE response
-        mockRcvTempFail();
-
-        // Move time forward to trigger retry
-        mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
-        mLooper.dispatchAll();
-
-        // Verify that rekey is triggered again
-        assertTrue(
-                mIkeSessionStateMachine.getCurrentState()
-                        instanceof IkeSessionStateMachine.ChildProcedureOngoing);
-        verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
-    }
-
     @Ignore
     public void disableTestTempFailureHandlerTimeout() throws Exception {
         long currentTime = 0;
@@ -4437,28 +4518,48 @@
 
     @Test
     public void testTempFailureHandlerCancelTimer() throws Exception {
-        mockSendRekeyChildReq();
+        mockRescheduleRekey(mSpyCurrentIkeSaRecord);
+        setupIdleStateMachine();
+
+        // Send Rekey-Create request
+        mIkeSessionStateMachine.sendMessage(
+                IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+                new IkeLocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+        mLooper.dispatchAll();
+        verifyRetransmissionStarted();
 
         // Mock sending TEMPORARY_FAILURE response
         mockRcvTempFail();
+        mLooper.dispatchAll();
+        verify(mSpyCurrentIkeSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
+        assertTrue(
+                mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
 
         // Move time forward to trigger retry
         mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
         mLooper.dispatchAll();
-        verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+        assertTrue(
+                mIkeSessionStateMachine.getCurrentState()
+                        instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
 
-        // Mock sending a valid response
-        ReceivedIkePacket resp =
-                makeDummyEncryptedReceivedIkePacketWithPayloadList(
-                        mSpyCurrentIkeSaRecord,
-                        EXCHANGE_TYPE_CREATE_CHILD_SA,
-                        true /*isResp*/,
-                        new LinkedList<>());
-        mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+        // Prepare "rekeyed" SA
+        setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+
+        // Receive valid Rekey-Create response
+        ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
         mIkeSessionStateMachine.sendMessage(
-                IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+                IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
         mLooper.dispatchAll();
 
+        // Receive Delete response
+        ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
+                makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+        mIkeSessionStateMachine.sendMessage(
+                IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket);
+        mLooper.dispatchAll();
+        assertTrue(
+                mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
         // Move time forward
         mLooper.moveTimeForward(IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS);
         mLooper.dispatchAll();
@@ -4466,8 +4567,6 @@
         // Validate IKE Session is not closed
         assertTrue(
                 mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
-        // Validate no more retry
-        verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
     }
 
     @Ignore
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionTestBase.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionTestBase.java
index 3728f36..e4e8379 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionTestBase.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionTestBase.java
@@ -18,8 +18,11 @@
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
@@ -33,6 +36,7 @@
 import android.net.NetworkCapabilities;
 import android.net.SocketKeepalive;
 import android.os.Handler;
+import android.os.PowerManager;
 
 import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
 import com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver;
@@ -50,9 +54,12 @@
             (Inet4Address) (InetAddresses.parseNumericAddress("127.0.0.1"));
     protected static final String REMOTE_HOSTNAME = "ike.test.android.com";
 
+    protected PowerManager.WakeLock mMockBusyWakelock;
+
     protected MockIpSecTestUtils mMockIpSecTestUtils;
     protected Context mSpyContext;
     protected IpSecManager mIpSecManager;
+    protected PowerManager mPowerManager;
 
     protected ConnectivityManager mMockConnectManager;
     protected Network mMockDefaultNetwork;
@@ -74,6 +81,11 @@
                         any(Handler.class));
         doNothing().when(mSpyContext).unregisterReceiver(any(IkeAlarmReceiver.class));
 
+        mPowerManager = mock(PowerManager.class);
+        mMockBusyWakelock = mock(PowerManager.WakeLock.class);
+        doReturn(mPowerManager).when(mSpyContext).getSystemService(eq(PowerManager.class));
+        doReturn(mMockBusyWakelock).when(mPowerManager).newWakeLock(anyInt(), anyString());
+
         mMockConnectManager = mock(ConnectivityManager.class);
         mMockDefaultNetwork = mock(Network.class);
         doReturn(mMockDefaultNetwork).when(mMockConnectManager).getActiveNetwork();
@@ -102,6 +114,6 @@
                 .getNetworkCapabilities(any(Network.class));
         doReturn(false)
                 .when(mMockNetworkCapabilities)
-                .hasCapability(RandomnessFactory.NETWORK_CAPABILITY_TRANSPORT_TEST);
+                .hasTransport(RandomnessFactory.TRANSPORT_TEST);
     }
 }
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocketTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocketTest.java
index 8c0bf4d..439fa27 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocketTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeUdpEncapSocketTest.java
@@ -118,12 +118,12 @@
 
         IkeUdpEncapSocket ikeSocketOne =
                 IkeUdpEncapSocket.getIkeUdpEncapSocket(
-                        mMockNetwork, mSpyIpSecManager, mockIkeSessionOne);
+                        mMockNetwork, mSpyIpSecManager, mockIkeSessionOne, Looper.myLooper());
         assertEquals(1, ikeSocketOne.mAliveIkeSessions.size());
 
         IkeUdpEncapSocket ikeSocketTwo =
                 IkeUdpEncapSocket.getIkeUdpEncapSocket(
-                        mMockNetwork, mSpyIpSecManager, mockIkeSessionTwo);
+                        mMockNetwork, mSpyIpSecManager, mockIkeSessionTwo, Looper.myLooper());
         assertEquals(2, ikeSocketTwo.mAliveIkeSessions.size());
         assertEquals(ikeSocketOne, ikeSocketTwo);
 
@@ -154,12 +154,12 @@
 
         IkeUdpEncapSocket ikeSocketOne =
                 IkeUdpEncapSocket.getIkeUdpEncapSocket(
-                        mockNetworkOne, mSpyIpSecManager, mockIkeSessionOne);
+                        mockNetworkOne, mSpyIpSecManager, mockIkeSessionOne, Looper.myLooper());
         assertEquals(1, ikeSocketOne.mAliveIkeSessions.size());
 
         IkeUdpEncapSocket ikeSocketTwo =
                 IkeUdpEncapSocket.getIkeUdpEncapSocket(
-                        mockNetworkTwo, mSpyIpSecManager, mockIkeSessionTwo);
+                        mockNetworkTwo, mSpyIpSecManager, mockIkeSessionTwo, Looper.myLooper());
         assertEquals(1, ikeSocketTwo.mAliveIkeSessions.size());
 
         assertNotEquals(ikeSocketOne, ikeSocketTwo);
@@ -192,7 +192,10 @@
         // Send IKE packet
         IkeUdpEncapSocket ikeSocket =
                 IkeUdpEncapSocket.getIkeUdpEncapSocket(
-                        mMockNetwork, mSpyIpSecManager, mMockIkeSessionStateMachine);
+                        mMockNetwork,
+                        mSpyIpSecManager,
+                        mMockIkeSessionStateMachine,
+                        Looper.myLooper());
         ikeSocket.sendIkePacket(mDataOne, IPV4_LOOPBACK);
 
         byte[] receivedData = receive(mDummyRemoteServerFd);
@@ -227,7 +230,8 @@
                                         IkeUdpEncapSocket.getIkeUdpEncapSocket(
                                                 mMockNetwork,
                                                 mSpyIpSecManager,
-                                                mMockIkeSessionStateMachine));
+                                                mMockIkeSessionStateMachine,
+                                                mIkeThread.getLooper()));
                                 createLatch.countDown();
                                 Log.d("IkeUdpEncapSocketTest", "IkeUdpEncapSocket created.");
                             } catch (ErrnoException
@@ -253,20 +257,18 @@
             sendToIkeUdpEncapSocket(mDummyRemoteServerFd, mDataOne, IPV4_LOOPBACK);
             receiveLatch.await();
 
-            assertEquals(1, ikeSocket.numPacketsReceived());
             assertArrayEquals(mDataOne, packetReceiver.mReceivedData);
 
             // Send second packet.
             sendToIkeUdpEncapSocket(mDummyRemoteServerFd, mDataTwo, IPV4_LOOPBACK);
             receiveLatch.await();
 
-            assertEquals(2, ikeSocket.numPacketsReceived());
             assertArrayEquals(mDataTwo, packetReceiver.mReceivedData);
 
             // Close IkeUdpEncapSocket.
             TestCountDownLatch closeLatch = new TestCountDownLatch();
-            ikeSocket
-                    .getHandler()
+            mIkeThread
+                    .getThreadHandler()
                     .post(
                             () -> {
                                 ikeSocket.releaseReference(mMockIkeSessionStateMachine);
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 644ba06..b193099 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
@@ -24,6 +24,7 @@
 import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -38,8 +39,6 @@
 import android.net.ipsec.ike.SaProposal;
 
 import com.android.internal.net.TestUtils;
-import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
-import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest;
 import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecord;
 import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecordConfig;
 import com.android.internal.net.ipsec.ike.SaRecord.IIpSecTransformHelper;
@@ -148,9 +147,6 @@
     private IkeMacIntegrity mHmacSha1IntegrityMac;
     private IkeCipher mAesCbcCipher;
 
-    private LocalRequest mMockFutureRekeyIkeEvent;
-    private ChildLocalRequest mMockFutureRekeyChildEvent;
-
     private SaLifetimeAlarmScheduler mMockLifetimeAlarmScheduler;
 
     private SaRecordHelper mSaRecordHelper = new SaRecordHelper();
@@ -168,8 +164,6 @@
                                 SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
                                 SaProposal.KEY_LEN_AES_128));
 
-        mMockFutureRekeyIkeEvent = mock(LocalRequest.class);
-        mMockFutureRekeyChildEvent = mock(ChildLocalRequest.class);
         mMockLifetimeAlarmScheduler = mock(SaLifetimeAlarmScheduler.class);
     }
 
@@ -192,8 +186,7 @@
                         IKE_AUTH_ALGO_KEY_LEN,
                         IKE_ENCR_ALGO_KEY_LEN,
                         true /*isLocalInit*/,
-                        mMockFutureRekeyIkeEvent,
-                        mock(SaLifetimeAlarmScheduler.class));
+                        mMockLifetimeAlarmScheduler);
 
         int keyMaterialLen =
                 IKE_SK_D_KEY_LEN
@@ -225,10 +218,10 @@
                 TestUtils.hexStringToByteArray(IKE_SK_PRF_INIT_HEX_STRING), ikeSaRecord.getSkPi());
         assertArrayEquals(
                 TestUtils.hexStringToByteArray(IKE_SK_PRF_RESP_HEX_STRING), ikeSaRecord.getSkPr());
+        verify(mMockLifetimeAlarmScheduler).scheduleLifetimeExpiryAlarm(anyString());
 
         ikeSaRecord.close();
-
-        verify(mMockFutureRekeyIkeEvent).cancel();
+        verify(mMockLifetimeAlarmScheduler).cancelLifetimeExpiryAlarm(anyString());
     }
 
     // Test generating keying material and building IpSecTransform for making Child SA.
@@ -306,7 +299,6 @@
                         TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING),
                         false /*isTransport*/,
                         true /*isLocalInit*/,
-                        mMockFutureRekeyChildEvent,
                         mMockLifetimeAlarmScheduler);
 
         ChildSaRecord childSaRecord =
@@ -332,8 +324,10 @@
                 TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING),
                 childSaRecord.getInboundDecryptionKey());
 
+        verify(mMockLifetimeAlarmScheduler).scheduleLifetimeExpiryAlarm(anyString());
+
         childSaRecord.close();
-        verify(mMockFutureRekeyChildEvent).cancel();
+        verify(mMockLifetimeAlarmScheduler).cancelLifetimeExpiryAlarm(anyString());
 
         SaRecord.setIpSecTransformHelper(new IpSecTransformHelper());
     }
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertReqPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertReqPayloadTest.java
new file mode 100644
index 0000000..f859a82
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertReqPayloadTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkeCertPayload.CERTIFICATE_ENCODING_X509_CERT_SIGNATURE;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.util.Pair;
+
+import com.android.internal.net.TestUtils;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class IkeCertReqPayloadTest {
+    private static final int CERT_ENCODING_TYPE = CERTIFICATE_ENCODING_X509_CERT_SIGNATURE;
+    private static final int NEXT_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_AUTH;
+    private static final byte[] CERT_REQ_PAYLOAD =
+            TestUtils.hexStringToByteArray("27000019040d0a12bb1f98996563f15b10db95c67eea7990fa");
+    private static final byte[] CA_SUBJECT_PUBLIC_KEY_INFO_HASH =
+            TestUtils.hexStringToByteArray("0d0a12bb1f98996563f15b10db95c67eea7990fa");
+
+    @Test
+    public void testDecode() throws Exception {
+        Pair<IkePayload, Integer> pair =
+                IkePayloadFactory.getIkePayload(
+                        IkePayload.PAYLOAD_TYPE_CERT_REQUEST,
+                        false /*isResp*/,
+                        ByteBuffer.wrap(CERT_REQ_PAYLOAD));
+
+        IkeCertReqPayload certPayload = (IkeCertReqPayload) pair.first;
+        assertEquals(CERT_ENCODING_TYPE, certPayload.certEncodingType);
+        assertArrayEquals(
+                CA_SUBJECT_PUBLIC_KEY_INFO_HASH, certPayload.caSubjectPublicKeyInforHashes);
+
+        assertEquals(NEXT_PAYLOAD_TYPE, (int) pair.second);
+    }
+
+    @Test
+    public void testEncode() throws Exception {
+        Pair<IkePayload, Integer> pair =
+                IkePayloadFactory.getIkePayload(
+                        IkePayload.PAYLOAD_TYPE_CERT_REQUEST,
+                        false /*isResp*/,
+                        ByteBuffer.wrap(CERT_REQ_PAYLOAD));
+        IkeCertReqPayload certPayload = (IkeCertReqPayload) pair.first;
+
+        ByteBuffer byteBuffer = ByteBuffer.allocate(CERT_REQ_PAYLOAD.length);
+        certPayload.encodeToByteBuffer(NEXT_PAYLOAD_TYPE, byteBuffer);
+        assertArrayEquals(CERT_REQ_PAYLOAD, byteBuffer.array());
+
+        assertEquals(CERT_REQ_PAYLOAD.length, certPayload.getPayloadLength());
+    }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java
index 884c76e..0b3c358 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java
@@ -85,19 +85,6 @@
     }
 
     @Test
-    public void testDecodeNotifyPayloadThrowException() throws Exception {
-        byte[] inputPacket =
-                TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING);
-        // Change Protocol ID to ESP
-        inputPacket[PROTOCOL_ID_OFFSET] = (byte) (IkePayload.PROTOCOL_ID_ESP & 0xFF);
-        try {
-            IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket);
-            fail("Expected InvalidSyntaxException: Protocol ID should not be ESP");
-        } catch (InvalidSyntaxException expected) {
-        }
-    }
-
-    @Test
     public void testGenerateNatDetectionData() throws Exception {
         long initiatorIkeSpi = Long.parseLong(IKE_INITIATOR_SPI_HEX_STRING, 16);
         long responderIkespi = Long.parseLong(IKE_RESPODNER_SPI_HEX_STRING, 16);