Support debug dump

Test: atest FakeVehicleHardwareTest
Bug: 199314530
Change-Id: Ied0d1cd7c21e62a7db085f3b5be465cb0d576e73
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
index cab184b..e701078 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -23,7 +23,9 @@
 #include <IVehicleHardware.h>
 #include <VehicleHalTypes.h>
 #include <VehiclePropertyStore.h>
+#include <android-base/parseint.h>
 #include <android-base/result.h>
+#include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 
 #include <map>
@@ -120,6 +122,28 @@
     ::android::base::Result<VehiclePropValuePool::RecyclableType> getUserHalProp(
             const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;
     bool isHvacPropAndHvacNotAvailable(int32_t propId);
+
+    std::string dumpAllProperties();
+    std::string dumpOnePropertyByConfig(
+            int rowNumber,
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& config);
+    std::string dumpOnePropertyById(int32_t propId, int32_t areaId);
+    std::string dumpHelp();
+    std::string dumpListProperties();
+    std::string dumpSpecificProperty(const std::vector<std::string>& options);
+
+    template <typename T>
+    ::android::base::Result<T> safelyParseInt(int index, const std::string& s) {
+        T out;
+        if (!::android::base::ParseInt(s, &out)) {
+            return ::android::base::Error() << ::android::base::StringPrintf(
+                           "non-integer argument at index %d: %s\n", index, s.c_str());
+        }
+        return out;
+    }
+
+    ::android::base::Result<void> checkArgumentsSize(const std::vector<std::string>& options,
+                                                     size_t minSize);
 };
 
 }  // namespace fake
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
index 9c97a2c..1f8e436 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -26,6 +26,7 @@
 #include <VehicleHalTypes.h>
 #include <VehicleUtils.h>
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
 
@@ -59,8 +60,10 @@
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 
+using ::android::base::EqualsIgnoreCase;
 using ::android::base::Error;
 using ::android::base::Result;
+using ::android::base::StringPrintf;
 
 const char* VENDOR_OVERRIDE_DIR = "/vendor/etc/automotive/vhaloverride/";
 const char* OVERRIDE_PROPERTY = "persist.vendor.vhal_init_value_override";
@@ -484,14 +487,148 @@
     return StatusCode::OK;
 }
 
