Merge "Make test for hardware AES support explicit." am: df3055d2ec

Change-Id: I7c117d6454af4abcad4ceb1f73d7a4e26d5b003f
diff --git a/luni/src/test/java/libcore/javax/crypto/HardwareAesTest.java b/luni/src/test/java/libcore/javax/crypto/HardwareAesTest.java
new file mode 100644
index 0000000..f79bb81
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/HardwareAesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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 libcore.javax.crypto;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import libcore.java.security.CpuFeatures;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class HardwareAesTest {
+
+    @Test
+    public void hardwareAesAvailability() {
+        // Test is only applicable if we know for sure that the device should support
+        // hardware AES.  That covers the important cases (non-emulated ARM and x86_64),
+        // For everything else we assume BoringSSL does the right thing.
+        assumeTrue(CpuFeatures.isKnownToSupportHardwareAes());
+        assertTrue(CpuFeatures.isAesHardwareAccelerated());
+    }
+}
diff --git a/support/src/test/java/libcore/java/security/CpuFeatures.java b/support/src/test/java/libcore/java/security/CpuFeatures.java
index 8ab610f..df65ed2 100644
--- a/support/src/test/java/libcore/java/security/CpuFeatures.java
+++ b/support/src/test/java/libcore/java/security/CpuFeatures.java
@@ -16,47 +16,87 @@
 
 package libcore.java.security;
 
+import android.system.Os;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import dalvik.system.VMRuntime;
 
