Merge "Send UPDATE_SA_ADDRESSES req on entering Mobike State."
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 b523c4c..5b67b9b 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -40,6 +40,7 @@
 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.IkeNotifyPayload.NOTIFY_TYPE_SIGNATURE_HASH_ALGORITHMS;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_UPDATE_SA_ADDRESSES;
 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;
@@ -524,7 +525,7 @@
     @VisibleForTesting final State mRekeyIkeRemoteDelete = new RekeyIkeRemoteDelete();
     @VisibleForTesting final State mDeleteIkeLocalDelete = new DeleteIkeLocalDelete();
     @VisibleForTesting final State mDpdIkeLocalInfo = new DpdIkeLocalInfo();
-    @VisibleForTesting final State mMobikeLocalMigrate = new MobikeLocalMigrateState();
+    @VisibleForTesting final State mMobikeLocalInfo = new MobikeLocalInfo();
 
     /** Constructor for testing. */
     @VisibleForTesting
@@ -637,7 +638,7 @@
             addState(mRekeyIkeRemoteDelete, mKillIkeSessionParent);
             addState(mDeleteIkeLocalDelete, mKillIkeSessionParent);
             addState(mDpdIkeLocalInfo, mKillIkeSessionParent);
-            addState(mMobikeLocalMigrate, mKillIkeSessionParent);
+            addState(mMobikeLocalInfo, mKillIkeSessionParent);
         // CHECKSTYLE:ON IndentationCheck
 
         setInitialState(mInitial);
@@ -1336,7 +1337,7 @@
                     transitionTo(mChildProcedureOngoing);
                     break;
                 case CMD_LOCAL_REQUEST_MOBIKE:
-                    transitionTo(mMobikeLocalMigrate);
+                    transitionTo(mMobikeLocalInfo);
                     break;
                 default:
                     cleanUpAndQuit(
@@ -5010,12 +5011,51 @@
     }
 
     /** MobikeLocalMigrateState initiates an UPDATE_SA_ADDRESSES exchange for the IKE Session. */
-    class MobikeLocalMigrateState extends DeleteBase {
-        // TODO(b/172014224): handle retransmission for the UPDATE_SA_ADDRESSES request
+    class MobikeLocalInfo extends DeleteBase {
+        private Retransmitter mRetransmitter;
 
         @Override
         public void enterState() {
-            // TODO(b/172014224): send UPDATE_SA_ADDRESSES req to peer
+            mRetransmitter = new EncryptedRetransmitter(buildUpdateSaAddressesReq());
+        }
+
+        private IkeMessage buildUpdateSaAddressesReq() {
+            // Generics required for addNatDetectionPayloadsToList that takes List<IkePayload> and
+            // buildEncryptedInformationalMessage that takes InformationalPayload[].
+            List<? super IkeInformationalPayload> payloadList = new ArrayList<>();
+            payloadList.add(new IkeNotifyPayload(NOTIFY_TYPE_UPDATE_SA_ADDRESSES));
+
+            // Only bother sending NAT detection payloads if NAT-T is supported in this IKE Session
+            if (mSupportNatTraversal) {
+                addNatDetectionPayloadsToList(
+                        (List<IkePayload>) payloadList,
+                        mLocalAddress,
+                        mRemoteAddress,
+                        mLocalPort,
+                        mIkeSocket.getIkeServerPort(),
+                        mCurrentIkeSaRecord.getInitiatorSpi(),
+                        mCurrentIkeSaRecord.getResponderSpi());
+            }
+
+            return buildEncryptedInformationalMessage(
+                    mCurrentIkeSaRecord,
+                    payloadList.toArray(new IkeInformationalPayload[payloadList.size()]),
+                    false /* isResp */,
+                    mCurrentIkeSaRecord.getLocalRequestMessageId());
+        }
+
+        @Override
+        protected void triggerRetransmit() {
+            mRetransmitter.retransmit();
+        }
+
+        @Override
+        public void exitState() {
+            super.exitState();
+
+            if (mRetransmitter != null) {
+                mRetransmitter.stopRetransmitting();
+            }
         }
 
         @Override
@@ -5030,6 +5070,32 @@
         }
     }
 
+    private static void addNatDetectionPayloadsToList(
+            List<IkePayload> payloadList,
+            InetAddress localAddr,
+            InetAddress remoteAddr,
+            int localPort,
+            int remotePort,
+            long initIkeSpi,
+            long respIkeSpi) {
+        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)));
+        }
+    }
+
     /**
      * Helper class to generate IKE SA creation payloads, in both request and response directions.
      */
