Merge changes I21e0b157,I8c90e134

* changes:
  Create IkeSocket using v6 UDP socket and dest port 4500
  Move common test code to IkeSocketTestBase
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 5b67b9b..d3735d2 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -3194,16 +3194,13 @@
                 List<IkeNotifyPayload> natSourcePayloads,
                 IkeNotifyPayload natDestPayload)
                 throws InvalidSyntaxException, IOException {
-            if (!natSourcePayloads.isEmpty() && natDestPayload != null) {
-                mSupportNatTraversal = true;
-            } else if (natSourcePayloads.isEmpty() && natDestPayload == null) {
+            if (!didPeerIncludeNattDetectionPayloads(natSourcePayloads, natDestPayload)) {
                 mSupportNatTraversal = false;
                 return;
-            } else {
-                throw new InvalidSyntaxException(
-                        "Missing source or destination NAT detection notification");
             }
 
+            mSupportNatTraversal = true;
+
             if (!(mRemoteAddress instanceof Inet4Address)) {
                 // UDP encapsulation not (currently) supported on IPv6. Even if there is a NAT on
                 // IPv6, the best we can currently do is try non-encap'd anyways
@@ -3214,24 +3211,8 @@
             long initIkeSpi = respMsg.ikeHeader.ikeInitiatorSpi;
             long respIkeSpi = respMsg.ikeHeader.ikeResponderSpi;
 
-            // Check if local node is behind NAT
-            byte[] expectedLocalNatData =
-                    IkeNotifyPayload.generateNatDetectionData(
-                            initIkeSpi, respIkeSpi, mLocalAddress, mLocalPort);
-            mLocalNatDetected = !Arrays.equals(expectedLocalNatData, natDestPayload.notifyData);
-
-            // Check if the remote node is behind NAT
-            byte[] expectedRemoteNatData =
-                    IkeNotifyPayload.generateNatDetectionData(
-                            initIkeSpi, respIkeSpi, mRemoteAddress, mIkeSocket.getIkeServerPort());
-            mRemoteNatDetected = true;
-            for (IkeNotifyPayload natPayload : natSourcePayloads) {
-                // If none of the received hash matches the expected value, the remote node is
-                // behind NAT.
-                if (Arrays.equals(expectedRemoteNatData, natPayload.notifyData)) {
-                    mRemoteNatDetected = false;
-                }
-            }
+            updateLocalAndRemoteNatDetected(
+                    initIkeSpi, respIkeSpi, natSourcePayloads, natDestPayload);
 
             if (mLocalNatDetected || mRemoteNatDetected) {
                 logd("Switching to UDP encap socket");
@@ -3306,6 +3287,54 @@
     }
 
     /**
+     * Returns if the peer included NAT-T detection payloads
+     *
+     * @throws InvalidSyntaxException if an invalid combination of NAT-T detection payloads are
+     *     received.
+     */
+    private boolean didPeerIncludeNattDetectionPayloads(
+            List<IkeNotifyPayload> natSourcePayloads, IkeNotifyPayload natDestPayload)
+            throws InvalidSyntaxException {
+        if (!natSourcePayloads.isEmpty() && natDestPayload != null) {
+            return true;
+        } else if (natSourcePayloads.isEmpty() && natDestPayload == null) {
+            return false;
+        } else {
+            throw new InvalidSyntaxException(
+                    "Missing source or destination NAT detection notification");
+        }
+    }
+
+    /**
+     * Updates whether the local or remote peer are behind NATs. Assumes that mRemoteAddress is an
+     * IPv4 address.
+     */
+    private void updateLocalAndRemoteNatDetected(
+            long initIkeSpi,
+            long respIkeSpi,
+            List<IkeNotifyPayload> natSourcePayloads,
+            IkeNotifyPayload natDestPayload) {
+        // Check if local node is behind NAT
+        byte[] expectedLocalNatData =
+                IkeNotifyPayload.generateNatDetectionData(
+                        initIkeSpi, respIkeSpi, mLocalAddress, mLocalPort);
+        mLocalNatDetected = !Arrays.equals(expectedLocalNatData, natDestPayload.notifyData);
+
+        // Check if the remote node is behind NAT
+        byte[] expectedRemoteNatData =
+                IkeNotifyPayload.generateNatDetectionData(
+                        initIkeSpi, respIkeSpi, mRemoteAddress, mIkeSocket.getIkeServerPort());
+        mRemoteNatDetected = true;
+        for (IkeNotifyPayload natPayload : natSourcePayloads) {
+            // If none of the received hash matches the expected value, the remote node is
+            // behind NAT.
+            if (Arrays.equals(expectedRemoteNatData, natPayload.notifyData)) {
+                mRemoteNatDetected = false;
+            }
+        }
+    }
+
+    /**
      * CreateIkeLocalIkeAuthBase represents the common state and functionality required to perform
      * IKE AUTH exchanges in both the EAP and non-EAP flows.
      */
@@ -5065,8 +5094,101 @@
         }
 
         @Override
-        public void handleResponseIkeMessage(IkeMessage msg) {
-            // TODO(b/172014224): handle resp from peer
+        public void handleResponseIkeMessage(IkeMessage resp) {
+            mRetransmitter.stopRetransmitting();
+
+            try {
+                validateResp(resp);
+
+                // TODO(b/172015298): migrate Child SAs or schedule rekey
+                // TODO(b/172013873): notify caller of Network Change, IPsec SA changes
+
+                transitionTo(mIdle);
+            } catch (IkeProtocolException | IOException e) {
+                handleIkeFatalError(e);
+            }
+        }
+
+        private void validateResp(IkeMessage resp) throws IkeProtocolException, IOException {
+            if (resp.ikeHeader.exchangeType != IkeHeader.EXCHANGE_TYPE_INFORMATIONAL) {
+                throw new InvalidSyntaxException(
+                        "Invalid exchange type; expected INFORMATIONAL, but got: "
+                                + resp.ikeHeader.exchangeType);
+            }
+
+            List<IkeNotifyPayload> natSourcePayloads = new ArrayList<>();
+            IkeNotifyPayload natDestPayload = null;
+
+            for (IkePayload payload : resp.ikePayloadList) {
+                switch (payload.payloadType) {
+                    case PAYLOAD_TYPE_NOTIFY:
+                        IkeNotifyPayload notifyPayload = (IkeNotifyPayload) payload;
+                        if (notifyPayload.isErrorNotify()) {
+                            // TODO(b/): handle UNACCEPTABLE_ADDRESSES payload
+                            throw notifyPayload.validateAndBuildIkeException();
+                        }
+
+                        switch (notifyPayload.notifyType) {
+                            case NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP:
+                                natSourcePayloads.add(notifyPayload);
+                                break;
+                            case NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP:
+                                if (natDestPayload != null) {
+                                    throw new InvalidSyntaxException(
+                                            "More than one"
+                                                    + " NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP"
+                                                    + " found");
+                                }
+                                natDestPayload = notifyPayload;
+                                break;
+                            default:
+                                // Unknown and unexpected status notifications are ignored as per
+                                // RFC7296.
+                                logw(
+                                        "Received unknown or unexpected status notifications with"
+                                                + " notify type: "
+                                                + notifyPayload.notifyType);
+                        }
+
+                        break;
+                    default:
+                        logw("Unexpected payload types found: " + payload.payloadType);
+                }
+            }
+
+            // Only handle NAT detection payloads if the peer indicates NAT-T support in IKE_INIT
+            if (mSupportNatTraversal) {
+                handleNatDetection(resp, natSourcePayloads, natDestPayload);
+            }
+        }
+
+        /**
+         * If MOBIKE and NAT-T are supported, we will already be using an IkeUdpEncapSocket for IPv4
+         * addresses. The only thing to do here is update our state for if the local and remote are
+         * behind NATs.
+         */
+        private void handleNatDetection(
+                IkeMessage resp,
+                List<IkeNotifyPayload> natSourcePayloads,
+                IkeNotifyPayload natDestPayload)
+                throws InvalidSyntaxException {
+            if (!didPeerIncludeNattDetectionPayloads(natSourcePayloads, natDestPayload)) {
+                // peer didn't include NAT-T detection payloads. NATT still supported for this
+                // session though
+                return;
+            }
+
+            if (!(mRemoteAddress instanceof Inet4Address)) {
+                // UDP encapsulation not (currently) supported on IPv6. Even if there is a NAT on
+                // IPv6, the best we can currently do is try non-encap'd anyways
+                return;
+            }
+
+            updateLocalAndRemoteNatDetected(
+                    resp.ikeHeader.ikeInitiatorSpi,
+                    resp.ikeHeader.ikeResponderSpi,
+                    natSourcePayloads,
+                    natDestPayload);
         }
     }
 
@@ -5215,6 +5337,12 @@
             // Only switch the IkeSocket if the underlying Network actually changes. This may not
             // always happen (ex: the underlying Network loses the current local address)
             if (!mNetwork.equals(oldNetwork)) {
+                // Changing IkeSockets - make sure to quit NAT-T keepalive if it's going
+                if (mIkeNattKeepalive != null) {
+                    mIkeNattKeepalive.stop();
+                    mIkeNattKeepalive = null;
+                }
+
                 IkeSocket newSocket;
                 // TODO(b/173237734): use port 4500 if NAT-T is enabled
                 if (isIpv4) {
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 904f8a1..5fa5a30 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
@@ -150,6 +150,7 @@
 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;
+import com.android.internal.net.ipsec.ike.keepalive.IkeNattKeepalive;
 import com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload;
 import com.android.internal.net.ipsec.ike.message.IkeAuthPayload;
 import com.android.internal.net.ipsec.ike.message.IkeAuthPskPayload;
@@ -355,6 +356,8 @@
     private IkeUdp6Socket mSpyIkeUdp6Socket;
     private IkeSocket mSpyCurrentIkeSocket;
 
+    private IkeNattKeepalive mMockIkeNattKeepalive;
+
     private TestLooper mLooper;
     private IkeSessionStateMachine mIkeSessionStateMachine;
 
@@ -811,6 +814,8 @@
         mExpectedCurrentSaRemoteReqMsgId = 0;
 
         mMockIke3gppCallback = mock(Ike3gppCallback.class);
+
+        mMockIkeNattKeepalive = mock(IkeNattKeepalive.class);
     }
 
     @After
@@ -1601,6 +1606,7 @@
         mIkeSessionStateMachine.mSupportNatTraversal = true;
         mIkeSessionStateMachine.mLocalNatDetected = true;
         mIkeSessionStateMachine.mRemoteNatDetected = false;
+        mIkeSessionStateMachine.mIkeNattKeepalive = mMockIkeNattKeepalive;
         mIkeSessionStateMachine.mSupportFragment = true;
         mIkeSessionStateMachine.mRemoteVendorIds =
                 Arrays.asList(REMOTE_VENDOR_ID_ONE, REMOTE_VENDOR_ID_TWO);
@@ -5574,38 +5580,6 @@
 
         verifySetNetwork(
                 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
@@ -5720,4 +5694,112 @@
                 mIkeSessionStateMachine.getCurrentState()
                         instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
     }
+
+    @Test
+    public void testMobikeLocalInfoSendsRequest() throws Exception {
+        setupIdleStateMachineWithMobike();
+
+        mIkeSessionStateMachine.sendMessage(
+                CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
+        mLooper.dispatchAll();
+
+        verifyUpdateSaAddressesReq(true /* expectNatDetection */);
+    }
+
+    @Test
+    public void testMobikeLocalInfoSendsRequestWithoutNatDetection() throws Exception {
+        setupIdleStateMachineWithMobike();
+        mIkeSessionStateMachine.mSupportNatTraversal = false;
+
+        mIkeSessionStateMachine.sendMessage(
+                CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
+        mLooper.dispatchAll();
+
+        verifyUpdateSaAddressesReq(false /* expectNatDetection */);
+    }
+
+    @Test
+    public void testMobikeLocalInfoHandlesResponse() throws Exception {
+        setupIdleStateMachineWithMobike();
+
+        mIkeSessionStateMachine.sendMessage(
+                CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
+        mLooper.dispatchAll();
+
+        verifyUpdateSaAddressesResp(
+                true /* natTraversalSupported */,
+                true /* localNatDetected */,
+                true /* remoteNatDetected */);
+    }
+
+    @Test
+    public void testMobikeLocalInfoHandlesResponseWithoutNatDetection() throws Exception {
+        setupIdleStateMachineWithMobike();
+        mIkeSessionStateMachine.mSupportNatTraversal = false;
+        mIkeSessionStateMachine.mLocalNatDetected = false;
+        mIkeSessionStateMachine.mRemoteNatDetected = false;
+
+        mIkeSessionStateMachine.sendMessage(
+                CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
+        mLooper.dispatchAll();
+
+        verifyUpdateSaAddressesResp(
+                false /* natTraversalSupported */,
+                false /* localNatDetected */,
+                false /* remoteNatDetected */);
+    }
+
+    private void verifyUpdateSaAddressesReq(boolean expectNatDetection) throws Exception {
+        List<IkePayload> reqPayloadList = verifyOutInfoMsgHeaderAndGetPayloads(false /* isResp */);
+        int expectedPayloads = expectNatDetection ? 3 : 1;
+        assertEquals(expectedPayloads, reqPayloadList.size());
+        assertTrue(isNotifyExist(reqPayloadList, NOTIFY_TYPE_UPDATE_SA_ADDRESSES));
+
+        if (expectNatDetection) {
+            assertTrue(isNotifyExist(reqPayloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
+            assertTrue(isNotifyExist(reqPayloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
+        }
+    }
+
+    private void verifyUpdateSaAddressesResp(
+            boolean natTraversalSupported, boolean localNatDetected, boolean remoteNatDetected)
+            throws Exception {
+        List<Integer> respPayloadTypeList = new ArrayList<>();
+        List<String> respPayloadHexStringList = new ArrayList<>();
+        if (natTraversalSupported) {
+            respPayloadTypeList.add(PAYLOAD_TYPE_NOTIFY);
+            respPayloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
+
+            respPayloadTypeList.add(PAYLOAD_TYPE_NOTIFY);
+            respPayloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
+        }
+
+        ReceivedIkePacket respIkePacket =
+                makeDummyEncryptedReceivedIkePacket(
+                        mSpyCurrentIkeSaRecord,
+                        EXCHANGE_TYPE_INFORMATIONAL,
+                        true /* isResp */,
+                        respPayloadTypeList,
+                        respPayloadHexStringList);
+        mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, respIkePacket);
+        mLooper.dispatchAll();
+
+        assertEquals(mIkeSessionStateMachine.mIdle, mIkeSessionStateMachine.getCurrentState());
+        assertEquals(natTraversalSupported, mIkeSessionStateMachine.mSupportNatTraversal);
+        assertEquals(localNatDetected, mIkeSessionStateMachine.mLocalNatDetected);
+        assertEquals(remoteNatDetected, mIkeSessionStateMachine.mRemoteNatDetected);
+
+        // TODO(b/173237734): check IkeSocket - if includeNatDetection then expect UdpEncap
+    }
+
+    @Test
+    public void testNattKeepaliveStoppedDuringMobilityEvent() throws Exception {
+        IkeNetworkCallbackBase callback = setupIdleStateMachineWithMobike();
+
+        verifySetNetwork(
+                callback, null /* rekeySaRecord */, mIkeSessionStateMachine.mMobikeLocalInfo);
+
+        verify(mMockIkeNattKeepalive).stop();
+        assertNull(mIkeSessionStateMachine.mIkeNattKeepalive);
+    }
 }