Merge "riscv64: fix debuggerd_test build."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index d09498f..d20de6b 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -368,6 +368,7 @@
     name: "crash_dump",
     srcs: [
         "crash_dump.cpp",
+        "tombstone_handler.cpp",
         "util.cpp",
     ],
     defaults: ["debuggerd_defaults"],
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index 394447b..8633cb8 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "name": "libtombstoned_client_rust_test"
+    },
+    {
+      "name": "MicrodroidHostTestCases"
     }
   ],
   "hwasan-presubmit": [
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 442392d..cf4c5d5 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -59,7 +59,7 @@
 #include "libdebuggerd/utility.h"
 
 #include "debuggerd/handler.h"
-#include "tombstoned/tombstoned.h"
+#include "tombstone_handler.h"
 
 #include "protocol.h"
 #include "util.h"
@@ -215,8 +215,8 @@
     // If we abort before we get an output fd, contact tombstoned to let any
     // potential listeners know that we failed.
     if (!g_tombstoned_connected) {
-      if (!tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, &g_proto_fd,
-                              kDebuggerdAnyIntercept)) {
+      if (!connect_tombstone_server(g_target_thread, &g_tombstoned_socket, &g_output_fd,
+                                    &g_proto_fd, kDebuggerdAnyIntercept)) {
         // We failed to connect, not much we can do.
         LOG(ERROR) << "failed to connected to tombstoned to report failure";
         _exit(1);
@@ -589,8 +589,8 @@
   {
     ATRACE_NAME("tombstoned_connect");
     LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;
-    g_tombstoned_connected = tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,
-                                                &g_proto_fd, dump_type);
+    g_tombstoned_connected = connect_tombstone_server(g_target_thread, &g_tombstoned_socket,
+                                                      &g_output_fd, &g_proto_fd, dump_type);
   }
 
   if (g_tombstoned_connected) {
@@ -673,7 +673,8 @@
 
   // Close stdout before we notify tombstoned of completion.
   close(STDOUT_FILENO);
-  if (g_tombstoned_connected && !tombstoned_notify_completion(g_tombstoned_socket.get())) {
+  if (g_tombstoned_connected &&
+      !notify_completion(g_tombstoned_socket.get(), g_output_fd.get(), g_proto_fd.get())) {
     LOG(ERROR) << "failed to notify tombstoned of completion";
   }
 
diff --git a/debuggerd/crasher/riscv64/crashglue.S b/debuggerd/crasher/riscv64/crashglue.S
index 5334887..42f59b3 100644
--- a/debuggerd/crasher/riscv64/crashglue.S
+++ b/debuggerd/crasher/riscv64/crashglue.S
@@ -6,39 +6,38 @@
 	sd ra, 8(sp)
 	.cfi_offset ra, -8
 
-	li	x0,0xdead0000+0
-	li	x1,0xdead0000+1
-	li	x2,0xdead0000+2
-	li	x3,0xdead0000+3
-	li	x4,0xdead0000+4
-	li	x5,0xdead0000+5
-	li	x6,0xdead0000+6
-	li	x7,0xdead0000+7
-	li	x8,0xdead0000+8
-	li	x9,0xdead0000+9
-	li	x10,0xdead0000+10
-	li	x11,0xdead0000+11
-	li	x12,0xdead0000+12
-	li	x13,0xdead0000+13
-	li	x14,0xdead0000+14
-	li	x15,0xdead0000+15
-	li	x16,0xdead0000+16
-	li	x17,0xdead0000+17
-	li	x18,0xdead0000+18
-	li	x19,0xdead0000+19
-	li	x20,0xdead0000+20
-	li	x21,0xdead0000+21
-	li	x22,0xdead0000+22
-	li	x23,0xdead0000+23
-	li	x24,0xdead0000+24
-	li	x25,0xdead0000+25
-	li	x26,0xdead0000+26
-	li	x27,0xdead0000+27
-	li	x28,0xdead0000+28
-	# don't trash the stack otherwise the signal handler won't run
-	#li	x29,0xdead0000+29
-	li	x30,0xdead0000+30
-	li	x31,0xdead0000+31
+	li	x0,0xa5a50000
+	li	x1,0xa5a50001
+	li	x2,0xa5a50002
+	li	x3,0xa5a50003
+	li	x4,0xa5a50004
+	li	x5,0xa5a50005
+	li	x6,0xa5a50006
+	li	x7,0xa5a50007
+	li	x8,0xa5a50008
+	li	x9,0xa5a50009
+	li	x10,0xa5a50010
+	li	x11,0xa5a50011
+	li	x12,0xa5a50012
+	li	x13,0xa5a50013
+	li	x14,0xa5a50014
+	li	x15,0xa5a50015
+	li	x16,0xa5a50016
+	li	x17,0xa5a50017
+	li	x18,0xa5a50018
+	li	x19,0xa5a50019
+	li	x20,0xa5a50020
+	li	x21,0xa5a50021
+	li	x22,0xa5a50022
+	li	x23,0xa5a50023
+	li	x24,0xa5a50024
+	li	x25,0xa5a50025
+	li	x26,0xa5a50026
+	li	x27,0xa5a50027
+	li	x28,0xa5a50028
+	li	x29,0xa5a50029
+	li	x30,0xa5a50030
+	li	x31,0xa5a50031
 
 	li sp, 0
 	ld t2, 0(zero)
diff --git a/debuggerd/tombstone_handler.cpp b/debuggerd/tombstone_handler.cpp
new file mode 100644
index 0000000..09df6d9
--- /dev/null
+++ b/debuggerd/tombstone_handler.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2023, 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 "tombstoned/tombstoned.h"
+
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <linux/vm_sockets.h>
+#include "util.h"
+
+using android::base::unique_fd;
+
+/*
+  Port number that VirtualMachineService listens on connections from the guest VMs.
+  Kep in sync with IVirtualMachineService.aidl
+*/
+const unsigned int VM_TOMBSTONES_SERVICE_PORT = 2000;
+
+static bool is_microdroid() {
+  return android::base::GetProperty("ro.hardware", "") == "microdroid";
+}
+
+static bool connect_tombstone_server_microdroid(unique_fd* text_output_fd,
+                                                unique_fd* proto_output_fd,
+                                                DebuggerdDumpType dump_type) {
+  // We do not wait for the property to be set, the default behaviour is not export tombstones.
+  if (!android::base::GetBoolProperty("microdroid_manager.export_tombstones.enabled", false)) {
+    LOG(WARNING) << "exporting tombstones is not enabled";
+    return false;
+  }
+
+  // Microdroid supports handling requests originating from crash_dump which
+  // supports limited dump types. Java traces and incept management are not supported.
+  switch (dump_type) {
+    case kDebuggerdNativeBacktrace:
+    case kDebuggerdTombstone:
+    case kDebuggerdTombstoneProto:
+      break;
+
+    default:
+      LOG(WARNING) << "Requested dump type: " << dump_type << " "
+                   << "not supported";
+  }
+
+  int fd1 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0));
+  int fd2 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0));
+  if (fd1 < 0 || fd2 < 0) {
+    LOG(WARNING) << "Unable to create virtual socket for writing tombstones";
+    return false;
+  }
+
+  unique_fd vsock_output_fd(fd1), vsock_proto_fd(fd2);
+
+  struct sockaddr_vm sa = (struct sockaddr_vm){
+      .svm_family = AF_VSOCK,
+      .svm_port = VM_TOMBSTONES_SERVICE_PORT,
+      .svm_cid = VMADDR_CID_HOST,
+  };
+
+  if (TEMP_FAILURE_RETRY(connect(vsock_output_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) {
+    PLOG(WARNING) << "Unable to connect to tombstone service in host";
+    return false;
+  }
+
+  if (dump_type == kDebuggerdTombstoneProto) {
+    if (TEMP_FAILURE_RETRY(connect(vsock_proto_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) {
+      PLOG(WARNING) << "Unable to connect to tombstone service in host";
+      return false;
+    }
+  }
+
+  *text_output_fd = std::move(vsock_output_fd);
+  if (proto_output_fd) {
+    *proto_output_fd = std::move(vsock_proto_fd);
+  }
+  return true;
+}
+
+static bool notify_completion_microdroid(int vsock_out, int vsock_proto) {
+  if (shutdown(vsock_out, SHUT_WR) || shutdown(vsock_proto, SHUT_WR)) return false;
+  return true;
+}
+bool connect_tombstone_server(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd,
+                              unique_fd* proto_output_fd, DebuggerdDumpType dump_type) {
+  if (is_microdroid()) {
+    return connect_tombstone_server_microdroid(text_output_fd, proto_output_fd, dump_type);
+  }
+  return tombstoned_connect(pid, tombstoned_socket, text_output_fd, proto_output_fd, dump_type);
+}
+
+bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto) {
+  if (is_microdroid()) {
+    return notify_completion_microdroid(vsock_out, vsock_proto);
+  }
+  return tombstoned_notify_completion(tombstoned_socket);
+}
diff --git a/debuggerd/tombstone_handler.h b/debuggerd/tombstone_handler.h
new file mode 100644
index 0000000..8726bd3
--- /dev/null
+++ b/debuggerd/tombstone_handler.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023, 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 <android-base/unique_fd.h>
+#include "dump_type.h"
+
+bool connect_tombstone_server(pid_t pid, android::base::unique_fd* tombstoned_socket,
+                              android::base::unique_fd* text_output_fd,
+                              android::base::unique_fd* proto_output_fd,
+                              DebuggerdDumpType dump_type);
+
+bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto);
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index a9a822c..0979fd2 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1202,10 +1202,9 @@
 }
 
 static void copy_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
-    if (buf->sz < AVB_FOOTER_SIZE) {
+    if (buf->sz < AVB_FOOTER_SIZE || is_logical(partition)) {
         return;
     }
-
     // If overflows and negative, it should be < buf->sz.
     int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
 
@@ -1259,11 +1258,7 @@
 
 static void flash_buf(const std::string& partition, struct fastboot_buffer* buf,
                       const bool apply_vbmeta) {
-    if (partition == "boot" || partition == "boot_a" || partition == "boot_b" ||
-        partition == "init_boot" || partition == "init_boot_a" || partition == "init_boot_b" ||
-        partition == "recovery" || partition == "recovery_a" || partition == "recovery_b") {
-        copy_avb_footer(partition, buf);
-    }
+    copy_avb_footer(partition, buf);
 
     // Rewrite vbmeta if that's what we're flashing and modification has been requested.
     if (g_disable_verity || g_disable_verification) {
@@ -1593,11 +1588,6 @@
     void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
     void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
 
-    // If the image uses the default slot, or the user specified "all", then
-    // the paired string will be empty. If the image requests a specific slot
-    // (for example, system_other) it is specified instead.
-    using ImageEntry = std::pair<const Image*, std::string>;
-
     std::vector<ImageEntry> boot_images_;
     std::vector<ImageEntry> os_images_;
     FlashingPlan* fp_;
@@ -1626,17 +1616,15 @@
     // or in bootloader fastboot.
     FlashImages(boot_images_);
 
-    auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_);
+    std::vector<std::unique_ptr<Task>> tasks;
 
-    if (flash_super_task) {
-        flash_super_task->Run();
+    if (auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_)) {
+        tasks.emplace_back(std::move(flash_super_task));
     } else {
         // Sync the super partition. This will reboot to userspace fastboot if needed.
-        std::unique_ptr<UpdateSuperTask> update_super_task = std::make_unique<UpdateSuperTask>(fp_);
-        update_super_task->Run();
+        tasks.emplace_back(std::make_unique<UpdateSuperTask>(fp_));
         // Resize any logical partition to 0, so each partition is reset to 0
         // extents, and will achieve more optimal allocation.
-        std::vector<std::unique_ptr<ResizeTask>> resize_tasks;
         for (const auto& [image, slot] : os_images_) {
             // Retrofit devices have two super partitions, named super_a and super_b.
             // On these devices, secondary slots must be flashed as physical
@@ -1646,17 +1634,14 @@
                 std::string partition_name = image->part_name + "_"s + slot;
                 if (image->IsSecondary() && is_logical(partition_name)) {
                     fp_->fb->DeletePartition(partition_name);
-                    std::unique_ptr<DeleteTask> delete_task =
-                            std::make_unique<DeleteTask>(fp_, partition_name);
-                    delete_task->Run();
                 }
+                tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
             }
-            resize_tasks.emplace_back(
-                    std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
+            tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
         }
-        for (auto& i : resize_tasks) {
-            i->Run();
-        }
+    }
+    for (auto& task : tasks) {
+        task->Run();
     }
     FlashImages(os_images_);
 }
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 44008e5..29befb7 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -20,11 +20,6 @@
 #include "super_flash_helper.h"
 
 using namespace std::string_literals;
-
-FlashTask::FlashTask(const std::string& slot, const std::string& pname, const bool apply_vbmeta)
-    : pname_(pname), fname_(find_item(pname)), slot_(slot), apply_vbmeta_(apply_vbmeta) {
-    if (fname_.empty()) die("cannot determine image filename for '%s'", pname_.c_str());
-}
 FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname,
                      const bool apply_vbmeta)
     : pname_(_pname), fname_(_fname), slot_(_slot), apply_vbmeta_(apply_vbmeta) {}
diff --git a/fastboot/task.h b/fastboot/task.h
index 264e43f..8aa4d2f 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -32,7 +32,6 @@
 
 class FlashTask : public Task {
   public:
-    FlashTask(const std::string& slot, const std::string& pname, const bool apply_vbmeta);
     FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
               const bool apply_vbmeta);
 
diff --git a/libsparse/sparse.cpp b/libsparse/sparse.cpp
index 396e7eb..ca7e5fe 100644
--- a/libsparse/sparse.cpp
+++ b/libsparse/sparse.cpp
@@ -260,8 +260,8 @@
   return s->block_size;
 }
 