-DumpResult FakeVehicleHardware::dump(const std::vector<std::string>&) {
+DumpResult FakeVehicleHardware::dump(const std::vector<std::string>& options) {
     DumpResult result;
-    // TODO(b/201830716): Implement this.
+    result.callerShouldDumpState = false;
+    if (options.size() == 0) {
+        // We only want caller to dump default state when there is no options.
+        result.callerShouldDumpState = true;
+        result.buffer = dumpAllProperties();
+        return result;
+    }
+    std::string option = options[0];
+    if (EqualsIgnoreCase(option, "--help")) {
+        result.buffer = dumpHelp();
+        return result;
+    } else if (EqualsIgnoreCase(option, "--list")) {
+        result.buffer = dumpListProperties();
+    } else if (EqualsIgnoreCase(option, "--get")) {
+        result.buffer = dumpSpecificProperty(options);
+    } else if (EqualsIgnoreCase(option, "--set")) {
+        // TODO(b/214613918): Support debug set values.
+    } else {
+        result.buffer = StringPrintf("Invalid option: %s\n", option.c_str());
+    }
     return result;
 }
 
+std::string FakeVehicleHardware::dumpHelp() {
+    return "Usage: \n\n"
+           "[no args]: dumps (id and value) all supported properties \n"
+           "--help: shows this help\n"
+           "--list: lists the ids of all supported properties\n"
+           "--get <PROP1> [PROP2] [PROPN]: dumps the value of specific properties \n"
+           "--set <PROP> [-i INT_VALUE [INT_VALUE ...]] [-i64 INT64_VALUE [INT64_VALUE ...]] "
+           "[-f FLOAT_VALUE [FLOAT_VALUE ...]] [-s STR_VALUE] "
+           "[-b BYTES_VALUE] [-a AREA_ID] : sets the value of property PROP. "
+           "Notice that the string, bytes and area value can be set just once, while the other can"
+           " have multiple values (so they're used in the respective array), "
+           "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n";
+}
+
+std::string FakeVehicleHardware::dumpAllProperties() {
+    auto configs = mServerSidePropStore->getAllConfigs();
+    if (configs.size() == 0) {
+        return "no properties to dump\n";
+    }
+    std::string msg = StringPrintf("dumping %zu properties\n", configs.size());
+    int rowNumber = 1;
+    for (const VehiclePropConfig& config : configs) {
+        msg += dumpOnePropertyByConfig(rowNumber++, config);
+    }
+    return msg;
+}
+
+std::string FakeVehicleHardware::dumpOnePropertyByConfig(int rowNumber,
+                                                         const VehiclePropConfig& config) {
+    size_t numberAreas = config.areaConfigs.size();
+    std::string msg = "";
+    if (numberAreas == 0) {
+        msg += StringPrintf("%d: ", rowNumber);
+        msg += dumpOnePropertyById(config.prop, /* areaId= */ 0);
+        return msg;
+    }
+    for (size_t j = 0; j < numberAreas; ++j) {
+        if (numberAreas > 1) {
+            msg += StringPrintf("%d-%zu: ", rowNumber, j);
+        } else {
+            msg += StringPrintf("%d: ", rowNumber);
+        }
+        msg += dumpOnePropertyById(config.prop, config.areaConfigs[j].areaId);
+    }
+    return msg;
+}
+
+std::string FakeVehicleHardware::dumpOnePropertyById(int32_t propId, int32_t areaId) {
+    VehiclePropValue value = {
+            .prop = propId,
+            .areaId = areaId,
+    };
+    bool isSpecialValue = false;
+    auto result = maybeGetSpecialValue(value, &isSpecialValue);
+    if (!isSpecialValue) {
+        result = mServerSidePropStore->readValue(value);
+    }
+    if (!result.ok()) {
+        return StringPrintf("failed to read property value: %d, error: %s, code: %d\n", propId,
+                            getErrorMsg(result).c_str(), getIntErrorCode(result));
+
+    } else {
+        return result.value()->toString() + "\n";
+    }
+}
+
+std::string FakeVehicleHardware::dumpListProperties() {
+    auto configs = mServerSidePropStore->getAllConfigs();
+    if (configs.size() == 0) {
+        return "no properties to list\n";
+    }
+    int rowNumber = 1;
+    std::string msg = StringPrintf("listing %zu properties\n", configs.size());
+    for (const auto& config : configs) {
+        msg += StringPrintf("%d: %d\n", rowNumber++, config.prop);
+    }
+    return msg;
+}
+
+Result<void> FakeVehicleHardware::checkArgumentsSize(const std::vector<std::string>& options,
+                                                     size_t minSize) {
+    size_t size = options.size();
+    if (size >= minSize) {
+        return {};
+    }
+    return Error() << StringPrintf("Invalid number of arguments: required at least %zu, got %zu\n",
+                                   minSize, size);
+}
+
+std::string FakeVehicleHardware::dumpSpecificProperty(const std::vector<std::string>& options) {
+    if (auto result = checkArgumentsSize(options, /*minSize=*/2); !result.ok()) {
+        return getErrorMsg(result);
+    }
+
+    // options[0] is the command itself...
+    int rowNumber = 1;
+    size_t size = options.size();
+    std::string msg = "";
+    for (size_t i = 1; i < size; ++i) {
+        auto propResult = safelyParseInt<int32_t>(i, options[i]);
+        if (!propResult.ok()) {
+            msg += getErrorMsg(propResult);
+            continue;
+        }
+        int32_t prop = propResult.value();
+        auto result = mServerSidePropStore->getConfig(prop);
+        if (!result.ok()) {
+            msg += StringPrintf("No property %d\n", prop);
+            continue;
+        }
+        msg += dumpOnePropertyByConfig(rowNumber++, *result.value());
+    }
+    return msg;
+}
+
 StatusCode FakeVehicleHardware::checkHealth() {
-    // TODO(b/201830716): Implement this.
+    // Always return OK for checkHealth.
     return StatusCode::OK;
 }
 
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
index 970d044..f821dfe 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -24,6 +24,7 @@
 
 #include <android-base/expected.h>
 #include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <utils/Log.h>
@@ -52,8 +53,10 @@
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 using ::android::base::expected;
+using ::android::base::StringPrintf;
 using ::android::base::unexpected;
 using ::testing::ContainerEq;
+using ::testing::ContainsRegex;
 using ::testing::Eq;
 using ::testing::IsSubsetOf;
 using ::testing::WhenSortedBy;
@@ -1203,6 +1206,82 @@
                          }));
 }
 
