Merge changes I00d1aa47,Icffbe67f
* changes: Add CTS for AES-CMAC Add tests for new IPsec algorithms in IpSecManagerTest
This commit is contained in:
@@ -16,6 +16,14 @@
|
|||||||
|
|
||||||
package android.net.cts;
|
package android.net.cts;
|
||||||
|
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_CRYPT_AES_GCM;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_MD5;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA1;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA256;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
|
||||||
|
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -31,6 +39,12 @@ import android.util.Log;
|
|||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.modules.utils.build.SdkLevel;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
@@ -42,12 +56,10 @@ import java.net.ServerSocket;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class IpSecBaseTest {
|
public class IpSecBaseTest {
|
||||||
|
|
||||||
@@ -71,6 +83,18 @@ public class IpSecBaseTest {
|
|||||||
0x20, 0x21, 0x22, 0x23
|
0x20, 0x21, 0x22, 0x23
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final Set<String> MANDATORY_IPSEC_ALGOS_SINCE_P = new HashSet<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(CRYPT_AES_CBC);
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_MD5);
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA1);
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA256);
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA384);
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA512);
|
||||||
|
MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_CRYPT_AES_GCM);
|
||||||
|
}
|
||||||
|
|
||||||
protected static final byte[] AUTH_KEY = getKey(256);
|
protected static final byte[] AUTH_KEY = getKey(256);
|
||||||
protected static final byte[] CRYPT_KEY = getKey(256);
|
protected static final byte[] CRYPT_KEY = getKey(256);
|
||||||
|
|
||||||
@@ -89,8 +113,24 @@ public class IpSecBaseTest {
|
|||||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks if an IPsec algorithm is enabled on the device */
|
||||||
|
protected static boolean hasIpSecAlgorithm(String algorithm) {
|
||||||
|
if (SdkLevel.isAtLeastS()) {
|
||||||
|
return IpSecAlgorithm.getSupportedAlgorithms().contains(algorithm);
|
||||||
|
} else {
|
||||||
|
return MANDATORY_IPSEC_ALGOS_SINCE_P.contains(algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] getKeyBytes(int byteLength) {
|
||||||
|
return Arrays.copyOf(KEY_DATA, byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
protected static byte[] getKey(int bitLength) {
|
protected static byte[] getKey(int bitLength) {
|
||||||
return Arrays.copyOf(KEY_DATA, bitLength / 8);
|
if (bitLength % 8 != 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid key length in bits" + bitLength);
|
||||||
|
}
|
||||||
|
return getKeyBytes(bitLength / 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static int getDomain(InetAddress address) {
|
protected static int getDomain(InetAddress address) {
|
||||||
|
|||||||
@@ -16,10 +16,33 @@
|
|||||||
|
|
||||||
package android.net.cts;
|
package android.net.cts;
|
||||||
|
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_AES_CMAC;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_AES_XCBC;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_CRYPT_AES_GCM;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_MD5;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA1;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA256;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
|
||||||
|
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
|
||||||
|
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
|
||||||
|
import static android.net.IpSecAlgorithm.CRYPT_AES_CTR;
|
||||||
import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
|
import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
|
||||||
import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
|
import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.AES_CMAC_ICV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.AES_CMAC_KEY_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.AES_CTR_BLK_SIZE;
|
||||||
|
import static android.net.cts.PacketUtils.AES_CTR_IV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN;
|
||||||
import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE;
|
import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE;
|
||||||
import static android.net.cts.PacketUtils.AES_GCM_IV_LEN;
|
import static android.net.cts.PacketUtils.AES_GCM_IV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.AES_XCBC_ICV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.AES_XCBC_KEY_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.CHACHA20_POLY1305_BLK_SIZE;
|
||||||
|
import static android.net.cts.PacketUtils.CHACHA20_POLY1305_ICV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.CHACHA20_POLY1305_IV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.HMAC_SHA512_ICV_LEN;
|
||||||
|
import static android.net.cts.PacketUtils.HMAC_SHA512_KEY_LEN;
|
||||||
import static android.net.cts.PacketUtils.IP4_HDRLEN;
|
import static android.net.cts.PacketUtils.IP4_HDRLEN;
|
||||||
import static android.net.cts.PacketUtils.IP6_HDRLEN;
|
import static android.net.cts.PacketUtils.IP6_HDRLEN;
|
||||||
import static android.net.cts.PacketUtils.TCP_HDRLEN_WITH_TIMESTAMP_OPT;
|
import static android.net.cts.PacketUtils.TCP_HDRLEN_WITH_TIMESTAMP_OPT;
|
||||||
@@ -27,15 +50,20 @@ import static android.net.cts.PacketUtils.UDP_HDRLEN;
|
|||||||
import static android.system.OsConstants.IPPROTO_TCP;
|
import static android.system.OsConstants.IPPROTO_TCP;
|
||||||
import static android.system.OsConstants.IPPROTO_UDP;
|
import static android.system.OsConstants.IPPROTO_UDP;
|
||||||
|
|
||||||
|
import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
|
||||||
|
import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import android.net.IpSecAlgorithm;
|
import android.net.IpSecAlgorithm;
|
||||||
import android.net.IpSecManager;
|
import android.net.IpSecManager;
|
||||||
import android.net.IpSecTransform;
|
import android.net.IpSecTransform;
|
||||||
import android.net.TrafficStats;
|
import android.net.TrafficStats;
|
||||||
|
import android.os.Build;
|
||||||
import android.platform.test.annotations.AppModeFull;
|
import android.platform.test.annotations.AppModeFull;
|
||||||
import android.system.ErrnoException;
|
import android.system.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
@@ -44,8 +72,11 @@ import android.system.OsConstants;
|
|||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.testutils.DevSdkIgnoreRule;
|
||||||
|
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||||
import com.android.testutils.SkipPresubmit;
|
import com.android.testutils.SkipPresubmit;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@@ -56,10 +87,15 @@ import java.net.DatagramSocket;
|
|||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||||
public class IpSecManagerTest extends IpSecBaseTest {
|
public class IpSecManagerTest extends IpSecBaseTest {
|
||||||
|
@Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
|
||||||
|
|
||||||
private static final String TAG = IpSecManagerTest.class.getSimpleName();
|
private static final String TAG = IpSecManagerTest.class.getSimpleName();
|
||||||
|
|
||||||
@@ -417,8 +453,12 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
switch (cryptOrAead.getName()) {
|
switch (cryptOrAead.getName()) {
|
||||||
case IpSecAlgorithm.CRYPT_AES_CBC:
|
case IpSecAlgorithm.CRYPT_AES_CBC:
|
||||||
return AES_CBC_IV_LEN;
|
return AES_CBC_IV_LEN;
|
||||||
|
case IpSecAlgorithm.CRYPT_AES_CTR:
|
||||||
|
return AES_CTR_IV_LEN;
|
||||||
case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
|
case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
|
||||||
return AES_GCM_IV_LEN;
|
return AES_GCM_IV_LEN;
|
||||||
|
case IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305:
|
||||||
|
return CHACHA20_POLY1305_IV_LEN;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"IV length unknown for algorithm" + cryptOrAead.getName());
|
"IV length unknown for algorithm" + cryptOrAead.getName());
|
||||||
@@ -433,8 +473,12 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
switch (cryptOrAead.getName()) {
|
switch (cryptOrAead.getName()) {
|
||||||
case IpSecAlgorithm.CRYPT_AES_CBC:
|
case IpSecAlgorithm.CRYPT_AES_CBC:
|
||||||
return AES_CBC_BLK_SIZE;
|
return AES_CBC_BLK_SIZE;
|
||||||
|
case IpSecAlgorithm.CRYPT_AES_CTR:
|
||||||
|
return AES_CTR_BLK_SIZE;
|
||||||
case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
|
case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
|
||||||
return AES_GCM_BLK_SIZE;
|
return AES_GCM_BLK_SIZE;
|
||||||
|
case IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305:
|
||||||
|
return CHACHA20_POLY1305_BLK_SIZE;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Blk size unknown for algorithm" + cryptOrAead.getName());
|
"Blk size unknown for algorithm" + cryptOrAead.getName());
|
||||||
@@ -516,7 +560,6 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
int blkSize,
|
int blkSize,
|
||||||
int truncLenBits)
|
int truncLenBits)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
int innerPacketSize = TEST_DATA.length + transportHdrLen + ipHdrLen;
|
int innerPacketSize = TEST_DATA.length + transportHdrLen + ipHdrLen;
|
||||||
int outerPacketSize =
|
int outerPacketSize =
|
||||||
PacketUtils.calculateEspPacketSize(
|
PacketUtils.calculateEspPacketSize(
|
||||||
@@ -663,6 +706,41 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
// checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1000);
|
// checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1000);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||||
|
@Test
|
||||||
|
public void testGetSupportedAlgorithms() throws Exception {
|
||||||
|
final Map<String, Integer> algoToRequiredMinSdk = new HashMap<>();
|
||||||
|
algoToRequiredMinSdk.put(CRYPT_AES_CBC, Build.VERSION_CODES.P);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_HMAC_MD5, Build.VERSION_CODES.P);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_HMAC_SHA1, Build.VERSION_CODES.P);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_HMAC_SHA256, Build.VERSION_CODES.P);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_HMAC_SHA384, Build.VERSION_CODES.P);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_HMAC_SHA512, Build.VERSION_CODES.P);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_CRYPT_AES_GCM, Build.VERSION_CODES.P);
|
||||||
|
|
||||||
|
// TODO: b/170424293 Use Build.VERSION_CODES.S when is finalized
|
||||||
|
algoToRequiredMinSdk.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1);
|
||||||
|
algoToRequiredMinSdk.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1);
|
||||||
|
|
||||||
|
final Set<String> supportedAlgos = IpSecAlgorithm.getSupportedAlgorithms();
|
||||||
|
|
||||||
|
// Verify all supported algorithms are valid
|
||||||
|
for (String algo : supportedAlgos) {
|
||||||
|
assertTrue("Found invalid algo " + algo, algoToRequiredMinSdk.keySet().contains(algo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all mandatory algorithms are supported
|
||||||
|
for (Entry<String, Integer> entry : algoToRequiredMinSdk.entrySet()) {
|
||||||
|
if (Math.min(getFirstApiLevel(), getVendorApiLevel()) >= entry.getValue()) {
|
||||||
|
assertTrue(
|
||||||
|
"Fail to support " + entry.getKey(),
|
||||||
|
supportedAlgos.contains(entry.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInterfaceCountersUdp4() throws Exception {
|
public void testInterfaceCountersUdp4() throws Exception {
|
||||||
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
@@ -849,6 +927,152 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IpSecAlgorithm buildCryptAesCtr() throws Exception {
|
||||||
|
return new IpSecAlgorithm(CRYPT_AES_CTR, getKeyBytes(AES_CTR_KEY_LEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpSecAlgorithm buildAuthHmacSha512() throws Exception {
|
||||||
|
return new IpSecAlgorithm(
|
||||||
|
AUTH_HMAC_SHA512, getKeyBytes(HMAC_SHA512_KEY_LEN), HMAC_SHA512_ICV_LEN * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCtrHmacSha512Tcp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCtr();
|
||||||
|
final IpSecAlgorithm auth = buildAuthHmacSha512();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
|
||||||
|
public void testAesCtrHmacSha512Tcp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCtr();
|
||||||
|
final IpSecAlgorithm auth = buildAuthHmacSha512();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCtrHmacSha512Udp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCtr();
|
||||||
|
final IpSecAlgorithm auth = buildAuthHmacSha512();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCtrHmacSha512Udp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCtr();
|
||||||
|
final IpSecAlgorithm auth = buildAuthHmacSha512();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpSecAlgorithm buildCryptAesCbc() throws Exception {
|
||||||
|
return new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpSecAlgorithm buildAuthAesXcbc() throws Exception {
|
||||||
|
return new IpSecAlgorithm(
|
||||||
|
AUTH_AES_XCBC, getKeyBytes(AES_XCBC_KEY_LEN), AES_XCBC_ICV_LEN * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpSecAlgorithm buildAuthAesCmac() throws Exception {
|
||||||
|
return new IpSecAlgorithm(
|
||||||
|
AUTH_AES_CMAC, getKeyBytes(AES_CMAC_KEY_LEN), AES_CMAC_ICV_LEN * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesXCbcTcp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesXcbc();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
|
||||||
|
public void testAesCbcAesXCbcTcp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesXcbc();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesXCbcUdp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesXcbc();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesXCbcUdp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesXcbc();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesCmacTcp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesCmac();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
|
||||||
|
public void testAesCbcAesCmacTcp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesCmac();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesCmacUdp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesCmac();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesCmacUdp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCbc();
|
||||||
|
final IpSecAlgorithm auth = buildAuthAesCmac();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAesGcm64Tcp4() throws Exception {
|
public void testAesGcm64Tcp4() throws Exception {
|
||||||
IpSecAlgorithm authCrypt =
|
IpSecAlgorithm authCrypt =
|
||||||
@@ -948,6 +1172,48 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IpSecAlgorithm buildAuthCryptChaCha20Poly1305() throws Exception {
|
||||||
|
return new IpSecAlgorithm(
|
||||||
|
AUTH_CRYPT_CHACHA20_POLY1305, AEAD_KEY, CHACHA20_POLY1305_ICV_LEN * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChaCha20Poly1305Tcp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
|
||||||
|
|
||||||
|
final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
|
||||||
|
public void testChaCha20Poly1305Tcp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
|
||||||
|
|
||||||
|
final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChaCha20Poly1305Udp4() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
|
||||||
|
|
||||||
|
final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChaCha20Poly1305Udp6() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
|
||||||
|
|
||||||
|
final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAesCbcHmacMd5Tcp4UdpEncap() throws Exception {
|
public void testAesCbcHmacMd5Tcp4UdpEncap() throws Exception {
|
||||||
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
@@ -1028,6 +1294,66 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCtrHmacSha512Tcp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCtr();
|
||||||
|
final IpSecAlgorithm auth = buildAuthHmacSha512();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCtrHmacSha512Udp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = buildCryptAesCtr();
|
||||||
|
final IpSecAlgorithm auth = buildAuthHmacSha512();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesXCbcTcp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
|
final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_XCBC, getKey(128), 96);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesXCbcUdp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
|
final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_XCBC, getKey(128), 96);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesCmacTcp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
|
final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_CMAC, getKey(128), 96);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAesCbcAesCmacUdp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
|
||||||
|
|
||||||
|
final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
|
final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_CMAC, getKey(128), 96);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAesGcm64Tcp4UdpEncap() throws Exception {
|
public void testAesGcm64Tcp4UdpEncap() throws Exception {
|
||||||
IpSecAlgorithm authCrypt =
|
IpSecAlgorithm authCrypt =
|
||||||
@@ -1076,6 +1402,24 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChaCha20Poly1305Tcp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
|
||||||
|
|
||||||
|
final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChaCha20Poly1305Udp4UdpEncap() throws Exception {
|
||||||
|
assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
|
||||||
|
|
||||||
|
final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
|
||||||
|
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCryptUdp4() throws Exception {
|
public void testCryptUdp4() throws Exception {
|
||||||
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
||||||
|
|||||||
@@ -43,17 +43,35 @@ public class PacketUtils {
|
|||||||
static final int UDP_HDRLEN = 8;
|
static final int UDP_HDRLEN = 8;
|
||||||
static final int TCP_HDRLEN = 20;
|
static final int TCP_HDRLEN = 20;
|
||||||
static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
|
static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
|
||||||
|
static final int ESP_BLK_SIZE = 4; // ESP has to be 4-byte aligned
|
||||||
|
|
||||||
// Not defined in OsConstants
|
// Not defined in OsConstants
|
||||||
static final int IPPROTO_IPV4 = 4;
|
static final int IPPROTO_IPV4 = 4;
|
||||||
static final int IPPROTO_ESP = 50;
|
static final int IPPROTO_ESP = 50;
|
||||||
|
|
||||||
// Encryption parameters
|
// Encryption parameters
|
||||||
static final int AES_GCM_IV_LEN = 8;
|
|
||||||
static final int AES_CBC_IV_LEN = 16;
|
static final int AES_CBC_IV_LEN = 16;
|
||||||
static final int AES_GCM_BLK_SIZE = 4;
|
|
||||||
static final int AES_CBC_BLK_SIZE = 16;
|
static final int AES_CBC_BLK_SIZE = 16;
|
||||||
|
|
||||||
|
static final int AES_CTR_KEY_LEN = 20;
|
||||||
|
static final int AES_CTR_BLK_SIZE = ESP_BLK_SIZE;
|
||||||
|
static final int AES_CTR_IV_LEN = 8;
|
||||||
|
|
||||||
|
// AEAD parameters
|
||||||
|
static final int AES_GCM_IV_LEN = 8;
|
||||||
|
static final int AES_GCM_BLK_SIZE = 4;
|
||||||
|
static final int CHACHA20_POLY1305_BLK_SIZE = ESP_BLK_SIZE;
|
||||||
|
static final int CHACHA20_POLY1305_IV_LEN = 8;
|
||||||
|
static final int CHACHA20_POLY1305_ICV_LEN = 16;
|
||||||
|
|
||||||
|
// Authentication parameters
|
||||||
|
static final int HMAC_SHA512_KEY_LEN = 64;
|
||||||
|
static final int HMAC_SHA512_ICV_LEN = 32;
|
||||||
|
static final int AES_XCBC_KEY_LEN = 16;
|
||||||
|
static final int AES_XCBC_ICV_LEN = 12;
|
||||||
|
static final int AES_CMAC_KEY_LEN = 16;
|
||||||
|
static final int AES_CMAC_ICV_LEN = 12;
|
||||||
|
|
||||||
// Encryption algorithms
|
// Encryption algorithms
|
||||||
static final String AES = "AES";
|
static final String AES = "AES";
|
||||||
static final String AES_CBC = "AES/CBC/NoPadding";
|
static final String AES_CBC = "AES/CBC/NoPadding";
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ java_library {
|
|||||||
"compatibility-device-util-axt",
|
"compatibility-device-util-axt",
|
||||||
"junit",
|
"junit",
|
||||||
"net-tests-utils",
|
"net-tests-utils",
|
||||||
|
"modules-utils-build",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
|||||||
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||||
import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION;
|
import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION;
|
||||||
|
|
||||||
|
import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
|
||||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -55,7 +56,6 @@ import android.net.wifi.WifiManager;
|
|||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemProperties;
|
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.system.OsConstants;
|
import android.system.OsConstants;
|
||||||
@@ -116,8 +116,7 @@ public final class CtsNetUtils {
|
|||||||
/** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */
|
/** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */
|
||||||
public boolean hasIpsecTunnelsFeature() {
|
public boolean hasIpsecTunnelsFeature() {
|
||||||
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
|
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
|
||||||
|| SystemProperties.getInt("ro.product.first_api_level", 0)
|
|| getFirstApiLevel() >= Build.VERSION_CODES.Q;
|
||||||
>= Build.VERSION_CODES.Q;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user