-static struct backed_block* move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to,
-                                                  unsigned int len) {
+static int move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to, unsigned int len,
+                                 backed_block** out_bb) {
   int64_t count = 0;
   struct output_file* out_counter;
   struct backed_block* last_bb = nullptr;
@@ -282,7 +282,7 @@
   out_counter = output_file_open_callback(out_counter_write, &count, to->block_size, to->len, false,
                                           true, 0, false);
   if (!out_counter) {
-    return nullptr;
+    return -1;
   }
 
   for (bb = start; bb; bb = backed_block_iter_next(bb)) {
@@ -319,7 +319,8 @@
 out:
   output_file_close(out_counter);
 
-  return bb;
+  *out_bb = bb;
+  return 0;
 }
 
 int sparse_file_resparse(struct sparse_file* in_s, unsigned int max_len, struct sparse_file** out_s,
@@ -337,7 +338,15 @@
   do {
     s = sparse_file_new(in_s->block_size, in_s->len);
 
-    bb = move_chunks_up_to_len(in_s, s, max_len);
+    if (move_chunks_up_to_len(in_s, s, max_len, &bb) < 0) {
+      sparse_file_destroy(s);
+      for (int i = 0; i < c && i < out_s_count; i++) {
+        sparse_file_destroy(out_s[i]);
+        out_s[i] = nullptr;
+      }
+      sparse_file_destroy(tmp);
+      return -1;
+    }
 
     if (c < out_s_count) {
       out_s[c] = s;
diff --git a/libstats/expresslog/Counter.cpp b/libstats/expresslog/Counter.cpp
index bee1303..9382041 100644
--- a/libstats/expresslog/Counter.cpp
+++ b/libstats/expresslog/Counter.cpp
@@ -28,5 +28,10 @@
     stats_write(EXPRESS_EVENT_REPORTED, metricIdHash, amount);
 }
 
+void Counter::logIncrementWithUid(const char* metricName, int32_t uid, int64_t amount) {
+    const int64_t metricIdHash = farmhash::Fingerprint64(metricName, strlen(metricName));
+    stats_write(EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
+}
+
 }  // namespace expresslog
 }  // namespace android
diff --git a/libstats/expresslog/include/Counter.h b/libstats/expresslog/include/Counter.h
index 57328f5..8d0ab6a 100644
--- a/libstats/expresslog/include/Counter.h
+++ b/libstats/expresslog/include/Counter.h
@@ -24,6 +24,8 @@
 class Counter final {
 public:
     static void logIncrement(const char* metricId, int64_t amount = 1);
+
+    static void logIncrementWithUid(const char* metricId, int32_t uid, int64_t amount = 1);
 };
 
 }  // namespace expresslog
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