Snap for 8121614 from 8dbd67e9015fb4c966d206a5440f015dbfde1cb1 to sdk-release

Change-Id: I2a36d620fdcb5aa7ecb85d07a1efdad202d42326
diff --git a/src/com/google/android/iwlan/IwlanHelper.java b/src/com/google/android/iwlan/IwlanHelper.java
index b0ab666..7e39059 100644
--- a/src/com/google/android/iwlan/IwlanHelper.java
+++ b/src/com/google/android/iwlan/IwlanHelper.java
@@ -22,6 +22,7 @@
 import android.location.Country;
 import android.location.CountryDetector;
 import android.net.ConnectivityManager;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -38,6 +39,7 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -46,8 +48,13 @@
     private static final String TAG = IwlanHelper.class.getSimpleName();
     private static CountryDetector mCountryDetector;
     private static final String LAST_KNOWN_COUNTRY_CODE_KEY = "last_known_country_code";
+    private static IpPrefix mNat64Prefix = new IpPrefix("64:ff9b::/96");
 
-    public static String getNai(Context context, int slotId) {
+    public static String getNai(Context context, int slotId, byte[] nextReauthId) {
+        if (nextReauthId != null) {
+            return new String(nextReauthId, StandardCharsets.UTF_8);
+        }
+
         StringBuilder naiBuilder = new StringBuilder();
         TelephonyManager tm = context.getSystemService(TelephonyManager.class);
         SubscriptionInfo subInfo = null;
@@ -105,15 +112,37 @@
     public static List<InetAddress> getAddressesForNetwork(Network network, Context context) {
         ConnectivityManager connectivityManager =
                 context.getSystemService(ConnectivityManager.class);
-        List<InetAddress> gatewayList = new ArrayList<InetAddress>();
+        List<InetAddress> gatewayList = new ArrayList<>();
         if (network != null) {
             LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
             if (linkProperties != null) {
-                for (LinkAddress laddr : linkProperties.getLinkAddresses()) {
-                    InetAddress inetaddr = laddr.getAddress();
+                for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) {
+                    InetAddress inetAddr = linkAddr.getAddress();
                     // skip linklocal and loopback addresses
-                    if (!inetaddr.isLoopbackAddress() && !inetaddr.isLinkLocalAddress()) {
-                        gatewayList.add(inetaddr);
+                    if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) {
+                        gatewayList.add(inetAddr);
+                    }
+                }
+                if (linkProperties.getNat64Prefix() != null) {
+                    mNat64Prefix = linkProperties.getNat64Prefix();
+                }
+            }
+        }
+        return gatewayList;
+    }
+
+    public static List<InetAddress> getStackedAddressesForNetwork(
+            Network network, Context context) {
+        ConnectivityManager connectivityManager =
+                context.getSystemService(ConnectivityManager.class);
+        List<InetAddress> gatewayList = new ArrayList<>();
+        if (network != null) {
+            LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
+            if (linkProperties != null) {
+                for (LinkAddress linkAddr : linkProperties.getAllLinkAddresses()) {
+                    InetAddress inetAddr = linkAddr.getAddress();
+                    if ((inetAddr instanceof Inet4Address)) {
+                        gatewayList.add(inetAddr);
                     }
                 }
             }
@@ -121,6 +150,16 @@
         return gatewayList;
     }
 
+    /**
+     * The method is to check if this IP address is an IPv4-embedded IPv6 address(Pref64::/n).
+     *
+     * @param ipAddress IP address
+     * @return True if it is an IPv4-embedded IPv6 addres, otherwise false.
+     */
+    public static boolean isIpv4EmbeddedIpv6Address(@NonNull InetAddress ipAddress) {
+        return (ipAddress instanceof Inet6Address) && mNat64Prefix.contains(ipAddress);
+    }
+
     public static boolean hasIpv6Address(List<InetAddress> localAddresses) {
         for (InetAddress address : localAddresses) {
             if (address instanceof Inet6Address) {
diff --git a/src/com/google/android/iwlan/epdg/EpdgSelector.java b/src/com/google/android/iwlan/epdg/EpdgSelector.java
index db23223..bbc474f 100644
--- a/src/com/google/android/iwlan/epdg/EpdgSelector.java
+++ b/src/com/google/android/iwlan/epdg/EpdgSelector.java
@@ -17,9 +17,12 @@
 package com.google.android.iwlan.epdg;
 
 import android.content.Context;
+import android.net.DnsResolver;
+import android.net.DnsResolver.DnsException;
 import android.net.Network;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
@@ -29,6 +32,7 @@
 import android.telephony.CellInfoGsm;
 import android.telephony.CellInfoLte;
 import android.telephony.CellInfoNr;
+import android.telephony.CellInfoTdscdma;
 import android.telephony.CellInfoWcdma;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -40,12 +44,16 @@
 
 import com.google.android.iwlan.IwlanError;
 import com.google.android.iwlan.IwlanHelper;
+import com.google.android.iwlan.epdg.NaptrDnsResolver.NaptrTarget;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
 
 public class EpdgSelector {
     private static final String TAG = "EpdgSelector";
@@ -162,7 +170,7 @@
                     }
                     break;
                 case PROTO_FILTER_IPV6:
-                    if (ipAddress instanceof Inet6Address) {
+                    if (!IwlanHelper.isIpv4EmbeddedIpv6Address(ipAddress)) {
                         validIpList.add(ipAddress);
                     }
                     break;
@@ -579,6 +587,206 @@
         }
     }
 
+    private String composeFqdnWithMccMnc(String mcc, String mnc, boolean isEmergency) {
+        StringBuilder domainName = new StringBuilder();
+
+        /*
+         * Operator Identifier based ePDG FQDN format:
+         * epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
+         *
+         * Operator Identifier based Emergency ePDG FQDN format:
+         * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org
+         */
+        domainName.setLength(0);
+        if (isEmergency) {
+            domainName.append("sos.");
+        }
+        domainName
+                .append("epdg.epc.mnc")
+                .append(mnc)
+                .append(".mcc")
+                .append(mcc)
+                .append(".pub.3gppnetwork.org");
+
+        return domainName.toString();
+    }
+
+    private boolean isRegisteredWith3GPP(TelephonyManager telephonyManager) {
+        List<CellInfo> cellInfoList = telephonyManager.getAllCellInfo();
+        if (cellInfoList == null) {
+            Log.e(TAG, "cellInfoList is NULL");
+        } else {
+            for (CellInfo cellInfo : cellInfoList) {
+                if (!cellInfo.isRegistered()) {
+                    continue;
+                }
+                if (cellInfo instanceof CellInfoGsm
+                        || cellInfo instanceof CellInfoTdscdma
+                        || cellInfo instanceof CellInfoWcdma
+                        || cellInfo instanceof CellInfoLte
+                        || cellInfo instanceof CellInfoNr) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void processNaptrResponse(
+            int filter,
+            ArrayList<InetAddress> validIpList,
+            boolean isEmergency,
+            Network network,
+            boolean isRegisteredWith3GPP,
+            List<NaptrTarget> naptrResponse,
+            Set<String> plmnsFromCarrierConfig,
+            String registeredhostName) {
+        Set<String> resultSet = new LinkedHashSet<>();
+
+        for (NaptrTarget target : naptrResponse) {
+            Log.d(TAG, "NaptrTarget - name: " + target.mName);
+            Log.d(TAG, "NaptrTarget - type: " + target.mType);
+            if (target.mType == NaptrDnsResolver.TYPE_A) {
+                resultSet.add(target.mName);
+            }
+        }
+
+        /*
+         * As 3GPP TS 23.402 4.5.4.5 bullet 2a,
+         * if the device registers via 3GPP and its PLMN info is in the NAPTR response,
+         * try to connect ePDG with this PLMN info.
+         */
+        if (isRegisteredWith3GPP) {
+            if (resultSet.contains(registeredhostName)) {
+                getIP(registeredhostName, filter, validIpList, network);
+                resultSet.remove(registeredhostName);
+            }
+        }
+
+        /*
+         * As 3GPP TS 23.402 4.5.4.5 bullet 2b
+         * Check if there is any PLMN in both ePDG selection information and the DNS response
+         */
+        for (String plmn : plmnsFromCarrierConfig) {
+            String[] mccmnc = splitMccMnc(plmn);
+            String carrierConfighostName = composeFqdnWithMccMnc(mccmnc[0], mccmnc[1], isEmergency);
+
+            if (resultSet.contains(carrierConfighostName)) {
+                getIP(carrierConfighostName, filter, validIpList, network);
+                resultSet.remove(carrierConfighostName);
+            }
+        }
+
+        /*
+         * Do FQDN with the remaining PLMNs in the ResultSet
+         */
+        for (String result : resultSet) {
+            getIP(result, filter, validIpList, network);
+        }
+    }
+
+    private void resolutionMethodVisitedCountry(
+            int filter, ArrayList<InetAddress> validIpList, boolean isEmergency, Network network) {
+        StringBuilder domainName = new StringBuilder();
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        telephonyManager =
+                telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
+
+        if (telephonyManager == null) {
+            Log.e(TAG, "TelephonyManager is NULL");
+            return;
+        }
+
+        final boolean isRegisteredWith3GPP = isRegisteredWith3GPP(telephonyManager);
+
+        // Get ePDG selection information from CarrierConfig
+        final Set<String> plmnsFromCarrierConfig =
+                new LinkedHashSet<>(
+                        Arrays.asList(
+                                IwlanHelper.getConfig(
+                                        CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY,
+                                        mContext,
+                                        mSlotId)));
+
+        final String cellMcc = telephonyManager.getNetworkOperator().substring(0, 3);
+        final String cellMnc = telephonyManager.getNetworkOperator().substring(3);
+        final String plmnFromNetwork =
+                new StringBuilder().append(cellMcc).append("-").append(cellMnc).toString();
+        final String registeredhostName = composeFqdnWithMccMnc(cellMcc, cellMnc, isEmergency);
+
+        /*
+        * As TS 23 402 4.5.4.4 bullet 3a
+        * If the UE determines to be located in a country other than its home country
+        * If the UE is registered via 3GPP access to a PLMN and this PLMN matches an entry
+          in the ePDG selection information, then the UE shall select an ePDG in this PLMN.
+        */
+        if (isRegisteredWith3GPP) {
+            if (plmnsFromCarrierConfig.contains(plmnFromNetwork)) {
+                getIP(registeredhostName, filter, validIpList, network);
+            }
+        }
+
+        /*
+         * Visited Country FQDN format:
+         * epdg.epc.mcc<MCC>.visited-country.pub.3gppnetwork.org
+         *
+         * Visited Country Emergency ePDG FQDN format:
+         * sos.epdg.epc.mcc<MCC>.visited-country.pub.3gppnetwork.org
+         */
+        if (isEmergency) {
+            domainName.append("sos.");
+        }
+        domainName
+                .append("epdg.epc.mcc")
+                .append(cellMcc)
+                .append(".visited-country.pub.3gppnetwork.org");
+
+        Log.d(TAG, "Visited Country FQDN with " + domainName.toString());
+
+        CompletableFuture<List<NaptrTarget>> naptrDnsResult = new CompletableFuture<>();
+        DnsResolver.Callback<List<NaptrTarget>> naptrDnsCb =
+                new DnsResolver.Callback<List<NaptrTarget>>() {
+                    @Override
+                    public void onAnswer(@NonNull final List<NaptrTarget> answer, final int rcode) {
+                        if (rcode == 0 && answer.size() != 0) {
+                            naptrDnsResult.complete(answer);
+                        } else {
+                            naptrDnsResult.completeExceptionally(new UnknownHostException());
+                        }
+                    }
+
+                    @Override
+                    public void onError(@Nullable final DnsException error) {
+                        naptrDnsResult.completeExceptionally(error);
+                    }
+                };
+        NaptrDnsResolver.query(network, domainName.toString(), r -> r.run(), null, naptrDnsCb);
+
+        try {
+            final List<NaptrTarget> naptrResponse = naptrDnsResult.get();
+            // Check if there is any record in the NAPTR response
+            if (naptrResponse != null && naptrResponse.size() > 0) {
+                processNaptrResponse(
+                        filter,
+                        validIpList,
+                        isEmergency,
+                        network,
+                        isRegisteredWith3GPP,
+                        naptrResponse,
+                        plmnsFromCarrierConfig,
+                        registeredhostName);
+            }
+        } catch (ExecutionException e) {
+            Log.e(TAG, "Cause of ExecutionException: ", e.getCause());
+        } catch (InterruptedException e) {
+            if (Thread.currentThread().interrupted()) {
+                Thread.currentThread().interrupt();
+            }
+            Log.e(TAG, "InterruptedException: ", e);
+        }
+    }
+
     public IwlanError getValidatedServerList(
             int transactionId,
             @ProtoFilter int filter,
@@ -600,6 +808,19 @@
                                     mContext,
                                     mSlotId);
 
+                    final boolean isVisitedCountryMethodRequired =
+                            Arrays.stream(addrResolutionMethods)
+                                    .anyMatch(
+                                            i ->
+                                                    i
+                                                            == CarrierConfigManager.Iwlan
+                                                                    .EPDG_ADDRESS_VISITED_COUNTRY);
+
+                    // In the visited country
+                    if (isRoaming && !inSameCountry() && isVisitedCountryMethodRequired) {
+                        resolutionMethodVisitedCountry(filter, validIpList, isEmergency, network);
+                    }
+
                     for (int addrResolutionMethod : addrResolutionMethods) {
                         switch (addrResolutionMethod) {
                             case CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC:
diff --git a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java
index 4a6c62c..9f3bb96 100644
--- a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java
+++ b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java
@@ -28,6 +28,8 @@
 import android.net.IpSecTransform;
 import android.net.LinkAddress;
 import android.net.Network;
+import android.net.eap.EapAkaInfo;
+import android.net.eap.EapInfo;
 import android.net.eap.EapSessionConfig;
 import android.net.ipsec.ike.ChildSaProposal;
 import android.net.ipsec.ike.ChildSessionCallback;
@@ -145,6 +147,7 @@
     private InetAddress mEpdgAddress;
     private Network mNetwork;
     private int mTransactionId = 0;
+    private int mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4V6;
     private boolean mIsEpdgAddressSelected;
     private IkeSessionCreator mIkeSessionCreator;
 
@@ -154,6 +157,8 @@
 
     private List<InetAddress> mLocalAddresses;
 
+    @Nullable private byte[] mNextReauthId = null;
+
     private static final Set<Integer> VALID_DH_GROUPS;
     private static final Set<Integer> VALID_KEY_LENGTHS;
     private static final Set<Integer> VALID_PRF_ALGOS;
@@ -431,6 +436,25 @@
             Log.d(TAG, "Ike session opened for apn: " + mApnName);
             TunnelConfig tunnelConfig = mApnNameToTunnelConfig.get(mApnName);
             tunnelConfig.setPcscfAddrList(sessionConfiguration.getPcscfServers());
+
+            boolean enabledFastReauth =
+                    (boolean)
+                            getConfig(
+                                    CarrierConfigManager.Iwlan
+                                            .KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL);
+            Log.d(
+                    TAG,
+                    "CarrierConfigManager.Iwlan.KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL "
+                            + enabledFastReauth);
+            if (enabledFastReauth) {
+                EapInfo eapInfo = sessionConfiguration.getEapInfo();
+                if (eapInfo != null && eapInfo instanceof EapAkaInfo) {
+                    mNextReauthId = ((EapAkaInfo) eapInfo).getReauthId();
+                    Log.d(TAG, "Update ReauthId: " + Arrays.toString(mNextReauthId));
+                } else {
+                    mNextReauthId = null;
+                }
+            }
         }
 
         @Override
@@ -442,12 +466,18 @@
         @Override
         public void onClosedExceptionally(IkeException exception) {
             Log.d(TAG, "Ike session onClosedExceptionally for apn: " + mApnName);
+
+            mNextReauthId = null;
+
             onSessionClosedWithException(exception, mApnName, EVENT_IKE_SESSION_CLOSED);
         }
 
         @Override
         public void onError(IkeProtocolException exception) {
             Log.d(TAG, "Ike session onError for apn: " + mApnName);
+
+            mNextReauthId = null;
+
             Log.d(
                     TAG,
                     "ErrorType:"
@@ -1175,7 +1205,9 @@
     }
 
     private IkeIdentification getLocalIdentification() {
-        String nai = IwlanHelper.getNai(mContext, mSlotId);
+        String nai;
+
+        nai = IwlanHelper.getNai(mContext, mSlotId, mNextReauthId);
 
         if (nai == null) {
             throw new IllegalArgumentException("Nai is null.");
@@ -1205,15 +1237,20 @@
 
     private EapSessionConfig getEapConfig() {
         int subId = IwlanHelper.getSubId(mContext, mSlotId);
-        String nai = IwlanHelper.getNai(mContext, mSlotId);
+        String nai = IwlanHelper.getNai(mContext, mSlotId, null);
 
         if (nai == null) {
             throw new IllegalArgumentException("Nai is null.");
         }
 
+        EapSessionConfig.EapAkaOption option = null;
+        if (mNextReauthId != null) {
+            option = new EapSessionConfig.EapAkaOption.Builder().setReauthId(mNextReauthId).build();
+        }
+
         Log.d(TAG, "getEapConfig: Nai: " + nai);
         return new EapSessionConfig.Builder()
-                .setEapAkaConfig(subId, TelephonyManager.APPTYPE_USIM)
+                .setEapAkaConfig(subId, TelephonyManager.APPTYPE_USIM, option)
                 .setEapIdentity(nai.getBytes(StandardCharsets.US_ASCII))
                 .build();
     }
@@ -1412,10 +1449,17 @@
                         }
 
                         try {
+                            if (mEpdgAddress instanceof Inet4Address
+                                    && mProtoFilter == EpdgSelector.PROTO_FILTER_IPV6) {
+                                mLocalAddresses =
+                                        IwlanHelper.getStackedAddressesForNetwork(
+                                                mNetwork, mContext);
+                            }
                             InetAddress localAddress =
                                     (mEpdgAddress instanceof Inet4Address)
                                             ? IwlanHelper.getIpv4Address(mLocalAddresses)
                                             : IwlanHelper.getIpv6Address(mLocalAddresses);
+                            Log.d(TAG, "Local address = " + localAddress);
                             tunnelConfig.setIface(
                                     ipSecManager.createIpSecTunnelInterface(
                                             localAddress, mEpdgAddress, mNetwork));
@@ -1473,19 +1517,19 @@
             return;
         }
 
-        int protoFilter = EpdgSelector.PROTO_FILTER_IPV4V6;
+        mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4V6;
         if (!IwlanHelper.hasIpv6Address(mLocalAddresses)) {
-            protoFilter = EpdgSelector.PROTO_FILTER_IPV4;
+            mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4;
         }
         if (!IwlanHelper.hasIpv4Address(mLocalAddresses)) {
-            protoFilter = EpdgSelector.PROTO_FILTER_IPV6;
+            mProtoFilter = EpdgSelector.PROTO_FILTER_IPV6;
         }
 
         EpdgSelector epdgSelector = getEpdgSelector();
         IwlanError epdgError =
                 epdgSelector.getValidatedServerList(
                         ++mTransactionId,
-                        protoFilter,
+                        mProtoFilter,
                         setupRequest.isRoaming(),
                         setupRequest.isEmergency(),
                         mNetwork,