Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e48c26f4 authored by Yu Shan's avatar Yu Shan
Browse files

Add input checks for Default VHAL.

Test: atest android.hardware.automotive.vehicle@2.0-default-impl-unit-tests
Bug: 188204722

Change-Id: I5f7084796a62c7e53a00f488fafb1026a182ce0c
parent d6022741
Loading
Loading
Loading
Loading
+193 −3
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
#define LOG_TAG "DefaultVehicleHal_v2_0"

#include <assert.h>
#include <utils/Log.h>
#include <utils/SystemClock.h>
#include <vhal_v2_0/RecurrentTimer.h>
@@ -72,6 +73,173 @@ bool DefaultVehicleHal::dump(const hidl_handle& fd, const hidl_vec<hidl_string>&
    return mVehicleClient->dump(fd, options);
}

StatusCode DefaultVehicleHal::checkPropValue(const VehiclePropValue& value,
                                             const VehiclePropConfig* config) {
    int32_t property = value.prop;
    VehiclePropertyType type = getPropType(property);
    switch (type) {
        case VehiclePropertyType::BOOLEAN:
        case VehiclePropertyType::INT32:
            if (value.value.int32Values.size() != 1) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::INT32_VEC:
            if (value.value.int32Values.size() < 1) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::INT64:
            if (value.value.int64Values.size() != 1) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::INT64_VEC:
            if (value.value.int64Values.size() < 1) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::FLOAT:
            if (value.value.floatValues.size() != 1) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::FLOAT_VEC:
            if (value.value.floatValues.size() < 1) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::BYTES:
            // We allow setting an empty bytes array.
            break;
        case VehiclePropertyType::STRING:
            // We allow setting an empty string.
            break;
        case VehiclePropertyType::MIXED:
            if (getPropGroup(property) == VehiclePropertyGroup::VENDOR) {
                // We only checks vendor mixed properties.
                return checkVendorMixedPropValue(value, config);
            }
            break;
        default:
            ALOGW("Unknown property type: %d", type);
            return StatusCode::INVALID_ARG;
    }
    return StatusCode::OK;
}

StatusCode DefaultVehicleHal::checkVendorMixedPropValue(const VehiclePropValue& value,
                                                        const VehiclePropConfig* config) {
    auto configArray = config->configArray;
    // configArray[0], 1 indicates the property has a String value, we allow the string value to
    // be empty.

    size_t int32Count = 0;
    // configArray[1], 1 indicates the property has a Boolean value.
    if (configArray[1] == 1) {
        int32Count++;
    }
    // configArray[2], 1 indicates the property has an Integer value.
    if (configArray[2] == 1) {
        int32Count++;
    }
    // configArray[3], the number indicates the size of Integer[] in the property.
    int32Count += static_cast<size_t>(configArray[3]);
    if (value.value.int32Values.size() != int32Count) {
        return StatusCode::INVALID_ARG;
    }

    size_t int64Count = 0;
    // configArray[4], 1 indicates the property has a Long value.
    if (configArray[4] == 1) {
        int64Count++;
    }
    // configArray[5], the number indicates the size of Long[] in the property.
    int64Count += static_cast<size_t>(configArray[5]);
    if (value.value.int64Values.size() != int64Count) {
        return StatusCode::INVALID_ARG;
    }

    size_t floatCount = 0;
    // configArray[6], 1 indicates the property has a Float value.
    if (configArray[6] == 1) {
        floatCount++;
    }
    // configArray[7], the number indicates the size of Float[] in the property.
    floatCount += static_cast<size_t>(configArray[7]);
    if (value.value.floatValues.size() != floatCount) {
        return StatusCode::INVALID_ARG;
    }

    // configArray[8], the number indicates the size of byte[] in the property.
    if (configArray[8] != 0 && value.value.bytes.size() != static_cast<size_t>(configArray[8])) {
        return StatusCode::INVALID_ARG;
    }
    return StatusCode::OK;
}

