Support set property in dump.

Support debug interface to set property value in FakeVehicleHardware.

Test: atest FakeVehicleHardwareTest.
Bug: 214613918
Change-Id: I64ef532274e20db1444e8583d71e0c0955c5b460
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 e701078..3c38025 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -131,6 +131,7 @@
     std::string dumpHelp();
     std::string dumpListProperties();
     std::string dumpSpecificProperty(const std::vector<std::string>& options);
+    std::string dumpSetProperties(const std::vector<std::string>& options);
 
     template <typename T>
     ::android::base::Result<T> safelyParseInt(int index, const std::string& s) {
@@ -141,6 +142,12 @@
         }
         return out;
     }
+    ::android::base::Result<float> safelyParseFloat(int index, const std::string& s);
+    std::vector<std::string> getOptionValues(const std::vector<std::string>& options,
+                                             size_t* index);
+    ::android::base::Result<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>
+    parseSetPropOptions(const std::vector<std::string>& options);
+    ::android::base::Result<std::vector<uint8_t>> parseHexString(const std::string& s);
 
     ::android::base::Result<void> checkArgumentsSize(const std::vector<std::string>& options,
                                                      size_t minSize);
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 1f8e436..9660793 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -25,6 +25,7 @@
 #include <PropertyUtils.h>
 #include <VehicleHalTypes.h>
 #include <VehicleUtils.h>
+#include <android-base/parsedouble.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <utils/Log.h>
@@ -62,12 +63,29 @@
 
 using ::android::base::EqualsIgnoreCase;
 using ::android::base::Error;
+using ::android::base::ParseFloat;
 using ::android::base::Result;
+using ::android::base::StartsWith;
 using ::android::base::StringPrintf;
 
 const char* VENDOR_OVERRIDE_DIR = "/vendor/etc/automotive/vhaloverride/";
 const char* OVERRIDE_PROPERTY = "persist.vendor.vhal_init_value_override";
 
+// A list of supported options for "--set" command.
+const std::unordered_set<std::string> SET_PROP_OPTIONS = {
+        // integer.
+        "-i",
+        // 64bit integer.
+        "-i64",
+        // float.
+        "-f",
+        // string.
+        "-s",
+        // bytes in hex format, e.g. 0xDEADBEEF.
+        "-b",
+        // Area id in integer.
+        "-a"};
+
 }  // namespace
 
 void FakeVehicleHardware::storePropInitialValue(const defaultconfig::ConfigDeclaration& config) {
@@ -505,7 +523,7 @@
     } else if (EqualsIgnoreCase(option, "--get")) {
         result.buffer = dumpSpecificProperty(options);
     } else if (EqualsIgnoreCase(option, "--set")) {
-        // TODO(b/214613918): Support debug set values.
+        result.buffer = dumpSetProperties(options);
     } else {
         result.buffer = StringPrintf("Invalid option: %s\n", option.c_str());
     }
@@ -627,6 +645,148 @@
     return msg;
 }
 
