Merge "trusty: Add trusty stats test" am: 8a42990cc6

Original change: https://android-review.googlesource.com/c/platform/system/core/+/2318709

Change-Id: I73ed373b5bb91d3c45f75808d565e4c0efa6a61b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/trusty/stats/test/Android.bp b/trusty/stats/test/Android.bp
new file mode 100644
index 0000000..6b2bce9
--- /dev/null
+++ b/trusty/stats/test/Android.bp
@@ -0,0 +1,47 @@
+// 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"],
+}
+
+cc_test {
+    name: "trusty_stats_test",
+    vendor: true,
+    srcs: [
+        "stats_test.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libtrusty",
+        "libbinder",
+        "libbinder_trusty",
+        "libutils",
+
+        // AIDL interface deps versions, please refer to below link
+        // https://source.android.com/docs/core/architecture/aidl/stable-aidl#module-naming-rules
+        "android.frameworks.stats-V1-cpp",
+        "android.trusty.stats.nw.setter-cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    require_root: true,
+    proprietary: true,
+}
diff --git a/trusty/stats/test/README.md b/trusty/stats/test/README.md
new file mode 100644
index 0000000..45e6af8
--- /dev/null
+++ b/trusty/stats/test/README.md
@@ -0,0 +1,97 @@
+# Development Notes
+
+*    First get [repo_pull.py and gerrit.py](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/) from aosp.
+
+*    Although this repo is not currently in Trusty’s manifest, it’s sufficient to copy these two python scripts to the root of the Trusty project and run them from there. Make sure to follow the [repo_pull installation](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/#installation) steps if necessary.
+
+## Build
+
+Build Android:
+
+```sh
+source build/envsetup.sh
+lunch qemu_trusty_arm64-userdebug
+m
+```
+
+Build Trusty:
+
+```sh
+./trusty/vendor/google/aosp/scripts/build.py qemu-generic-arm64-test-debug --skip-tests 2>stderr.log
+```
+
+## Trusty PORT_TEST
+
+On QEmu:
+
+```sh
+./build-root/build-qemu-generic-arm64-test-debug/run --headless --boot-test "com.android.trusty.stats.test" --verbose
+```
+
+On device: (Build for your device's debug target on both Adroid and Trusty)
+
+```sh
+/vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
+```
+
+On device, in a loop:
+
+```sh
+cat << 'EOF' > metrics.sh
+#!/system/bin/sh
+TIMES=${1:-0}
+X=0
+while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
+do
+  echo "######################## stats.test $X " $(( X++ ));
+  /vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
+done
+EOF
+
+adb wait-for-device
+adb push metrics.sh /data/user/test/metrics.sh
+adb shell sh /data/user/test/metrics.sh
+```
+
+## Android Native Test
+
+On QEmu:
+
+```sh
+./build-root/build-qemu-generic-arm64-test-debug/run --headless --android $ANDROID_PROJECT_ROOT --shell-command "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test" --verbose
+```
+
+On device: (Build for your device's debug target on both Adroid and Trusty)
+
+```sh
+/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+```
+
+On device, in a loop:
+
+```sh
+cat << 'EOF' > metrics-nw.sh
+#!/system/bin/sh
+TIMES=${1:-0}
+X=0
+while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
+do
+  echo "######################## stats.test $X " $(( X++ ));
+  /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+done
+EOF
+
+adb wait-for-device
+adb push metrics.sh /data/user/test/metrics-nw.sh
+adb shell sh /data/user/test/metrics-nw.sh
+```
+
+## Trusty Backtrace analysis
+
+
+```
+$ export A2L=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-addr2line
+$ export OD=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-objdump
+$ $OD -d -C build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf > objdump.lst
+$ $A2L -e build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf 0xe5104
+```
diff --git a/trusty/stats/test/stats_test.cpp b/trusty/stats/test/stats_test.cpp
new file mode 100644
index 0000000..1edddeb
--- /dev/null
+++ b/trusty/stats/test/stats_test.cpp
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#include <errno.h>
+#include <getopt.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <condition_variable>
+#include <cstddef>
+#include <mutex>
+#include <queue>
+
+#include <android-base/expected.h>
+#include <android-base/logging.h>
+#include <android/frameworks/stats/BnStats.h>
+#include <android/frameworks/stats/IStats.h>
+#include <android/trusty/stats/nw/setter/IStatsSetter.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportRaw.h>
+#include <binder/RpcTransportTipcAndroid.h>
+#include <binder/RpcTrusty.h>
+#include <trusty/tipc.h>
+
+/** DOC:
+ * ./build-root/build-qemu-generic-arm64-test-debug/run \
+ *       --android $ANDROID_PROJECT_ROOT \
+ *       --headless --shell-command \
+ *       "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test"
+ *
+ * adb -s emulator-5554 shell \
+ *       /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+ */
+using ::android::base::unique_fd;
+using ::android::binder::Status;
+using ::android::frameworks::stats::BnStats;
+using ::android::frameworks::stats::IStats;
+using ::android::frameworks::stats::VendorAtom;
+using ::android::frameworks::stats::VendorAtomValue;
+using ::android::trusty::stats::nw::setter::IStatsSetter;
+
+constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0";
+constexpr const char kTrustyStatsSetterTest[] =
+        "com.android.frameworks.stats.trusty.test.relayer.istats_setter";
+constexpr const char kTrustyStatsSetterMetrics[] =
+        "com.android.frameworks.stats.trusty.metrics.istats_setter";
+constexpr const char kTrustyStatsPortTest[] = "com.android.trusty.stats.test";
+constexpr const char kTrustyCrashPortTest[] = "com.android.trusty.crashtest";
+constexpr const char kTrustyCrasherUuid[] = "7ee4dddc-177a-420a-96ea-5d413d88228e:crasher";
+
+enum TrustyAtoms : int32_t {
+    TrustyAppCrashed = 100072,
+    TrustyError = 100145,
+    TrustyStorageError = 100146
+};
+
+enum TestMsgHeader : int32_t {
+    TEST_PASSED = 0,
+    TEST_FAILED = 1,
+    TEST_MESSAGE = 2,
+};
+
+namespace android {
+namespace trusty {
+namespace stats {
+
+class Stats : public BnStats {
+  public:
+    Stats() : BnStats() {}
+
+    Status reportVendorAtom(const VendorAtom& vendorAtom) override {
+        const char* atomIdStr = vendorAtomStr(vendorAtom.atomId);
+        ALOGD("Vendor atom reported of type: %s\n", atomIdStr);
+        std::lock_guard lock(mLock);
+        mQueueVendorAtom.push(vendorAtom);
+        mCondVar.notify_one();
+        return Status::ok();
+    }
+
+    status_t getVendorAtom(VendorAtom* pVendorAtom, int64_t waitForMs) {
+        std::unique_lock lock(mLock);
+        while (mQueueVendorAtom.empty()) {
+            auto rc = mCondVar.wait_for(lock, std::chrono::milliseconds(waitForMs));
+            if (rc == std::cv_status::timeout) {
+                return TIMED_OUT;
+            }
+        }
+        *pVendorAtom = mQueueVendorAtom.front();
+        mQueueVendorAtom.pop();
+        return NO_ERROR;
+    }
+
+  private:
+    const char* vendorAtomStr(int32_t atomId) {
+        switch (atomId) {
+            case TrustyAtoms::TrustyAppCrashed:
+                return "TrustyAtoms::TrustyAppCrashed";
+            case TrustyAtoms::TrustyError:
+                return "TrustyAtoms::TrustyError";
+            case TrustyAtoms::TrustyStorageError:
+                return "TrustyAtoms::TrustyStorageError";
+            default:
+                return "unknown TrustyAtoms type";
+        }
+    }
+    std::mutex mLock;
+    std::condition_variable mCondVar;
+    std::queue<VendorAtom> mQueueVendorAtom;
+};
+
+class TrustyStatsTestBase : public ::testing::Test {
+  protected:
+    TrustyStatsTestBase(std::string&& portNameStatsSetter, std::string&& portNamePortTest)
+        : mPortTestFd(-1),
+          mPortNameStatsSetter(std::move(portNameStatsSetter)),
+          mPortNamePortTest(std::move(portNamePortTest)) {}
+
+    void SetUp() override {
+        // Commenting out the server portion because we do not have any direct
+        // incoming call Calls from TA are currently being handled on the mSession's
+        // extra thread. android::sp<::android::RpcServer> server =
+        // ::android::RpcServer::make(::android::RpcTransportCtxFactoryRaw::make());
+
+        mStats = android::sp<Stats>::make();
+        // Increasing number of incoming threads on mSession to be able to receive
+        // callbacks
+        auto session_initializer = [](sp<RpcSession>& session) {
+            session->setMaxIncomingThreads(1);
+        };
+
+        ASSERT_FALSE(mSession);
+        mSession = RpcTrustyConnectWithSessionInitializer(
+                kTrustyDefaultDeviceName, mPortNameStatsSetter.c_str(), session_initializer);
+        ASSERT_TRUE(mSession);
+
+        auto root = mSession->getRootObject();
+        ASSERT_TRUE(root);
+        auto statsSetter = IStatsSetter::asInterface(root);
+        ASSERT_TRUE(statsSetter);
+        statsSetter->setInterface(mStats);
+    }
+    void TearDown() override {
+        // close connection to unitest app
+        if (mPortTestFd != -1) {
+            tipc_close(mPortTestFd);
+        }
+        mPortTestFd = -1;
+
+        if (mSession) {
+            // shutdownAndWait here races with sending out the DecStrong
+            // messages after reportVendorAtom returns, so we delay it a little
+            // bit to give the messages time to go out over the transport
+            usleep(50000);
+            ASSERT_TRUE(mSession->shutdownAndWait(true));
+        }
+        mSession.clear();
+        mStats.clear();
+    }
+    void StartPortTest() {
+        // connect to unitest app
+        mPortTestFd = tipc_connect(kTrustyDefaultDeviceName, mPortNamePortTest.c_str());
+        if (mPortTestFd < 0) {
+            ALOGE("Failed to connect to '%s' app: %s\n", kTrustyStatsPortTest,
+                  strerror(-mPortTestFd));
+        }
+        ASSERT_GT(mPortTestFd, 0);
+    }
+    void WaitPortTestDone() {
+        // wait for test to complete
+        char rxBuf[1024];
+        const char prolog[] = "Trusty PORT_TEST:";
+        strncpy(rxBuf, prolog, sizeof(prolog) - 1);
+        char* pRxBuf = rxBuf + sizeof(prolog) - 1;
+        size_t remainingBufSize = sizeof(rxBuf) - sizeof(prolog) - 1;
+
+        ASSERT_NE(mPortTestFd, -1);
+        for (;;) {
+            int rc = read(mPortTestFd, pRxBuf, remainingBufSize);
+            ASSERT_GT(rc, 0);
+            ASSERT_LT(rc, (int)remainingBufSize);
+            if (pRxBuf[0] == TEST_PASSED) {
+                break;
+            } else if (pRxBuf[0] == TEST_FAILED) {
+                break;
+            } else if (pRxBuf[0] == TEST_MESSAGE) {
+                pRxBuf[0] = ' ';
+                write(STDOUT_FILENO, rxBuf, rc + sizeof(prolog) - 1);
+            } else {
+                ALOGE("Bad message header: %d\n", rxBuf[0]);
+                break;
+            }
+        }
+        ASSERT_EQ(pRxBuf[0], TEST_PASSED);
+    }
+
+    android::sp<Stats> mStats;
+
+  private:
+    android::sp<RpcSession> mSession;
+    int mPortTestFd;
+    std::string mPortNameStatsSetter;
+    std::string mPortNamePortTest;
+};
+
+class TrustyStatsTest : public TrustyStatsTestBase {
+  protected:
+    TrustyStatsTest() : TrustyStatsTestBase(kTrustyStatsSetterTest, kTrustyStatsPortTest) {}
+};
+
+class TrustyMetricsCrashTest : public TrustyStatsTestBase {
+  protected:
+    TrustyMetricsCrashTest()
+        : TrustyStatsTestBase(kTrustyStatsSetterMetrics, kTrustyCrashPortTest) {}
+};
+
+TEST_F(TrustyStatsTest, CheckAtoms) {
+    int atomAppCrashedCnt = 0;
+    int atomStorageErrorCnt = 0;
+    int atomTrustyErrorCnt = 0;
+    uint64_t blockForMs = 500;
+    StartPortTest();
+    WaitPortTestDone();
+    for (;;) {
+        VendorAtom vendorAtom;
+        auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
+        ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
+        if (status == TIMED_OUT) {
+            // No more atoms
+            break;
+        }
+
+        ASSERT_THAT(vendorAtom.atomId,
+                    ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
+                                     ::testing::Eq(TrustyAtoms::TrustyError),
+                                     ::testing::Eq(TrustyAtoms::TrustyStorageError)));
+        ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
+        switch (vendorAtom.atomId) {
+            case TrustyAtoms::TrustyAppCrashed:
+                ++atomAppCrashedCnt;
+                ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
+                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
+                break;
+            case TrustyAtoms::TrustyStorageError:
+                ++atomStorageErrorCnt;
+                ASSERT_EQ(vendorAtom.values[0].get<VendorAtomValue::intValue>(), 5);
+                ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()),
+                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
+                ASSERT_STREQ(String8(vendorAtom.values[2].get<VendorAtomValue::stringValue>()),
+                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
+                ASSERT_EQ(vendorAtom.values[3].get<VendorAtomValue::intValue>(), 1);
+                ASSERT_EQ(vendorAtom.values[4].get<VendorAtomValue::intValue>(), 3);
+                ASSERT_EQ(vendorAtom.values[5].get<VendorAtomValue::longValue>(),
+                          0x4BCDEFABBAFEDCBALL);
+                ASSERT_EQ(vendorAtom.values[6].get<VendorAtomValue::intValue>(), 4);
+                ASSERT_EQ(vendorAtom.values[7].get<VendorAtomValue::longValue>(), 1023);
+                break;
+            case TrustyAtoms::TrustyError:
+                ++atomTrustyErrorCnt;
+                break;
+            default:
+                FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
+                break;
+        }
+    };
+    ASSERT_EQ(atomAppCrashedCnt, 1);
+    ASSERT_EQ(atomStorageErrorCnt, 1);
+    ASSERT_EQ(atomTrustyErrorCnt, 0);
+}
+
+TEST_F(TrustyMetricsCrashTest, CheckTrustyCrashAtoms) {
+    const std::vector<uint32_t> kExpectedCrashReasonsArm64{
+            0x00000001U,  // exit_failure (twice)
+            0x00000001U,
+            0x92000004U,  // read_null_ptr
+            0xf200002aU,  // brk_instruction
+            0x92000004U,  // read_bad_ptr
+            0x92000044U,  // crash_write_bad_ptr
+            0x9200004fU,  // crash_write_ro_ptr
+            0x8200000fU,  // crash_exec_rodata
+            0x8200000fU,  // crash_exec_data
+    };
+    const std::vector<uint32_t> kExpectedCrashReasonsArm32{
+            0x00000001U,  // exit_failure (twice)
+            0x00000001U,
+            0x20000007U,  // read_null_ptr
+            0x20000007U,  // read_bad_ptr
+            0x20000807U,  // crash_write_bad_ptr
+            0x2000080fU,  // crash_write_ro_ptr
+            0x3000000fU,  // crash_exec_rodata
+            0x3000000fU,  // crash_exec_data
+    };
+
+    int expectedAtomCnt = 7;
+    int atomAppCrashedCnt = 0;
+    int atomStorageErrorCnt = 0;
+    int atomTrustyErrorCnt = 0;
+    std::vector<uint32_t> atomCrashReasons;
+    uint64_t blockForMs = 500;
+    StartPortTest();
+    WaitPortTestDone();
+    for (;;) {
+        VendorAtom vendorAtom;
+        auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
+        ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
+        if (status == TIMED_OUT) {
+            // No more atoms
+            break;
+        }
+
+        ASSERT_THAT(vendorAtom.atomId,
+                    ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
+                                     ::testing::Eq(TrustyAtoms::TrustyError),
+                                     ::testing::Eq(TrustyAtoms::TrustyStorageError)));
+        ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
+
+        switch (vendorAtom.atomId) {
+            case TrustyAtoms::TrustyAppCrashed:
+                ++atomAppCrashedCnt;
+                ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
+                             kTrustyCrasherUuid);
+                atomCrashReasons.push_back(vendorAtom.values[1].get<VendorAtomValue::intValue>());
+                break;
+            case TrustyAtoms::TrustyStorageError:
+                ++atomStorageErrorCnt;
+                break;
+            case TrustyAtoms::TrustyError:
+                ++atomTrustyErrorCnt;
+                ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()), "");
+                break;
+            default:
+                FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
+        }
+    }
+    ASSERT_GE(atomAppCrashedCnt, expectedAtomCnt - 1);
+    ASSERT_EQ(atomStorageErrorCnt, 0);
+    // There is one dropped event left over from Trusty boot,
+    // it may show up here
+    ASSERT_LE(atomTrustyErrorCnt, 1);
+    ASSERT_THAT(atomCrashReasons,
+                ::testing::AnyOf(kExpectedCrashReasonsArm64, kExpectedCrashReasonsArm32));
+};
+
+}  // namespace stats
+}  // namespace trusty
+}  // namespace android