StatusCode DefaultVehicleHal::checkValueRange(const VehiclePropValue& value,
                                              const VehiclePropConfig* config) {
    int32_t property = value.prop;
    VehiclePropertyType type = getPropType(property);
    const VehicleAreaConfig* areaConfig;
    if (isGlobalProp(property)) {
        if (config->areaConfigs.size() == 0) {
            return StatusCode::OK;
        }
        areaConfig = &(config->areaConfigs[0]);
    } else {
        for (auto& c : config->areaConfigs) {
            // areaId might contain multiple areas.
            if (c.areaId & value.areaId) {
                areaConfig = &c;
                break;
            }
        }
    }
    switch (type) {
        case VehiclePropertyType::INT32:
            if (areaConfig->minInt32Value == 0 && areaConfig->maxInt32Value == 0) {
                break;
            }
            // We already checked this in checkPropValue.
            assert(value.value.int32Values.size() > 0);
            if (value.value.int32Values[0] < areaConfig->minInt32Value ||
                value.value.int32Values[0] > areaConfig->maxInt32Value) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::INT64:
            if (areaConfig->minInt64Value == 0 && areaConfig->maxInt64Value == 0) {
                break;
            }
            // We already checked this in checkPropValue.
            assert(value.value.int64Values.size() > 0);
            if (value.value.int64Values[0] < areaConfig->minInt64Value ||
                value.value.int64Values[0] > areaConfig->maxInt64Value) {
                return StatusCode::INVALID_ARG;
            }
            break;
        case VehiclePropertyType::FLOAT:
            if (areaConfig->minFloatValue == 0 && areaConfig->maxFloatValue == 0) {
                break;
            }
            // We already checked this in checkPropValue.
            assert(value.value.floatValues.size() > 0);
            if (value.value.floatValues[0] < areaConfig->minFloatValue ||
                value.value.floatValues[0] > areaConfig->maxFloatValue) {
                return StatusCode::INVALID_ARG;
            }
            break;
        default:
            // We don't check the rest of property types. Additional logic needs to be added if
            // required for real implementation. E.g., you might want to enforce the range
            // checks on vector as well or you might want to check the range for mixed property.
            break;
    }
    return StatusCode::OK;
}