+std::vector<std::string> FakeVehicleHardware::getOptionValues(
+        const std::vector<std::string>& options, size_t* index) {
+    std::vector<std::string> values;
+    while (*index < options.size()) {
+        std::string option = options[*index];
+        if (SET_PROP_OPTIONS.find(option) != SET_PROP_OPTIONS.end()) {
+            return std::move(values);
+        }
+        values.push_back(option);
+        (*index)++;
+    }
+    return std::move(values);
+}
+
+Result<VehiclePropValue> FakeVehicleHardware::parseSetPropOptions(
+        const std::vector<std::string>& options) {
+    // Options format:
+    // --set PROP [-f f1 f2...] [-i i1 i2...] [-i64 i1 i2...] [-s s1 s2...] [-b b1 b2...] [-a a]
+    size_t optionIndex = 1;
+    auto result = safelyParseInt<int32_t>(optionIndex, options[optionIndex]);
+    if (!result.ok()) {
+        return Error() << StringPrintf("Property value: \"%s\" is not a valid int: %s\n",
+                                       options[optionIndex].c_str(), getErrorMsg(result).c_str());
+    }
+    VehiclePropValue prop = {};
+    prop.prop = result.value();
+    prop.status = VehiclePropertyStatus::AVAILABLE;
+    optionIndex++;
+    std::unordered_set<std::string> parsedOptions;
+
+    while (optionIndex < options.size()) {
+        std::string type = options[optionIndex];
+        optionIndex++;
+        size_t currentIndex = optionIndex;
+        std::vector<std::string> values = getOptionValues(options, &optionIndex);
+        if (parsedOptions.find(type) != parsedOptions.end()) {
+            return Error() << StringPrintf("Duplicate \"%s\" options\n", type.c_str());
+        }
+        parsedOptions.insert(type);
+        if (EqualsIgnoreCase(type, "-i")) {
+            if (values.size() == 0) {
+                return Error() << "No values specified when using \"-i\"\n";
+            }
+            prop.value.int32Values.resize(values.size());
+            for (size_t i = 0; i < values.size(); i++) {
+                auto int32Result = safelyParseInt<int32_t>(currentIndex + i, values[i]);
+                if (!int32Result.ok()) {
+                    return Error()
+                           << StringPrintf("Value: \"%s\" is not a valid int: %s\n",
+                                           values[i].c_str(), getErrorMsg(int32Result).c_str());
+                }
+                prop.value.int32Values[i] = int32Result.value();
+            }
+        } else if (EqualsIgnoreCase(type, "-i64")) {
+            if (values.size() == 0) {
+                return Error() << "No values specified when using \"-i64\"\n";
+            }
+            prop.value.int64Values.resize(values.size());
+            for (size_t i = 0; i < values.size(); i++) {
+                auto int64Result = safelyParseInt<int64_t>(currentIndex + i, values[i]);
+                if (!int64Result.ok()) {
+                    return Error()
+                           << StringPrintf("Value: \"%s\" is not a valid int64: %s\n",
+                                           values[i].c_str(), getErrorMsg(int64Result).c_str());
+                }
+                prop.value.int64Values[i] = int64Result.value();
+            }
+        } else if (EqualsIgnoreCase(type, "-f")) {
+            if (values.size() == 0) {
+                return Error() << "No values specified when using \"-f\"\n";
+            }
+            prop.value.floatValues.resize(values.size());
+            for (size_t i = 0; i < values.size(); i++) {
+                auto floatResult = safelyParseFloat(currentIndex + i, values[i]);
+                if (!floatResult.ok()) {
+                    return Error()
+                           << StringPrintf("Value: \"%s\" is not a valid float: %s\n",
+                                           values[i].c_str(), getErrorMsg(floatResult).c_str());
+                }
+                prop.value.floatValues[i] = floatResult.value();
+            }
+        } else if (EqualsIgnoreCase(type, "-s")) {
+            if (values.size() != 1) {
+                return Error() << "Expect exact one value when using \"-s\"\n";
+            }
+            prop.value.stringValue = values[0];
+        } else if (EqualsIgnoreCase(type, "-b")) {
+            if (values.size() != 1) {
+                return Error() << "Expect exact one value when using \"-b\"\n";
+            }
+            auto bytesResult = parseHexString(values[0]);
+            if (!bytesResult.ok()) {
+                return Error() << StringPrintf("value: \"%s\" is not a valid hex string: %s\n",
+                                               values[0].c_str(), getErrorMsg(bytesResult).c_str());
+            }
+            prop.value.byteValues = std::move(bytesResult.value());
+        } else if (EqualsIgnoreCase(type, "-a")) {
+            if (values.size() != 1) {
+                return Error() << "Expect exact one value when using \"-a\"\n";
+            }
+            auto int32Result = safelyParseInt<int32_t>(currentIndex, values[0]);
+            if (!int32Result.ok()) {
+                return Error() << StringPrintf("Area ID: \"%s\" is not a valid int: %s\n",
+                                               values[0].c_str(), getErrorMsg(int32Result).c_str());
+            }
+            prop.areaId = int32Result.value();
+        } else {
+            return Error() << StringPrintf("Unknown option: %s\n", type.c_str());
+        }
+    }
+
+    return prop;
+}
+
+std::string FakeVehicleHardware::dumpSetProperties(const std::vector<std::string>& options) {
+    if (auto result = checkArgumentsSize(options, 3); !result.ok()) {
+        return getErrorMsg(result);
+    }
+
+    auto parseResult = parseSetPropOptions(options);
+    if (!parseResult.ok()) {
+        return getErrorMsg(parseResult);
+    }
+    VehiclePropValue prop = std::move(parseResult.value());
+    ALOGD("Dump: Setting property: %s", prop.toString().c_str());
+
+    bool isSpecialValue = false;
+    auto setResult = maybeSetSpecialValue(prop, &isSpecialValue);
+
+    if (!isSpecialValue) {
+        auto updatedValue = mValuePool->obtain(prop);
+        updatedValue->timestamp = elapsedRealtimeNano();
+        setResult = mServerSidePropStore->writeValue(std::move(updatedValue));
+    }
+
+    if (setResult.ok()) {
+        return StringPrintf("Set property: %s\n", prop.toString().c_str());
+    }
+    return StringPrintf("failed to set property: %s, error: %s\n", prop.toString().c_str(),
+                        getErrorMsg(setResult).c_str());
+}
+
 StatusCode FakeVehicleHardware::checkHealth() {
     // Always return OK for checkHealth.
     return StatusCode::OK;
@@ -689,6 +849,49 @@
     }
 }
 