-class CpuFeatures {
+public class CpuFeatures {
+    /** Machine architecture, determined from the "machine" value returned by uname() */
+    private enum Arch {
+        // 64bit ARM can return armv8 or aarch64.
+        // 32bit ARM should return armv7 or armv7a
+        ARM("^aarch.*|^arm.*"),
+        // 64bit Android and Linux generally return x86_64.
+        // 32bit Android and Linux generally return i686
+        // Other host architectures can potentially return x86 or i386.
+        X86("^x86.*|i386|i686");
+
+        private final String machineRegex;
+
+        Arch(String machineRegex) {
+            this.machineRegex = machineRegex;
+        }
+
+        /**
+         * Returns the architecture of this machine by matching against output from uname()
+         * against the regex for each known family.
+         */
+        public static Arch currentArch() {
+            String machine = Os.uname().machine;
+            for (Arch type : values()) {
+                if (machine.matches(type.machineRegex)) {
+                    return type;
+                }
+            }
+            throw new IllegalStateException("Unknown machine value: " + machine);
+        }
+    }
+
+    private enum InstructionSet {
+        ARM_32(Arch.ARM, "arm"),
+        ARM_64(Arch.ARM, "arm64"),
+        X86_32(Arch.X86, "x86"),
+        X86_64(Arch.X86, "x86_64");
+
+        private final Arch arch;
+        private final String name;
+
+        InstructionSet(Arch arch, String name) {
+            this.arch = arch;
+            this.name = name;
+        }
+
+        public Arch architecture() {
+            return arch;
+        }
+
+        /**
+         * Returns the current InstructionSet set by matching against the name fields above.
+         */
+        public static InstructionSet currentInstructionSet() {
+            // Always returns one of the values from VMRuntime.ABI_TO_INSTRUCTION_SET_MAP.
+            String instructionSet = VMRuntime.getCurrentInstructionSet();
+            for (InstructionSet set : values()) {
+                if (instructionSet.equals(set.name)) {
+                    return set;
+                }
+            }
+            throw new IllegalStateException("Unknown instruction set: " + instructionSet);
+        }
+    }
+
     private CpuFeatures() {
     }
 
-    static boolean isAESHardwareAccelerated() {
-        // Expectations based on CPU type: If these aren't met then Conscrypt
-        // integration tests will fail and the cause should be investigated.
-        String instructionSet = VMRuntime.getCurrentInstructionSet();
-        if (instructionSet.startsWith("arm")) {
-            // All ARM CPUs with the "aes" feature should have hardware AES.
-            List<String> features = getListFromCpuinfo("Features");
-            if (features != null && features.contains("aes")) {
-                return true;
-            }
-        } else if (instructionSet.startsWith("x86")) {
-            // x86 CPUs with the "aes" flag and running in 64bit mode should have hardware AES.
-            if ("x86_64".equals(instructionSet)) {
-                List<String> flags = getListFromCpuinfo("flags");
-                if (flags != null && flags.contains("aes")) {
-                    return true;
-                }
-            } else {
-                // Hardware AES not supported in 32bit mode.
-                return false;
-            }
-        }
-
-        // Otherwise trust Conscrypt NativeCrypto's own checks, for example if we're in an
-        // emulated ABI, it might bridge to a library that has accelerated AES instructions.
+    /**
+     * Returns true if this device has hardware AES support as determined by BoringSSL.
+     */
+    public static boolean isAesHardwareAccelerated() {
         try {
             Class<?> nativeCrypto = Class.forName("com.android.org.conscrypt.NativeCrypto");
             Method EVP_has_aes_hardware = nativeCrypto.getDeclaredMethod("EVP_has_aes_hardware");
@@ -71,33 +111,51 @@
         return false;
     }
 
-    private static String getFieldFromCpuinfo(String field) {
-        try {
-            BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"));
-            Pattern p = Pattern.compile(field + "\\s*:\\s*(.*)");
+    /**
+     * Returns true if this device should have hardware AES support based on CPU information.
+     *
+     * A return value of false means that acceleration isn't expected, but it may still be available
+     * e.g. via bridging to a native library in an emulated environment.
+     */
+    public static boolean isKnownToSupportHardwareAes() {
+        Arch architecture = Arch.currentArch();
+        InstructionSet instructionSet = InstructionSet.currentInstructionSet();
 
-            try {
-                String line;
-                while ((line = br.readLine()) != null) {
-                    Matcher m = p.matcher(line);
-                    if (m.matches()) {
-                        return m.group(1);
-                    }
+        if (!instructionSet.architecture().equals(architecture)) {
+            // Different architectures imply an emulated environment, so unable to determine if
+            // hardware acceleration is expected.  Assume not.
+            return false;
+        }
+
+        if (architecture.equals(Arch.ARM)) {
+            // All ARM CPUs (32 and 64 bit) with the "aes" feature should have hardware AES.
+            return cpuFieldContainsAes("Features");
+        } else if (instructionSet.equals(InstructionSet.X86_64)) {
+            // x86 CPUs with the "aes" flag and running in 64bit mode should have hardware AES.
+            // Hardware AES is not *expected* in 32bit mode, but may be available.
+            return cpuFieldContainsAes("flags");
+        }
+        return false;
+    }
+
+
+    /**
+     * Returns true if any line in the output from /proc/cpuinfo matches the provided
+     * field name and contains the word "aes" in its list of values.
+     *
+     * Example line from /proc/cpuinfo: Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32
+     */
+    private static boolean cpuFieldContainsAes(String fieldName) {
+        try (BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"))) {
+            String regex = "^" + fieldName + "\\s*:.*\\baes\\b.*";
+            String line;
+            while ((line = br.readLine()) != null) {
+                if (line.matches(regex)) {
+                    return true;
                 }
-            } finally {
-                br.close();
             }
         } catch (IOException ignored) {
         }
-
-        return null;
-    }
-
-    private static List<String> getListFromCpuinfo(String fieldName) {
-        String features = getFieldFromCpuinfo(fieldName);
-        if (features == null)
-            return null;
-
-        return Arrays.asList(features.split("\\s"));
+        return false;
     }
 }
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index d53117b..7521cf9 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -757,7 +757,7 @@
                             "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
                             "SSL_RSA_WITH_RC4_128_MD5",
                             "TLS_EMPTY_RENEGOTIATION_INFO_SCSV")
-            : CpuFeatures.isAESHardwareAccelerated() ? CIPHER_SUITES_ANDROID_AES_HARDWARE
+            : CpuFeatures.isAesHardwareAccelerated() ? CIPHER_SUITES_ANDROID_AES_HARDWARE
                     : CIPHER_SUITES_ANDROID_SOFTWARE;
 
     private static final Map<String, Class<? extends KeySpec>> PRIVATE_KEY_SPEC_CLASSES;