Add sts-common-util to platform_testing

Bug: 190537825
Test: sts-tradefed run sts-dynamic-full
Change-Id: I41b0b4893fb41a06a7dea502c6c3a4a4a11eda8c
Merged-In: I41b0b4893fb41a06a7dea502c6c3a4a4a11eda8c
diff --git a/libraries/sts-common-util/OWNERS b/libraries/sts-common-util/OWNERS
new file mode 100644
index 0000000..69d2081
--- /dev/null
+++ b/libraries/sts-common-util/OWNERS
@@ -0,0 +1,3 @@
+# STS Owners
+cdombroski@google.com
+musashi@google.com
diff --git a/libraries/sts-common-util/device-side/Android.bp b/libraries/sts-common-util/device-side/Android.bp
new file mode 100644
index 0000000..1692a7c
--- /dev/null
+++ b/libraries/sts-common-util/device-side/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_static {
+    name: "sts-device-util",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "sts-common-util-devicesidelib",
+    ],
+
+    libs: [
+        "compatibility-device-util-axt",
+    ],
+}
diff --git a/libraries/sts-common-util/device-side/src/com/android/sts/common/util/StsExtraBusinessLogicTestCase.java b/libraries/sts-common-util/device-side/src/com/android/sts/common/util/StsExtraBusinessLogicTestCase.java
new file mode 100644
index 0000000..0eede16
--- /dev/null
+++ b/libraries/sts-common-util/device-side/src/com/android/sts/common/util/StsExtraBusinessLogicTestCase.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 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.android.sts.common.util;
+
+import com.android.compatibility.common.util.ExtraBusinessLogicTestCase;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Rule;
+import org.junit.runner.Description;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/** The device-side implementation of StsLogic. */
+public class StsExtraBusinessLogicTestCase extends ExtraBusinessLogicTestCase implements StsLogic {
+
+    private LocalDate deviceSpl = null;
+    @Rule public DescriptionProvider descriptionProvider = new DescriptionProvider();
+
+    protected StsExtraBusinessLogicTestCase() {
+        mDependentOnBusinessLogic = false;
+    }
+
+    @Override
+    public List<String> getExtraBusinessLogics() {
+        String stsDynamicPlan =
+                InstrumentationRegistry.getArguments().getString("sts-dynamic-plan");
+        switch (stsDynamicPlan) {
+            case "incremental":
+                return StsLogic.STS_EXTRA_BUSINESS_LOGIC_INCREMENTAL;
+            case "full":
+                return StsLogic.STS_EXTRA_BUSINESS_LOGIC_FULL;
+            default:
+                throw new RuntimeException(
+                        "Could not find Dynamic STS plan in InstrumentationRegistry arguments");
+        }
+    }
+
+    @Override
+    public Description getTestDescription() {
+        return descriptionProvider.getDescription();
+    }
+
+    @Override
+    public LocalDate getDeviceSpl() {
+        if (deviceSpl == null) {
+            deviceSpl = SplUtils.localDateFromSplString(Build.VERSION.SECURITY_PATCH);
+        }
+        return deviceSpl;
+    }
+
+    /**
+     * Specify the latest release bulletin. Control this from the command-line with the following:
+     * --test-arg
+     * com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:release-bulletin-spl:=2020-06
+     */
+    @Override
+    public LocalDate getReleaseBulletinSpl() {
+        String releaseBulletinSpl =
+                InstrumentationRegistry.getArguments().getString("release-bulletin-spl");
+        if (releaseBulletinSpl == null) {
+            return null;
+        }
+        // bulletin is released by month; add any day - only the year and month are compared.
+        releaseBulletinSpl =
+                String.format("%s-%02d", releaseBulletinSpl, SplUtils.Type.PARTIAL.day);
+        return SplUtils.localDateFromSplString(releaseBulletinSpl);
+    }
+
+    @Override
+    public void logInfo(String logTag, String format, Object... args) {
+        Log.i(logTag, String.format(format, args));
+    }
+
+    @Override
+    public void logDebug(String logTag, String format, Object... args) {
+        Log.d(logTag, String.format(format, args));
+    }
+
+    @Override
+    public void logWarn(String logTag, String format, Object... args) {
+        Log.w(logTag, String.format(format, args));
+    }
+
+    @Override
+    public void logError(String logTag, String format, Object... args) {
+        Log.e(logTag, String.format(format, args));
+    }
+}
diff --git a/libraries/sts-common-util/host-side/Android.bp b/libraries/sts-common-util/host-side/Android.bp
new file mode 100644
index 0000000..471056f
--- /dev/null
+++ b/libraries/sts-common-util/host-side/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+    name: "sts-host-util",
+    defaults: ["cts_error_prone_rules"],
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "sts-common-util-hostsidelib",
+    ],
+
+    libs: [
+        "compatibility-tradefed",
+        "tradefed",
+    ],
+}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/tradefed/testtype/StsExtraBusinessLogicHostTestBase.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/tradefed/testtype/StsExtraBusinessLogicHostTestBase.java
new file mode 100644
index 0000000..c7fad5b
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/tradefed/testtype/StsExtraBusinessLogicHostTestBase.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 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.android.sts.common.tradefed.testtype;
+
+import com.android.compatibility.common.tradefed.testtype.ExtraBusinessLogicHostTestBase;
+import com.android.ddmlib.Log;
+import com.android.sts.common.util.DescriptionProvider;
+import com.android.sts.common.util.SplUtils;
+import com.android.sts.common.util.StsLogic;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.Rule;
+import org.junit.runner.Description;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/** The host-side implementation of StsLogic. */
+public class StsExtraBusinessLogicHostTestBase extends ExtraBusinessLogicHostTestBase
+        implements StsLogic {
+
+    private LocalDate deviceSpl = null;
+    @Rule public DescriptionProvider descriptionProvider = new DescriptionProvider();
+
+    public StsExtraBusinessLogicHostTestBase() {
+        super();
+        mDependentOnBusinessLogic = false;
+    }
+
+    @Override
+    public List<String> getExtraBusinessLogics() {
+        String stsDynamicPlan = getBuild().getBuildAttributes().get("sts-dynamic-plan");
+        switch (stsDynamicPlan) {
+            case "incremental":
+                return StsLogic.STS_EXTRA_BUSINESS_LOGIC_INCREMENTAL;
+            case "full":
+                return StsLogic.STS_EXTRA_BUSINESS_LOGIC_FULL;
+            default:
+                throw new RuntimeException("Could not find Dynamic STS plan in build attributes");
+        }
+    }
+
+    @Override
+    public Description getTestDescription() {
+        return descriptionProvider.getDescription();
+    }
+
+    @Override
+    public LocalDate getDeviceSpl() {
+        if (deviceSpl == null) {
+            try {
+                String splString = getDevice().getProperty("ro.build.version.security_patch");
+                deviceSpl = SplUtils.localDateFromSplString(splString);
+            } catch (DeviceNotAvailableException e) {
+                throw new RuntimeException("couldn't get the security patch level", e);
+            }
+        }
+        return deviceSpl;
+    }
+
+    /**
+     * Specify the latest release bulletin. Control this from the command-line with the following
+     * command line argument: --build-attribute "release-bulletin-spl=2021-06"
+     */
+    @Override
+    public LocalDate getReleaseBulletinSpl() {
+        String releaseBulletinSpl = getBuild().getBuildAttributes().get("release-bulletin-spl");
+        if (releaseBulletinSpl == null) {
+            return null;
+        }
+        // bulletin is released by month; add any day - only the year and month are compared.
+        releaseBulletinSpl =
+                String.format("%s-%02d", releaseBulletinSpl, SplUtils.Type.PARTIAL.day);
+        return SplUtils.localDateFromSplString(releaseBulletinSpl);
+    }
+
+    @Override
+    public void logInfo(String logTag, String format, Object... args) {
+        Log.i(logTag, String.format(format, args));
+    }
+
+    @Override
+    public void logDebug(String logTag, String format, Object... args) {
+        Log.d(logTag, String.format(format, args));
+    }
+
+    @Override
+    public void logWarn(String logTag, String format, Object... args) {
+        Log.w(logTag, String.format(format, args));
+    }
+
+    @Override
+    public void logError(String logTag, String format, Object... args) {
+        Log.e(logTag, String.format(format, args));
+    }
+}
diff --git a/libraries/sts-common-util/util/Android.bp b/libraries/sts-common-util/util/Android.bp
new file mode 100644
index 0000000..b94bcb0
--- /dev/null
+++ b/libraries/sts-common-util/util/Android.bp
@@ -0,0 +1,47 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Build the common utility library for use device-side
+java_library_static {
+    name: "sts-common-util-devicesidelib",
+    visibility: [
+        "//platform_testing/libraries/sts-common-util/device-side",
+    ],
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "junit",
+        "platform-test-annotations",
+        "compatibility-common-util-devicesidelib",
+    ],
+}
+
+java_library_host {
+    name: "sts-common-util-hostsidelib",
+    visibility: [
+        "//platform_testing/libraries/sts-common-util/host-side",
+    ],
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "compatibility-common-util-hostsidelib",
+    ],
+    libs: [
+        "junit",
+        "platform-test-annotations",
+    ],
+}
diff --git a/libraries/sts-common-util/util/src/com/android/sts/common/util/DescriptionProvider.java b/libraries/sts-common-util/util/src/com/android/sts/common/util/DescriptionProvider.java
new file mode 100644
index 0000000..60d46ea
--- /dev/null
+++ b/libraries/sts-common-util/util/src/com/android/sts/common/util/DescriptionProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.android.sts.common.util;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/** Provide a way for tests to get their description while running. */
+public class DescriptionProvider extends TestWatcher {
+    private volatile Description description;
+
+    @Override
+    protected void starting(Description description) {
+        this.description = description;
+    }
+
+    public Description getDescription() {
+        return description;
+    }
+}
diff --git a/libraries/sts-common-util/util/src/com/android/sts/common/util/SplUtils.java b/libraries/sts-common-util/util/src/com/android/sts/common/util/SplUtils.java
new file mode 100644
index 0000000..afb7b12
--- /dev/null
+++ b/libraries/sts-common-util/util/src/com/android/sts/common/util/SplUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 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.android.sts.common.util;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/** Tools for Security Patch Levels and LocalDates representing them. */
+public final class SplUtils {
+    private static final ZoneId UTC_ZONE_ID = ZoneId.of("UTC");
+    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    public enum Type {
+        PARTIAL(1), // platform
+        COMPLETE(5); // device-specific (kernel, soc, etc)
+
+        public final int day;
+
+        Type(int day) {
+            this.day = day;
+        }
+    }
+
+    public static LocalDate localDateFromMillis(long millis) {
+        return Instant.ofEpochMilli(millis).atZone(UTC_ZONE_ID).toLocalDate();
+    }
+
+    public static LocalDate localDateFromSplString(String spl) {
+        return LocalDate.parse(spl, formatter);
+    }
+
+    public static String format(LocalDate date) {
+        return date.format(formatter);
+    }
+}
diff --git a/libraries/sts-common-util/util/src/com/android/sts/common/util/StsLogic.java b/libraries/sts-common-util/util/src/com/android/sts/common/util/StsLogic.java
new file mode 100644
index 0000000..648199c
--- /dev/null
+++ b/libraries/sts-common-util/util/src/com/android/sts/common/util/StsLogic.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 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.android.sts.common.util;
+
+import static org.junit.Assume.*;
+import static org.junit.Assert.*;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.BusinessLogicMapStore;
+
+import org.junit.runner.Description;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** Common STS extra business logic for host-side and device-side to implement. */
+public interface StsLogic {
+
+    static final String LOG_TAG = StsLogic.class.getSimpleName();
+
+    // keep in sync with google3:
+    // //wireless/android/partner/apbs/*/config/xtsbgusinesslogic/sts_business_logic.gcl
+    List<String> STS_EXTRA_BUSINESS_LOGIC_FULL = Arrays.asList(new String[]{
+        "uploadSpl",
+        "uploadModificationTime",
+        "declaredSpl",
+    });
+    List<String> STS_EXTRA_BUSINESS_LOGIC_INCREMENTAL = Arrays.asList(new String[]{
+        "uploadSpl",
+        "uploadModificationTime",
+        "declaredSpl",
+        "incremental",
+    });
+
+    Description getTestDescription();
+
+    LocalDate getDeviceSpl();
+
+    LocalDate getReleaseBulletinSpl();
+
+    default long[] getCveBugIds() {
+        AsbSecurityTest annotation = getTestDescription().getAnnotation(AsbSecurityTest.class);
+        if (annotation == null) {
+            return null;
+        }
+        return annotation.cveBugId();
+    }
+
+    default boolean isBugSplDataKnownMissing() {
+        long[] bugIds = getCveBugIds();
+        if (bugIds == null) {
+            // no spl data, don't complain
+            return true;
+        }
+        // true if the bug id is older than ~ June 2020
+        return Arrays.stream(bugIds).min().getAsLong() < 157905780;
+    }
+
+    default LocalDate getMinTestSpl() {
+        Map<String, String> map = BusinessLogicMapStore.getMap("security_bulletins");
+        if (map == null) {
+            throw new IllegalArgumentException("Could not find the security bulletin map");
+        }
+        LocalDate minSpl = null;
+        for (long cveBugId : getCveBugIds()) {
+            String splString = map.get(Long.toString(cveBugId));
+            if (splString == null) {
+                // This bug id wasn't found in the map.
+                // This is a new test or the bug was removed from the bulletin and this is an old
+                // binary. Neither is a critical issue and the test will run in these cases.
+                // New test: developer should be able to write the test without getting blocked.
+                // Removed bug + old binary: test will run.
+                logWarn(LOG_TAG, "could not find the CVE bug %d in the spl map", cveBugId);
+                continue;
+            }
+            LocalDate spl = SplUtils.localDateFromSplString(splString);
+            if (minSpl == null) {
+                minSpl = spl;
+            } else if (spl.isBefore(minSpl)) {
+                minSpl = spl;
+            }
+        }
+        return minSpl;
+    }
+
+    default LocalDate getMinModificationDate() {
+        Map<String, String> map = BusinessLogicMapStore.getMap("sts_modification_times");
+        if (map == null) {
+            throw new IllegalArgumentException("Could not find the modification date map");
+        }
+        LocalDate minModificationDate = null;
+        for (long cveBugId : getCveBugIds()) {
+            String modificationMillisString = map.get(Long.toString(cveBugId));
+            if (modificationMillisString == null) {
+                logInfo(LOG_TAG,
+                        "Could not find the CVE bug %d in the modification date map", cveBugId);
+                continue;
+            }
+            LocalDate modificationDate =
+                    SplUtils.localDateFromMillis(Long.parseLong(modificationMillisString));
+            if (minModificationDate == null) {
+                minModificationDate = modificationDate;
+            } else if (modificationDate.isBefore(minModificationDate)) {
+                minModificationDate = modificationDate;
+            }
+        }
+        return minModificationDate;
+    }
+
+    default boolean shouldSkipIncremental() {
+        logDebug(LOG_TAG, "filtering by incremental");
+
+        long[] bugIds = getCveBugIds();
+        if (bugIds == null) {
+            // There were no @AsbSecurityTest annotations
+            logInfo(LOG_TAG, "not an ASB test");
+            return false;
+        }
+
+        // check if test spl is older than the past 6 months from the device spl
+        LocalDate deviceSpl = getDeviceSpl();
+        LocalDate incrementalCutoffSpl = deviceSpl.plusMonths(-6);
+
+        LocalDate minTestModifiedDate = getMinModificationDate();
+        if (minTestModifiedDate == null) {
+            // could not get the modification date - run the test
+            if (isBugSplDataKnownMissing()) {
+                logDebug(LOG_TAG, "no data for this old test");
+                return true;
+            }
+            return false;
+        }
+        if (minTestModifiedDate.isAfter(incrementalCutoffSpl)) {
+            logDebug(LOG_TAG, "the test was recently modified");
+            return false;
+        }
+
+        LocalDate minTestSpl = getMinTestSpl();
+        if (minTestSpl == null) {
+            // could not get the test spl - run the test
+            logWarn(LOG_TAG, "could not get the test SPL");
+            return false;
+        }
+        if (minTestSpl.isAfter(incrementalCutoffSpl)) {
+            logDebug(LOG_TAG, "the test has a recent SPL");
+            return false;
+        }
+
+        logDebug(LOG_TAG, "test should skip");
+        return true;
+    }
+
+    default boolean shouldSkipDeclaredSpl() {
+        if (getCveBugIds() == null) {
+            // There were no @AsbSecurityTest annotations
+            logInfo(LOG_TAG, "not an ASB test");
+            return false;
+        }
+
+        LocalDate releaseBulletinSpl = getReleaseBulletinSpl();
+        LocalDate minTestSpl = getMinTestSpl();
+        if (releaseBulletinSpl != null && !isBugSplDataKnownMissing()) {
+            // assert that the test has a known SPL when we expect the data to be fresh
+            assertNotNull("Unknown SPL for new CVE", minTestSpl);
+
+            // set the days to be the same so we only compare year-month
+            releaseBulletinSpl = releaseBulletinSpl.withDayOfMonth(minTestSpl.getDayOfMonth());
+            // the test SPL can't be equal to or after the release bulletin SPL
+            assertFalse("Newer SPL than release bulletin", releaseBulletinSpl.isBefore(minTestSpl));
+        }
+        if (minTestSpl == null) {
+            // no SPL for this test; run normally
+            return false;
+        }
+
+        // skip if the test is newer than the device SPL
+        LocalDate deviceSpl = getDeviceSpl();
+        return minTestSpl.isAfter(deviceSpl);
+    }
+
+    default void skip(String message) {
+        assumeTrue(message, false);
+    }
+
+    public void logInfo(String logTag, String format, Object... args);
+
+    public void logDebug(String logTag, String format, Object... args);
+
+    public void logWarn(String logTag, String format, Object... args);
+
+    public void logError(String logTag, String format, Object... args);
+}