+Result<float> FakeVehicleHardware::safelyParseFloat(int index, const std::string& s) {
+    float out;
+    if (!ParseFloat(s, &out)) {
+        return Error() << StringPrintf("non-float argument at index %d: %s\n", index, s.c_str());
+    }
+    return out;
+}
+
+Result<std::vector<uint8_t>> FakeVehicleHardware::parseHexString(const std::string& s) {
+    std::vector<uint8_t> bytes;
+    if (s.size() % 2 != 0) {
+        return Error() << StringPrintf("invalid hex string: %s, should have even size\n",
+                                       s.c_str());
+    }
+    if (!StartsWith(s, "0x")) {
+        return Error() << StringPrintf("hex string should start with \"0x\", got %s\n", s.c_str());
+    }
+    std::string subs = s.substr(2);
+    std::transform(subs.begin(), subs.end(), subs.begin(),
+                   [](unsigned char c) { return std::tolower(c); });
+
+    bool highDigit = true;
+    for (size_t i = 0; i < subs.size(); i++) {
+        char c = subs[i];
+        uint8_t v;
+        if (c >= '0' && c <= '9') {
+            v = c - '0';
+        } else if (c >= 'a' && c <= 'f') {
+            v = c - 'a' + 10;
+        } else {
+            return Error() << StringPrintf("invalid character %c in hex string %s\n", c,
+                                           subs.c_str());
+        }
+        if (highDigit) {
+            bytes.push_back(v * 16);
+        } else {
+            bytes[bytes.size() - 1] += v;
+        }
+        highDigit = !highDigit;
+    }
+    return bytes;
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive
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 f821dfe..0812c2a 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -62,6 +62,7 @@
 using ::testing::WhenSortedBy;
 
 constexpr int INVALID_PROP_ID = 0;
+constexpr char CAR_MAKE[] = "Default Car";
 
 }  // namespace
 
@@ -1282,6 +1283,185 @@
     ASSERT_THAT(result.buffer, ContainsRegex("Invalid option: --invalid"));
 }
 