@@ -5050,22 +5116,15 @@
                             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)));
-            }
+
+            addNatDetectionPayloadsToList(
+                    payloadList,
+                    localAddr,
+                    remoteAddr,
+                    localPort,
+                    remotePort,
+                    initIkeSpi,
+                    respIkeSpi);
 
             return payloadList;
         }
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 f2f365e..4617932 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
@@ -139,6 +139,12 @@
      * the request/response of IKE_AUTH exchange.
      */
     public static final int NOTIFY_TYPE_MOBIKE_SUPPORTED = 16396;
+    /**
+     * Used for notifying the Responder that an address change has occurred during a MOBIKE-enabled
+     * IKE Session. Only allowed in Informational exchanges sent after the IKE_AUTH exchange has
+     * finished.
+     */
+    public static final int NOTIFY_TYPE_UPDATE_SA_ADDRESSES = 16400;
 
     /**
      * Used in any INFORMATIONAL request for return routability check purposes when performing
@@ -228,6 +234,7 @@
         NOTIFY_TYPE_TO_STRING.put(
                 NOTIFY_TYPE_ESP_TFC_PADDING_NOT_SUPPORTED, "ESP TCP Padding not supported");
         NOTIFY_TYPE_TO_STRING.put(NOTIFY_TYPE_MOBIKE_SUPPORTED, "MOBIKE supported");
+        NOTIFY_TYPE_TO_STRING.put(NOTIFY_TYPE_UPDATE_SA_ADDRESSES, "UPDATE_SA_ADDRESSES");
         NOTIFY_TYPE_TO_STRING.put(NOTIFY_TYPE_COOKIE2, "COOKIE2");
         NOTIFY_TYPE_TO_STRING.put(
                 NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED, "Fragmentation supported");
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 ce61fd2..904f8a1 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
@@ -58,6 +58,7 @@
 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_SIGNATURE_HASH_ALGORITHMS;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_UPDATE_SA_ADDRESSES;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA;
@@ -5553,16 +5554,58 @@
         assertEquals(expectedState, mIkeSessionStateMachine.getCurrentState());
     }
 
-    @Test
-    public void testSetNetworkIdleState() throws Exception {
+    private IkeNetworkCallbackBase setupIdleStateMachineWithMobike() throws Exception {
         IkeNetworkCallbackBase callback =
                 verifyMobikeEnabled(true /* doesPeerSupportMobike */, mMockDefaultNetwork);
+
+        // reset IkeMessageHelper to make verifying outbound req easier
+        resetMockIkeMessageHelper();
+
         mIkeSessionStateMachine.sendMessage(
                 IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
         mLooper.dispatchAll();
 
+        return callback;
+    }
+
+    @Test
+    public void testSetNetworkIdleState() throws Exception {
+        IkeNetworkCallbackBase callback = setupIdleStateMachineWithMobike();
+
         verifySetNetwork(
-                callback, null /* rekeySaRecord */, mIkeSessionStateMachine.mMobikeLocalMigrate);
+                callback, null /* rekeySaRecord */, mIkeSessionStateMachine.mMobikeLocalInfo);
+
+        // Verify outbound UPDATE_SA_ADDRESSES req is sent in MobikeLocalMigrate
+        verifyUpdateSaAddressesReq(true /* expectNatDetection */);
+    }
+
+    @Test
+    public void testSetNetworkIdleStateWithoutNatDetection() throws Exception {
+        IkeNetworkCallbackBase callback = setupIdleStateMachineWithMobike();
+
+        mIkeSessionStateMachine.mSupportNatTraversal = false;
+        mIkeSessionStateMachine.mLocalNatDetected = false;
+        mIkeSessionStateMachine.mRemoteNatDetected = false;
+
+        verifySetNetwork(
+                callback,
+                mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord,
+                mIkeSessionStateMachine.mMobikeLocalInfo);
+
+        // Verify outbound UPDATE_SA_ADDRESSES req is sent in MobikeLocalMigrate
+        verifyUpdateSaAddressesReq(false /* expectNatDetection */);
+    }
+
+    private void verifyUpdateSaAddressesReq(boolean expectNatDetection) {
+        List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(false /* isResp */);
+        int expectedPayloads = expectNatDetection ? 3 : 1;
+        assertEquals(expectedPayloads, payloadList.size());
+        assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_UPDATE_SA_ADDRESSES));
+
+        if (expectNatDetection) {
+            assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
+            assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
+        }
     }
 
     @Test