StatusCode DefaultVehicleHal::set(const VehiclePropValue& propValue) {
    if (propValue.status != VehiclePropertyStatus::AVAILABLE) {
        // Android side cannot set property status - this value is the
@@ -79,12 +247,26 @@ StatusCode DefaultVehicleHal::set(const VehiclePropValue& propValue) {
        // its underlying hardware
        return StatusCode::INVALID_ARG;
    }
    auto currentPropValue = mPropStore->readValueOrNull(propValue);

    if (currentPropValue == nullptr) {
    int32_t property = propValue.prop;
    const VehiclePropConfig* config = mPropStore->getConfigOrNull(property);
    if (config == nullptr) {
        ALOGW("no config for prop 0x%x", property);
        return StatusCode::INVALID_ARG;
    }
    if (currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {
    auto status = checkPropValue(propValue, config);
    if (status != StatusCode::OK) {
        ALOGW("invalid property value: %s", toString(propValue).c_str());
        return status;
    }
    status = checkValueRange(propValue, config);
    if (status != StatusCode::OK) {
        ALOGW("property value out of range: %s", toString(propValue).c_str());
        return status;
    }

    auto currentPropValue = mPropStore->readValueOrNull(propValue);
    if (currentPropValue && currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {
        // do not allow Android side to set() a disabled/error property
        return StatusCode::NOT_AVAILABLE;
    }
@@ -151,6 +333,14 @@ StatusCode DefaultVehicleHal::subscribe(int32_t property, float sampleRate) {
    if (!isContinuousProperty(property)) {
        return StatusCode::INVALID_ARG;
    }

    // If the config does not exist, isContinuousProperty should return false.
    const VehiclePropConfig* config = mPropStore->getConfigOrNull(property);
    if (sampleRate < config->minSampleRate || sampleRate > config->maxSampleRate) {
        ALOGW("sampleRate out of range");
        return StatusCode::INVALID_ARG;
    }

    mRecurrentTimer.registerRecurrentEvent(hertzToNanoseconds(sampleRate), property);
    return StatusCode::OK;
}
+10 −0
Original line number Diff line number Diff line
@@ -61,6 +61,16 @@ class DefaultVehicleHal : public VehicleHal {
    virtual void onPropertyValue(const VehiclePropValue& value, bool updateStatus);
    // Returns a lambda that could be used in mRecurrentTimer.
    RecurrentTimer::Action getTimerAction();
    // Check whether a propValue is valid according to its type.
    StatusCode checkPropValue(const VehiclePropValue& propValue, const VehiclePropConfig* config);
    // Check whether the property value is within the range according to area config.
    StatusCode checkValueRange(const VehiclePropValue& propValue, const VehiclePropConfig* config);

  private:
    // Check whether a vendor mixed value property is valid according to its config array.
    // See 'VehiclePropertyType' documentation in 'types.hal' for detail.
    StatusCode checkVendorMixedPropValue(const VehiclePropValue& value,
                                         const VehiclePropConfig* config);
};

}  // namespace impl
+184 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include <gtest/gtest.h>
#include <sys/mman.h>
#include <vhal_v2_0/ConcurrentQueue.h>
#include <vhal_v2_0/DefaultConfig.h>
#include <vhal_v2_0/DefaultVehicleConnector.h>
#include <vhal_v2_0/DefaultVehicleHal.h>
#include <vhal_v2_0/PropertyUtils.h>
@@ -40,6 +41,8 @@ using ::android::hardware::automotive::vehicle::V2_0::VehiclePropValue;
using ::android::hardware::automotive::vehicle::V2_0::VehiclePropValuePool;
using ::android::hardware::automotive::vehicle::V2_0::impl::DefaultVehicleConnector;
using ::android::hardware::automotive::vehicle::V2_0::impl::DefaultVehicleHal;
using ::android::hardware::automotive::vehicle::V2_0::impl::HVAC_LEFT;
using ::android::hardware::automotive::vehicle::V2_0::impl::kMixedTypePropertyForTest;

using VehiclePropValuePtr = recyclable_ptr<VehiclePropValue>;

@@ -194,6 +197,32 @@ TEST_F(DefaultVhalImplTest, testSetString) {
    EXPECT_EQ("My Vehicle", gotValue->value.stringValue);
}

TEST_F(DefaultVhalImplTest, testSetMixed) {
    VehiclePropValue value;
    value.prop = kMixedTypePropertyForTest;
    // mixed prop.
    // .configArray = {1, 1, 0, 2, 0, 0, 1, 0, 0}
    // 1 string, 1 int, 0 bool, 2 ints, 0 int64, 0 int64s, 1 float, 0 floats, 0 bytes
    value.value.stringValue = "test";
    value.value.int32Values.resize(3);
    value.value.int32Values[0] = 1;
    value.value.int32Values[1] = 2;
    value.value.int32Values[2] = 3;
    value.value.floatValues.resize(1);
    value.value.floatValues[0] = 1.0f;

    StatusCode status = mHal->set(value);
    EXPECT_EQ(StatusCode::OK, status);

    auto gotValue = mHal->get(value, &status);
    EXPECT_EQ(StatusCode::OK, status);
    EXPECT_EQ("test", gotValue->value.stringValue);
    EXPECT_EQ(1, gotValue->value.int32Values[0]);
    EXPECT_EQ(2, gotValue->value.int32Values[1]);
    EXPECT_EQ(3, gotValue->value.int32Values[2]);
    EXPECT_EQ(1.0f, gotValue->value.floatValues[0]);
}

TEST_F(DefaultVhalImplTest, testSetUnknownProperty) {
    VehiclePropValue value;
    value.prop = 0;
@@ -247,6 +276,13 @@ TEST_F(DefaultVhalImplTest, testSubscribeInvalidProp) {
    EXPECT_EQ(StatusCode::INVALID_ARG, mHal->subscribe(toInt(VehicleProperty::INFO_MAKE), 10));
}

TEST_F(DefaultVhalImplTest, testSubscribeSampleRateOutOfRange) {
    EXPECT_EQ(StatusCode::INVALID_ARG,
              mHal->subscribe(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 10.1));
    EXPECT_EQ(StatusCode::INVALID_ARG,
              mHal->subscribe(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0.5));
}

TEST_F(DefaultVhalImplTest, testUnsubscribe) {
    auto status = mHal->subscribe(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 10);
    EXPECT_EQ(StatusCode::OK, status);
@@ -300,4 +336,152 @@ TEST_F(DefaultVhalImplTest, testDump) {
    EXPECT_NE(std::string::npos, std::string(buf).find(infoMake));
}

class DefaultVhalImplSetInvalidPropTest : public DefaultVhalImplTest,
                                          public testing::WithParamInterface<VehiclePropValue> {};

std::vector<VehiclePropValue> GenSetInvalidPropParams() {
    std::vector<VehiclePropValue> props;
    // int prop with no value.
    VehiclePropValue intProp = {.prop = toInt(VehicleProperty::INFO_MODEL_YEAR)};
    props.push_back(intProp);

    // int prop with more than one value.
    VehiclePropValue intPropWithValues = {.prop = toInt(VehicleProperty::INFO_MODEL_YEAR)};
    intPropWithValues.value.int32Values.resize(2);
    props.push_back(intPropWithValues);

    // int vec prop with no value.
    VehiclePropValue intVecProp = {.prop = toInt(VehicleProperty::INFO_FUEL_TYPE)};
    props.push_back(intVecProp);

    // int64 prop with no value.
    VehiclePropValue int64Prop = {.prop = toInt(VehicleProperty::EPOCH_TIME)};
    props.push_back(int64Prop);

    // int64 prop with more than one value.
    VehiclePropValue int64PropWithValues = {.prop = toInt(VehicleProperty::EPOCH_TIME)};
    int64PropWithValues.value.int64Values.resize(2);
    props.push_back(int64PropWithValues);

    // int64 vec prop with no value.
    VehiclePropValue int64VecProp = {.prop = toInt(VehicleProperty::WHEEL_TICK)};
    props.push_back(int64VecProp);

    // float prop with no value.
    VehiclePropValue floatProp = {.prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY)};
    props.push_back(floatProp);

    // float prop with more than one value.
    VehiclePropValue floatPropWithValues = {.prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY)};
    floatPropWithValues.value.floatValues.resize(2);
    props.push_back(floatPropWithValues);

    // float vec prop with no value.
    VehiclePropValue floatVecProp = {
            .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION)};
    props.push_back(floatVecProp);

    // bool prop with no value.
    VehiclePropValue boolProp = {
            .prop = toInt(VehicleProperty::FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME)};
    props.push_back(boolProp);

    // bool prop with more than one value.
    VehiclePropValue boolPropWithValues = {
            .prop = toInt(VehicleProperty::FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME)};
    boolPropWithValues.value.int32Values.resize(2);
    props.push_back(boolPropWithValues);

    // mixed prop.
    // .configArray = {1, 1, 0, 2, 0, 0, 1, 0, 0}
    // 1 string, 1 int, 0 bool, 2 ints, 0 int64, 0 int64s, 1 float, 0 floats, 0 bytes
    VehiclePropValue mixedProp1 = {.prop = kMixedTypePropertyForTest};
    // Expect 1 bool, and 2 ints, we only have 1 value.
    mixedProp1.value.int32Values.resize(1);
    mixedProp1.value.floatValues.resize(1);
    props.push_back(mixedProp1);

    VehiclePropValue mixedProp2 = {.prop = kMixedTypePropertyForTest};
    mixedProp2.value.int32Values.resize(3);
    // Missing float value.
    mixedProp2.value.floatValues.resize(0);
    props.push_back(mixedProp2);

    return props;
}

TEST_P(DefaultVhalImplSetInvalidPropTest, testSetInvalidPropValue) {
    VehiclePropValue value = GetParam();

    StatusCode status = mHal->set(value);

    EXPECT_EQ(StatusCode::INVALID_ARG, status);
}

INSTANTIATE_TEST_SUITE_P(DefaultVhalImplSetInvalidPropTests, DefaultVhalImplSetInvalidPropTest,
                         testing::ValuesIn(GenSetInvalidPropParams()));

struct SetPropRangeTestCase {
    std::string name;
    VehiclePropValue prop;
    StatusCode code;
};

class DefaultVhalImplSetPropRangeTest : public DefaultVhalImplTest,
                                        public testing::WithParamInterface<SetPropRangeTestCase> {};

std::vector<SetPropRangeTestCase> GenSetPropRangeParams() {
    std::vector<SetPropRangeTestCase> tc;
    VehiclePropValue intPropNormal = {.prop = toInt(VehicleProperty::HVAC_FAN_SPEED),
                                      .areaId = HVAC_LEFT,
                                      // min: 1, max: 7
                                      .value.int32Values = {3}};
    tc.push_back({"normal_case_int", intPropNormal, StatusCode::OK});

    VehiclePropValue intPropSmall = {.prop = toInt(VehicleProperty::HVAC_FAN_SPEED),
                                     .areaId = HVAC_LEFT,
                                     // min: 1, max: 7
                                     .value.int32Values = {0}};
    tc.push_back({"normal_case_int_too_small", intPropSmall, StatusCode::INVALID_ARG});

    VehiclePropValue intPropLarge = {.prop = toInt(VehicleProperty::HVAC_FAN_SPEED),
                                     .areaId = HVAC_LEFT,
                                     // min: 1, max: 7
                                     .value.int32Values = {8}};
    tc.push_back({"normal_case_int_too_large", intPropLarge, StatusCode::INVALID_ARG});

    VehiclePropValue floatPropNormal = {.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
                                        .areaId = HVAC_LEFT,
                                        // min: 16, max: 32
                                        .value.floatValues = {26}};
    tc.push_back({"normal_case_float", floatPropNormal, StatusCode::OK});
    VehiclePropValue floatPropSmall = {.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
                                       .areaId = HVAC_LEFT,
                                       // min: 16, max: 32
                                       .value.floatValues = {15.5}};
    tc.push_back({"normal_case_float_too_small", floatPropSmall, StatusCode::INVALID_ARG});
    VehiclePropValue floatPropLarge = {.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
                                       .areaId = HVAC_LEFT,
                                       // min: 16, max: 32
                                       .value.floatValues = {32.6}};
    tc.push_back({"normal_case_float_too_large", floatPropLarge, StatusCode::INVALID_ARG});

    return tc;
}

TEST_P(DefaultVhalImplSetPropRangeTest, testSetPropRange) {
    SetPropRangeTestCase tc = GetParam();

    StatusCode status = mHal->set(tc.prop);

    EXPECT_EQ(tc.code, status);
}

INSTANTIATE_TEST_SUITE_P(
        DefaultVhalImplSetPropRangeTests, DefaultVhalImplSetPropRangeTest,
        testing::ValuesIn(GenSetPropRangeParams()),
        [](const testing::TestParamInfo<DefaultVhalImplSetPropRangeTest::ParamType>& info) {
            return info.param.name;
        });

}  // namespace