+struct SetPropTestCase {
+    std::string test_name;
+    std::vector<std::string> options;
+    bool success;
+    std::string errorMsg = "";
+};
+
+class FakeVehicleHardwareSetPropTest : public FakeVehicleHardwareTest,
+                                       public testing::WithParamInterface<SetPropTestCase> {};
+
+TEST_P(FakeVehicleHardwareSetPropTest, cmdSetOneProperty) {
+    const SetPropTestCase& tc = GetParam();
+
+    DumpResult result = getHardware()->dump(tc.options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    if (tc.success) {
+        ASSERT_THAT(result.buffer, ContainsRegex("Set property:"));
+    } else {
+        ASSERT_THAT(result.buffer, ContainsRegex(tc.errorMsg));
+    }
+}
+
+std::vector<SetPropTestCase> GenSetPropParams() {
+    std::string infoMakeProperty = std::to_string(toInt(VehicleProperty::INFO_MAKE));
+    return {
+            {"success_set_string", {"--set", infoMakeProperty, "-s", CAR_MAKE}, true},
+            {"success_set_bytes", {"--set", infoMakeProperty, "-b", "0xdeadbeef"}, true},
+            {"success_set_bytes_caps", {"--set", infoMakeProperty, "-b", "0xDEADBEEF"}, true},
+            {"success_set_int", {"--set", infoMakeProperty, "-i", "2147483647"}, true},
+            {"success_set_ints",
+             {"--set", infoMakeProperty, "-i", "2147483647", "0", "-2147483648"},
+             true},
+            {"success_set_int64",
+             {"--set", infoMakeProperty, "-i64", "-9223372036854775808"},
+             true},
+            {"success_set_int64s",
+             {"--set", infoMakeProperty, "-i64", "-9223372036854775808", "0",
+              "9223372036854775807"},
+             true},
+            {"success_set_float", {"--set", infoMakeProperty, "-f", "1.175494351E-38"}, true},
+            {"success_set_floats",
+             {"--set", infoMakeProperty, "-f", "-3.402823466E+38", "0", "3.402823466E+38"},
+             true},
+            {"success_set_area", {"--set", infoMakeProperty, "-a", "2147483647"}, true},
+            {"fail_no_options", {"--set", infoMakeProperty}, false, "Invalid number of arguments"},
+            {"fail_less_than_4_options",
+             {"--set", infoMakeProperty, "-i"},
+             false,
+             "No values specified"},
+            {"fail_unknown_options", {"--set", infoMakeProperty, "-abcd"}, false, "Unknown option"},
+            {"fail_invalid_property",
+             {"--set", "not valid", "-s", CAR_MAKE},
+             false,
+             "not a valid int"},
+            {"fail_duplicate_string",
+             {"--set", infoMakeProperty, "-s", CAR_MAKE, "-s", CAR_MAKE},
+             false,
+             "Duplicate \"-s\" options"},
+            {"fail_multiple_strings",
+             {"--set", infoMakeProperty, "-s", CAR_MAKE, CAR_MAKE},
+             false,
+             "Expect exact one value"},
+            {"fail_no_string_value",
+             {"--set", infoMakeProperty, "-s", "-a", "1234"},
+             false,
+             "Expect exact one value"},
+            {"fail_duplicate_bytes",
+             {"--set", infoMakeProperty, "-b", "0xdeadbeef", "-b", "0xdeadbeef"},
+             false,
+             "Duplicate \"-b\" options"},
+            {"fail_multiple_bytes",
+             {"--set", infoMakeProperty, "-b", "0xdeadbeef", "0xdeadbeef"},
+             false,
+             "Expect exact one value"},
+            {"fail_invalid_bytes",
+             {"--set", infoMakeProperty, "-b", "0xgood"},
+             false,
+             "not a valid hex string"},
+            {"fail_invalid_bytes_no_prefix",
+             {"--set", infoMakeProperty, "-b", "deadbeef"},
+             false,
+             "not a valid hex string"},
+            {"fail_invalid_int",
+             {"--set", infoMakeProperty, "-i", "abc"},
+             false,
+             "not a valid int"},
+            {"fail_int_out_of_range",
+             {"--set", infoMakeProperty, "-i", "2147483648"},
+             false,
+             "not a valid int"},
+            {"fail_no_int_value",
+             {"--set", infoMakeProperty, "-i", "-s", CAR_MAKE},
+             false,
+             "No values specified"},
+            {"fail_invalid_int64",
+             {"--set", infoMakeProperty, "-i64", "abc"},
+             false,
+             "not a valid int64"},
+            {"fail_int64_out_of_range",
+             {"--set", infoMakeProperty, "-i64", "-9223372036854775809"},
+             false,
+             "not a valid int64"},
+            {"fail_no_int64_value",
+             {"--set", infoMakeProperty, "-i64", "-s", CAR_MAKE},
+             false,
+             "No values specified"},
+            {"fail_invalid_float",
+             {"--set", infoMakeProperty, "-f", "abc"},
+             false,
+             "not a valid float"},
+            {"fail_float_out_of_range",
+             {"--set", infoMakeProperty, "-f", "-3.402823466E+39"},
+             false,
+             "not a valid float"},
+            {"fail_no_float_value",
+             {"--set", infoMakeProperty, "-f", "-s", CAR_MAKE},
+             false,
+             "No values specified"},
+            {"fail_multiple_areas",
+             {"--set", infoMakeProperty, "-a", "2147483648", "0"},
+             false,
+             "Expect exact one value"},
+            {"fail_invalid_area",
+             {"--set", infoMakeProperty, "-a", "abc"},
+             false,
+             "not a valid int"},
+            {"fail_area_out_of_range",
+             {"--set", infoMakeProperty, "-a", "2147483648"},
+             false,
+             "not a valid int"},
+            {"fail_no_area_value",
+             {"--set", infoMakeProperty, "-a", "-s", CAR_MAKE},
+             false,
+             "Expect exact one value"},
+    };
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        FakeVehicleHardwareSetPropTests, FakeVehicleHardwareSetPropTest,
+        testing::ValuesIn(GenSetPropParams()),
+        [](const testing::TestParamInfo<FakeVehicleHardwareSetPropTest::ParamType>& info) {
+            return info.param.test_name;
+        });
+
+TEST_F(FakeVehicleHardwareTest, SetComplexPropTest) {
+    std::string infoMakeProperty = std::to_string(toInt(VehicleProperty::INFO_MAKE));
+    getHardware()->dump({"--set", infoMakeProperty,      "-s",   CAR_MAKE,
+                         "-b",    "0xdeadbeef",          "-i",   "2147483647",
+                         "0",     "-2147483648",         "-i64", "-9223372036854775808",
+                         "0",     "9223372036854775807", "-f",   "-3.402823466E+38",
+                         "0",     "3.402823466E+38",     "-a",   "123"});
+    VehiclePropValue requestProp;
+    requestProp.prop = toInt(VehicleProperty::INFO_MAKE);
+    requestProp.areaId = 123;
+    auto result = getValue(requestProp);
+    ASSERT_TRUE(result.ok());
+    VehiclePropValue value = result.value();
+    ASSERT_EQ(value.prop, toInt(VehicleProperty::INFO_MAKE));
+    ASSERT_EQ(value.areaId, 123);
+    ASSERT_STREQ(CAR_MAKE, value.value.stringValue.c_str());
+    uint8_t bytes[] = {0xde, 0xad, 0xbe, 0xef};
+    ASSERT_FALSE(memcmp(bytes, value.value.byteValues.data(), sizeof(bytes)));
+    ASSERT_EQ(3u, value.value.int32Values.size());
+    ASSERT_EQ(2147483647, value.value.int32Values[0]);
+    ASSERT_EQ(0, value.value.int32Values[1]);
+    ASSERT_EQ(-2147483648, value.value.int32Values[2]);
+    ASSERT_EQ(3u, value.value.int64Values.size());
+    // -9223372036854775808 is not a valid literal since '-' and '9223372036854775808' would be two
+    // tokens and the later does not fit in unsigned long long.
+    ASSERT_EQ(-9223372036854775807 - 1, value.value.int64Values[0]);
+    ASSERT_EQ(0, value.value.int64Values[1]);
+    ASSERT_EQ(9223372036854775807, value.value.int64Values[2]);
+    ASSERT_EQ(3u, value.value.floatValues.size());
+    ASSERT_EQ(-3.402823466E+38f, value.value.floatValues[0]);
+    ASSERT_EQ(0.0f, value.value.floatValues[1]);
+    ASSERT_EQ(3.402823466E+38f, value.value.floatValues[2]);
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive