Merge "Fix NullPointException in creating first Child"
diff --git a/Android.bp b/Android.bp
index f954c1c..7445067 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,8 +14,8 @@
 
 java_sdk_library {
     name: "android.net.ipsec.ike",
+    defaults: ["framework-module-defaults"],
     installable: true,
-    sdk_version: "module_current",
 
     aidl: {
         local_include_dirs: ["src/java"],
@@ -28,7 +28,6 @@
 
     libs: [
         "unsupportedappusage",
-        "framework-annotations-lib",
     ],
 
     api_packages: [
@@ -41,13 +40,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,32 +85,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",
-    ],
-    dist: { dest: "android.net.ipsec.ike.txt" },
-}
-
-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"],
-    dist: { dest: "android.net.ipsec.ike.jar" },
-}
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 c92f465..fd500b8 100644
--- a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
@@ -773,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:
@@ -882,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,
@@ -919,6 +924,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     /**
@@ -974,6 +984,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     /**
@@ -1355,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();
                             }
@@ -1396,6 +1414,11 @@
             }
             handleChildFatalError(exception);
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     private ChildSaProposal addDhGroupsFromChildSessionParamsIfAbsent() {
@@ -1564,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:
@@ -2046,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/IkeSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
index 4fa2da5..e86bc84 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -951,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();
         }
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.