+TEST_F(FakeVehicleHardwareTest, testDumpAllProperties) {
+    std::vector<std::string> options;
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_TRUE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex("dumping .+ properties"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpHelp) {
+    std::vector<std::string> options;
+    options.push_back("--help");
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex("Usage: "));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpListProperties) {
+    std::vector<std::string> options;
+    options.push_back("--list");
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex("listing .+ properties"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpSpecificProperties) {
+    std::vector<std::string> options;
+    options.push_back("--get");
+    std::string prop1 = std::to_string(toInt(VehicleProperty::INFO_FUEL_CAPACITY));
+    std::string prop2 = std::to_string(toInt(VehicleProperty::TIRE_PRESSURE));
+    options.push_back(prop1);
+    options.push_back(prop2);
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer,
+                ContainsRegex(StringPrintf("1:.*prop: %s.*\n2-0:.*prop: %s.*\n2-1:.*prop: %s.*\n",
+                                           prop1.c_str(), prop2.c_str(), prop2.c_str())));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpSpecificPropertiesInvalidProp) {
+    std::vector<std::string> options;
+    options.push_back("--get");
+    std::string prop1 = std::to_string(toInt(VehicleProperty::INFO_FUEL_CAPACITY));
+    std::string prop2 = std::to_string(INVALID_PROP_ID);
+    options.push_back(prop1);
+    options.push_back(prop2);
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex(StringPrintf("1:.*prop: %s.*\nNo property %d\n",
+                                                          prop1.c_str(), INVALID_PROP_ID)));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpSpecificPropertiesNoArg) {
+    std::vector<std::string> options;
+    options.push_back("--get");
+
+    // No arguments.
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex("Invalid number of arguments"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpInvalidOptions) {
+    std::vector<std::string> options;
+    options.push_back("--invalid");
+
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex("Invalid option: --invalid"));
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive
diff --git a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
index e3267dd..5e7adfc 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
@@ -72,6 +72,7 @@
                                      const std::vector<int32_t>& propIds) override;
     ::ndk::ScopedAStatus returnSharedMemory(const CallbackType& callback,
                                             int64_t sharedMemoryId) override;
+    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
 
     IVehicleHardware* getHardware();
 
@@ -208,6 +209,8 @@
 
     void monitorBinderLifeCycle(const CallbackType& callback);
 
+    bool checkDumpPermission();
+
     template <class T>
     static std::shared_ptr<T> getOrCreateClient(
             std::unordered_map<const AIBinder*, std::shared_ptr<T>>* clients,
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index dd88979..c0a66da 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -24,6 +24,8 @@
 
 #include <android-base/result.h>
 #include <android-base/stringprintf.h>
+#include <android/binder_ibinder.h>
+#include <private/android_filesystem_config.h>
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
 
@@ -718,6 +720,39 @@
     mLinkToDeathImpl = std::move(impl);
 }
 
