blob: 9a4af93b119bbf2b0590baf7bea3686f1ebda7f7 [file] [log] [blame]
/*
* Copyright 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.google.android.iwlan.epdg;
import static android.net.DnsResolver.TYPE_A;
import static android.net.DnsResolver.TYPE_AAAA;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static java.util.stream.Collectors.toList;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.DnsResolver;
import android.net.InetAddresses;
import android.net.Network;
import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentityGsm;
import android.telephony.CellIdentityLte;
import android.telephony.CellIdentityNr;
import android.telephony.CellIdentityWcdma;
import android.telephony.CellInfo;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoNr;
import android.telephony.CellInfoWcdma;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.google.android.iwlan.IwlanError;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
public class EpdgSelectorTest {
private static final String TAG = "EpdgSelectorTest";
private EpdgSelector mEpdgSelector;
public static final int DEFAULT_SLOT_INDEX = 0;
private static final String TEST_IP_ADDRESS = "127.0.0.1";
private static final String TEST_IP_ADDRESS_1 = "127.0.0.2";
private static final String TEST_IP_ADDRESS_2 = "127.0.0.3";
private static final String TEST_IPV6_ADDRESS = "0000:0000:0000:0000:0000:0000:0000:0001";
private static int testPcoIdIPv6 = 0xFF01;
private static int testPcoIdIPv4 = 0xFF02;
private String testPcoString = "testPcoData";
private byte[] pcoData = testPcoString.getBytes();
private List<String> ehplmnList = new ArrayList<String>();
@Mock private Context mMockContext;
@Mock private Network mMockNetwork;
@Mock private SubscriptionManager mMockSubscriptionManager;
@Mock private SubscriptionInfo mMockSubscriptionInfo;
@Mock private CarrierConfigManager mMockCarrierConfigManager;
@Mock private TelephonyManager mMockTelephonyManager;
@Mock private SharedPreferences mMockSharedPreferences;
@Mock private CellInfoGsm mMockCellInfoGsm;
@Mock private CellIdentityGsm mMockCellIdentityGsm;
@Mock private CellInfoWcdma mMockCellInfoWcdma;
@Mock private CellIdentityWcdma mMockCellIdentityWcdma;
@Mock private CellInfoLte mMockCellInfoLte;
@Mock private CellIdentityLte mMockCellIdentityLte;
@Mock private CellInfoNr mMockCellInfoNr;
@Mock private CellIdentityNr mMockCellIdentityNr;
@Mock private DnsResolver mMockDnsResolver;
private PersistableBundle mTestBundle;
private FakeDns mFakeDns;
MockitoSession mStaticMockSession;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mStaticMockSession = mockitoSession().mockStatic(DnsResolver.class).startMocking();
mEpdgSelector = new EpdgSelector(mMockContext, DEFAULT_SLOT_INDEX);
when(mMockContext.getSystemService(eq(SubscriptionManager.class)))
.thenReturn(mMockSubscriptionManager);
when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt()))
.thenReturn(mMockSubscriptionInfo);
when(mMockSubscriptionInfo.getMccString()).thenReturn("311");
when(mMockSubscriptionInfo.getMncString()).thenReturn("120");
when(mMockContext.getSystemService(eq(TelephonyManager.class)))
.thenReturn(mMockTelephonyManager);
when(mMockTelephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(mMockTelephonyManager);
ehplmnList.add("300120");
when(mMockTelephonyManager.getEquivalentHomePlmns()).thenReturn(ehplmnList);
when(mMockTelephonyManager.getSimCountryIso()).thenReturn("ca");
when(mMockContext.getSharedPreferences(anyString(), anyInt()))
.thenReturn(mMockSharedPreferences);
when(mMockSharedPreferences.getString(any(), any())).thenReturn("US");
// Mock carrier configs with test bundle
mTestBundle = new PersistableBundle();
when(mMockContext.getSystemService(eq(CarrierConfigManager.class)))
.thenReturn(mMockCarrierConfigManager);
when(mMockCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mTestBundle);
lenient().when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver);
mFakeDns = new FakeDns();
mFakeDns.startMocking();
}
@After
public void cleanUp() throws Exception {
mStaticMockSession.finishMocking();
mFakeDns.clearAll();
}
@Test
public void testStaticMethodPass() throws Exception {
// Set DnsResolver query mock
final String testStaticAddress = "epdg.epc.mnc088.mcc888.pub.3gppnetwork.org";
mFakeDns.setAnswer(testStaticAddress, new String[] {TEST_IP_ADDRESS}, TYPE_A);
// Set carrier config mock
mTestBundle.putIntArray(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC});
mTestBundle.putString(
CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress);
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(false /*isEmergency*/);
InetAddress expectedAddress = InetAddress.getByName(TEST_IP_ADDRESS);
assertEquals(testInetAddresses.size(), 1);
assertEquals(testInetAddresses.get(0), expectedAddress);
}
@Test
public void testRoamStaticMethodPass() throws Exception {
// Set DnsResolver query mock
final String testRoamStaticAddress = "epdg.epc.mnc088.mcc888.pub.3gppnetwork.org";
mFakeDns.setAnswer(testRoamStaticAddress, new String[] {TEST_IP_ADDRESS}, TYPE_A);
// Set carrier config mock
mTestBundle.putIntArray(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC});
mTestBundle.putString(
CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_ROAMING_STRING,
testRoamStaticAddress);
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(false /*isEmergency*/);
InetAddress expectedAddress = InetAddress.getByName(TEST_IP_ADDRESS);
assertEquals(testInetAddresses.size(), 1);
assertEquals(testInetAddresses.get(0), expectedAddress);
}
@Test
public void testPlmnResolutionMethod() throws Exception {
testPlmnResolutionMethod(false);
}
@Test
public void testPlmnResolutionMethodForEmergency() throws Exception {
testPlmnResolutionMethod(true);
}
@Test
public void testPlmnResolutionMethodWithNoPlmnInCarrierConfig() throws Exception {
// setUp() fills default values for mcc-mnc
String expectedFqdn1 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org";
String expectedFqdn2 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org";
mFakeDns.setAnswer(expectedFqdn1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A);
mFakeDns.setAnswer(expectedFqdn2, new String[] {TEST_IP_ADDRESS_2}, TYPE_A);
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(false /*isEmergency*/);
assertEquals(testInetAddresses.size(), 2);
assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IP_ADDRESS_1)));
assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IP_ADDRESS_2)));
}
private void testPlmnResolutionMethod(boolean isEmergency) throws Exception {
String expectedFqdn1 =
(isEmergency)
? "sos.epdg.epc.mnc480.mcc310.pub.3gppnetwork.org"
: "epdg.epc.mnc480.mcc310.pub.3gppnetwork.org";
String expectedFqdn2 =
(isEmergency)
? "sos.epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"
: "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org";
String expectedFqdn3 =
(isEmergency)
? "sos.epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"
: "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org";
mTestBundle.putIntArray(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN});
mTestBundle.putStringArray(
CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY,
new String[] {"310-480", "300-120", "311-120"});
mFakeDns.setAnswer(expectedFqdn1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A);
mFakeDns.setAnswer(expectedFqdn2, new String[] {TEST_IP_ADDRESS_2}, TYPE_A);
mFakeDns.setAnswer(expectedFqdn3, new String[] {TEST_IP_ADDRESS}, TYPE_A);
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(isEmergency);
assertEquals(testInetAddresses.size(), 3);
assertEquals(testInetAddresses.get(0), InetAddress.getByName(TEST_IP_ADDRESS));
assertEquals(testInetAddresses.get(1), InetAddress.getByName(TEST_IP_ADDRESS_2));
assertEquals(testInetAddresses.get(2), InetAddress.getByName(TEST_IP_ADDRESS_1));
}
@Test
public void testCarrierConfigStaticAddressList() throws Exception {
// Set Network.getAllByName mock
final String addr1 = "epdg.epc.mnc480.mcc310.pub.3gppnetwork.org";
final String addr2 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org";
final String addr3 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org";
final String testStaticAddress = addr1 + "," + addr2 + "," + addr3;
mFakeDns.setAnswer(addr1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A);
mFakeDns.setAnswer(addr2, new String[] {TEST_IP_ADDRESS_2}, TYPE_A);
mFakeDns.setAnswer(addr3, new String[] {TEST_IP_ADDRESS}, TYPE_A);
// Set carrier config mock
mTestBundle.putIntArray(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC});
mTestBundle.putString(
CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress);
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(false /*isEmergency*/);
assertEquals(testInetAddresses.size(), 3);
assertEquals(testInetAddresses.get(0), InetAddress.getByName(TEST_IP_ADDRESS_1));
assertEquals(testInetAddresses.get(1), InetAddress.getByName(TEST_IP_ADDRESS_2));
assertEquals(testInetAddresses.get(2), InetAddress.getByName(TEST_IP_ADDRESS));
}
private ArrayList<InetAddress> getValidatedServerListWithDefaultParams(boolean isEmergency)
throws Exception {
ArrayList<InetAddress> testInetAddresses = new ArrayList<InetAddress>();
final CountDownLatch latch = new CountDownLatch(1);
IwlanError ret =
mEpdgSelector.getValidatedServerList(
1234,
EpdgSelector.PROTO_FILTER_IPV4V6,
false /*isRoaming*/,
isEmergency,
mMockNetwork,
new EpdgSelector.EpdgSelectorCallback() {
@Override
public void onServerListChanged(
int transactionId, ArrayList<InetAddress> validIPList) {
assertEquals(transactionId, 1234);
for (InetAddress mInetAddress : validIPList) {
testInetAddresses.add(mInetAddress);
}
Log.d(TAG, "onServerListChanged received");
latch.countDown();
}
@Override
public void onError(int transactionId, IwlanError epdgSelectorError) {
Log.d(TAG, "onError received");
latch.countDown();
}
});
assertEquals(ret.getErrorType(), IwlanError.NO_ERROR);
latch.await(1, TimeUnit.SECONDS);
return testInetAddresses;
}
@Test
public void testSetPcoData() throws Exception {
addTestPcoIdsToTestConfigBundle();
boolean retIPv6 = mEpdgSelector.setPcoData(testPcoIdIPv6, pcoData);
boolean retIPv4 = mEpdgSelector.setPcoData(testPcoIdIPv4, pcoData);
boolean retIncorrect = mEpdgSelector.setPcoData(0xFF00, pcoData);
assertTrue(retIPv6);
assertTrue(retIPv4);
assertFalse(retIncorrect);
}
@Test
public void testPcoResolutionMethod() throws Exception {
mTestBundle.putIntArray(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PCO});
addTestPcoIdsToTestConfigBundle();
mEpdgSelector.clearPcoData();
boolean retIPv6 =
mEpdgSelector.setPcoData(
testPcoIdIPv6, InetAddress.getByName(TEST_IPV6_ADDRESS).getAddress());
boolean retIPv4 =
mEpdgSelector.setPcoData(
testPcoIdIPv4, InetAddress.getByName(TEST_IP_ADDRESS).getAddress());
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(false /* isEmergency */);
assertEquals(testInetAddresses.size(), 2);
assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IP_ADDRESS)));
assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IPV6_ADDRESS)));
}
private void addTestPcoIdsToTestConfigBundle() {
mTestBundle.putInt(CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV6_INT, testPcoIdIPv6);
mTestBundle.putInt(CarrierConfigManager.Iwlan.KEY_EPDG_PCO_ID_IPV4_INT, testPcoIdIPv4);
}
@Test
public void testCellularResolutionMethod() throws Exception {
testCellularResolutionMethod(false);
}
@Test
public void testCellularResolutionMethodForEmergency() throws Exception {
testCellularResolutionMethod(true);
}
private void testCellularResolutionMethod(boolean isEmergency) throws Exception {
int testMcc = 311;
int testMnc = 120;
String testMccString = "311";
String testMncString = "120";
int testLac = 65484;
int testTac = 65484;
int testNrTac = 16764074;
List<CellInfo> fakeCellInfoArray = new ArrayList<CellInfo>();
mTestBundle.putIntArray(
CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_CELLULAR_LOC});
// Set cell info mock
fakeCellInfoArray.add(mMockCellInfoGsm);
when(mMockCellInfoGsm.isRegistered()).thenReturn(true);
when(mMockCellInfoGsm.getCellIdentity()).thenReturn(mMockCellIdentityGsm);
when(mMockCellIdentityGsm.getMcc()).thenReturn(testMcc);
when(mMockCellIdentityGsm.getMnc()).thenReturn(testMnc);
when(mMockCellIdentityGsm.getLac()).thenReturn(testLac);
fakeCellInfoArray.add(mMockCellInfoWcdma);
when(mMockCellInfoWcdma.isRegistered()).thenReturn(true);
when(mMockCellInfoWcdma.getCellIdentity()).thenReturn(mMockCellIdentityWcdma);
when(mMockCellIdentityWcdma.getMcc()).thenReturn(testMcc);
when(mMockCellIdentityWcdma.getMnc()).thenReturn(testMnc);
when(mMockCellIdentityWcdma.getLac()).thenReturn(testLac);
fakeCellInfoArray.add(mMockCellInfoLte);
when(mMockCellInfoLte.isRegistered()).thenReturn(true);
when(mMockCellInfoLte.getCellIdentity()).thenReturn(mMockCellIdentityLte);
when(mMockCellIdentityLte.getMcc()).thenReturn(testMcc);
when(mMockCellIdentityLte.getMnc()).thenReturn(testMnc);
when(mMockCellIdentityLte.getTac()).thenReturn(testTac);
fakeCellInfoArray.add(mMockCellInfoNr);
when(mMockCellInfoNr.isRegistered()).thenReturn(true);
when(mMockCellInfoNr.getCellIdentity()).thenReturn(mMockCellIdentityNr);
when(mMockCellIdentityNr.getMccString()).thenReturn(testMccString);
when(mMockCellIdentityNr.getMncString()).thenReturn(testMncString);
when(mMockCellIdentityNr.getTac()).thenReturn(testNrTac);
when(mMockTelephonyManager.getAllCellInfo()).thenReturn(fakeCellInfoArray);
setAnswerForCellularMethod(isEmergency, 311, 120);
setAnswerForCellularMethod(isEmergency, 300, 120);
ArrayList<InetAddress> testInetAddresses =
getValidatedServerListWithDefaultParams(isEmergency);
assertEquals(testInetAddresses.size(), 3);
assertEquals(testInetAddresses.get(0), InetAddress.getByName(TEST_IP_ADDRESS));
assertEquals(testInetAddresses.get(1), InetAddress.getByName(TEST_IP_ADDRESS_1));
assertEquals(testInetAddresses.get(2), InetAddress.getByName(TEST_IP_ADDRESS_2));
}
private void setAnswerForCellularMethod(boolean isEmergency, int mcc, int mnc)
throws Exception {
String expectedFqdn1 =
(isEmergency)
? "lacffcc.sos.epdg.epc.mnc" + mnc + ".mcc" + mcc + ".pub.3gppnetwork.org"
: "lacffcc.epdg.epc.mnc" + mnc + ".mcc" + mcc + ".pub.3gppnetwork.org";
String expectedFqdn2 =
(isEmergency)
? "tac-lbcc.tac-hbff.tac.sos.epdg.epc.mnc"
+ mnc
+ ".mcc"
+ mcc
+ ".pub.3gppnetwork.org"
: "tac-lbcc.tac-hbff.tac.epdg.epc.mnc"
+ mnc
+ ".mcc"
+ mcc
+ ".pub.3gppnetwork.org";
String expectedFqdn3 =
(isEmergency)
? "tac-lbaa.tac-mbcc.tac-hbff.5gstac.sos.epdg.epc.mnc"
+ mnc
+ ".mcc"
+ mcc
+ ".pub.3gppnetwork.org"
: "tac-lbaa.tac-mbcc.tac-hbff.5gstac.epdg.epc.mnc"
+ mnc
+ ".mcc"
+ mcc
+ ".pub.3gppnetwork.org";
mFakeDns.setAnswer(expectedFqdn1, new String[] {TEST_IP_ADDRESS}, TYPE_A);
mFakeDns.setAnswer(expectedFqdn2, new String[] {TEST_IP_ADDRESS_1}, TYPE_A);
mFakeDns.setAnswer(expectedFqdn3, new String[] {TEST_IP_ADDRESS_2}, TYPE_A);
}
/**
* Fakes DNS responses.
*
* <p>Allows test methods to configure the IP addresses that will be resolved by
* Network#getAllByName and by DnsResolver#query.
*/
class FakeDns {
/** Data class to record the Dns entry. */
class DnsEntry {
final String mHostname;
final int mType;
final List<InetAddress> mAddresses;
DnsEntry(String host, int type, List<InetAddress> addr) {
mHostname = host;
mType = type;
mAddresses = addr;
}
// Full match or partial match that target host contains the entry hostname to support
// random private dns probe hostname.
private boolean matches(String hostname, int type) {
return hostname.endsWith(mHostname) && type == mType;
}
}
private final ArrayList<DnsEntry> mAnswers = new ArrayList<DnsEntry>();
/** Clears all DNS entries. */
private synchronized void clearAll() {
mAnswers.clear();
}
/** Returns the answer for a given name and type on the given mock network. */
private synchronized List<InetAddress> getAnswer(Object mock, String hostname, int type) {
return mAnswers.stream()
.filter(e -> e.matches(hostname, type))
.map(answer -> answer.mAddresses)
.findFirst()
.orElse(null);
}
/** Sets the answer for a given name and type. */
private synchronized void setAnswer(String hostname, String[] answer, int type)
throws UnknownHostException {
DnsEntry record = new DnsEntry(hostname, type, generateAnswer(answer));
// Remove the existing one.
mAnswers.removeIf(entry -> entry.matches(hostname, type));
// Add or replace a new record.
mAnswers.add(record);
}
private List<InetAddress> generateAnswer(String[] answer) {
if (answer == null) return new ArrayList<>();
return Arrays.stream(answer)
.map(addr -> InetAddresses.parseNumericAddress(addr))
.collect(toList());
}
// Regardless of the type, depends on what the responses contained in the network.
private List<InetAddress> queryAllTypes(Object mock, String hostname) {
List<InetAddress> answer = new ArrayList<>();
addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_A));
addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_AAAA));
return answer;
}
private void addAllIfNotNull(List<InetAddress> list, List<InetAddress> c) {
if (c != null) {
list.addAll(c);
}
}
/** Starts mocking DNS queries. */
private void startMocking() throws UnknownHostException {
doAnswer(
invocation -> {
return mockQuery(
invocation,
1 /* posHostname */,
3 /* posExecutor */,
5 /* posCallback */,
-1 /* posType */);
})
.when(mMockDnsResolver)
.query(any(), any(), anyInt(), any(), any(), any());
}
// Mocking queries on DnsResolver#query.
private Answer mockQuery(
InvocationOnMock invocation,
int posHostname,
int posExecutor,
int posCallback,
int posType) {
String hostname = (String) invocation.getArgument(posHostname);
Executor executor = (Executor) invocation.getArgument(posExecutor);
DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(posCallback);
List<InetAddress> answer;
answer = queryAllTypes(invocation.getMock(), hostname);
if (answer != null && answer.size() > 0) {
new Handler(Looper.getMainLooper())
.post(
() -> {
executor.execute(() -> callback.onAnswer(answer, 0));
});
}
// If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
return null;
}
}
}