+bool DefaultVehicleHal::checkDumpPermission() {
+    uid_t uid = AIBinder_getCallingUid();
+    return uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM;
+}
+
+binder_status_t DefaultVehicleHal::dump(int fd, const char** args, uint32_t numArgs) {
+    if (!checkDumpPermission()) {
+        dprintf(fd, "Caller must be root, system or shell");
+        return STATUS_PERMISSION_DENIED;
+    }
+
+    std::vector<std::string> options;
+    for (uint32_t i = 0; i < numArgs; i++) {
+        options.push_back(args[i]);
+    }
+    DumpResult result = mVehicleHardware->dump(options);
+    dprintf(fd, "%s", (result.buffer + "\n").c_str());
+    if (!result.callerShouldDumpState) {
+        dprintf(fd, "Skip dumping Vehicle HAL State.\n");
+        return STATUS_OK;
+    }
+    dprintf(fd, "Vehicle HAL State: \n");
+    {
+        std::scoped_lock<std::mutex> lockGuard(mLock);
+        dprintf(fd, "Containing %zu property configs\n", mConfigsByPropId.size());
+        dprintf(fd, "Currently have %zu getValues clients\n", mGetValuesClients.size());
+        dprintf(fd, "Currently have %zu setValues clients\n", mSetValuesClients.size());
+        dprintf(fd, "Currently have %zu subscription clients\n",
+                mSubscriptionClients->countClients());
+    }
+    return STATUS_OK;
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
index 566f0bd..7443d5b 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
@@ -27,6 +27,7 @@
 #include <android-base/thread_annotations.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <sys/mman.h>
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
 
@@ -75,6 +76,7 @@
 using ::ndk::ScopedFileDescriptor;
 using ::ndk::SpAIBinder;
 
+using ::testing::ContainsRegex;
 using ::testing::Eq;
 using ::testing::UnorderedElementsAre;
 using ::testing::UnorderedElementsAreArray;
@@ -1545,6 +1547,45 @@
             << "expect OnBinderDied context to be deleted when binder is unlinked";
 }
 
+TEST_F(DefaultVehicleHalTest, testDumpCallerShouldDump) {
+    std::string buffer = "Dump from hardware";
+    getHardware()->setDumpResult({
+            .callerShouldDumpState = true,
+            .buffer = buffer,
+    });
+    int fd = memfd_create("memfile", 0);
+    getClient()->dump(fd, nullptr, 0);
+
+    lseek(fd, 0, SEEK_SET);
+    char buf[10240] = {};
+    read(fd, buf, sizeof(buf));
+    close(fd);
+
+    std::string msg(buf);
+
+    ASSERT_THAT(msg, ContainsRegex(buffer + "\nVehicle HAL State: \n"));
+}
+
+TEST_F(DefaultVehicleHalTest, testDumpCallerShouldNotDump) {
+    std::string buffer = "Dump from hardware";
+    getHardware()->setDumpResult({
+            .callerShouldDumpState = false,
+            .buffer = buffer,
+    });
+    int fd = memfd_create("memfile", 0);
+    getClient()->dump(fd, nullptr, 0);
+
+    lseek(fd, 0, SEEK_SET);
+    char buf[10240] = {};
+    read(fd, buf, sizeof(buf));
+    close(fd);
+
+    std::string msg(buf);
+
+    ASSERT_THAT(msg, ContainsRegex(buffer));
+    ASSERT_EQ(msg.find("Vehicle HAL State: "), std::string::npos);
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
index eec32dd..66aef7c 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
@@ -71,13 +71,15 @@
                                 &mGetValueResponses);
 }
 
+void MockVehicleHardware::setDumpResult(DumpResult result) {
+    mDumpResult = result;
+}
+
 DumpResult MockVehicleHardware::dump(const std::vector<std::string>&) {
-    // TODO(b/200737967): mock this.
-    return DumpResult{};
+    return mDumpResult;
 }
 
 StatusCode MockVehicleHardware::checkHealth() {
-    // TODO(b/200737967): mock this.
     return StatusCode::OK;
 }
 
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
index 0844de1..74d4fae 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
@@ -79,6 +79,7 @@
     void setStatus(const char* functionName,
                    ::aidl::android::hardware::automotive::vehicle::StatusCode status);
     void setSleepTime(int64_t timeInNano);
+    void setDumpResult(DumpResult result);
 
   private:
     mutable std::mutex mLock;
@@ -114,6 +115,8 @@
             const std::vector<RequestType>& requests,
             std::list<std::vector<RequestType>>* storedRequests,
             std::list<std::vector<ResultType>>* storedResponses) const REQUIRES(mLock);
+
+    DumpResult mDumpResult;
 };
 
 }  // namespace vehicle