Migrate VehiclePropertyStore.

Test: atest VehicleHalVehicleUtilsTest
Bug: 199337732
Change-Id: Ia18699a0115fdb004c57c0e6fb02b043ddb138b5
This commit is contained in:
Yu Shan
2021-09-16 19:44:55 -07:00
parent 9af220ec42
commit 81fbbea6b9
5 changed files with 687 additions and 1 deletions

View File

@@ -32,5 +32,6 @@ cc_defaults {
"-Wall",
"-Wextra",
"-Werror",
"-Wthread-safety",
],
}

View File

@@ -0,0 +1,136 @@
/*
* 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.
*/
#ifndef android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_
#define android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <VehicleHalTypes.h>
#include <android-base/result.h>
#include <android-base/thread_annotations.h>
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
// Encapsulates work related to storing and accessing configuration, storing and modifying
// vehicle property values.
//
// VehiclePropertyValues stored in a sorted map thus it makes easier to get range of values, e.g.
// to get value for all areas for particular property.
//
// This class is thread-safe, however it uses blocking synchronization across all methods.
class VehiclePropertyStore {
public:
// Function that used to calculate unique token for given VehiclePropValue.
using TokenFunction = ::std::function<int64_t(
const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)>;
// Register the given property according to the config. A property has to be registered first
// before write/read. If tokenFunc is not nullptr, it would be used to generate a unique
// property token to act as the key the property store. Otherwise, {propertyID, areaID} would be
// used as the key.
void registerProperty(
const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& config,
TokenFunction tokenFunc = nullptr);
// Stores provided value. Returns true if value was written returns false if config wasn't
// registered.
::android::base::Result<void> writeValue(
const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
// Remove a given property value from the property store. The 'propValue' would be used to
// generate the key for the value to remove.
void removeValue(
const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
// Remove all the values for the property.
void removeValuesForProperty(int32_t propId);
// Read all the stored values.
std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue> readAllValues()
const;
// Read all the values for the property.
::android::base::Result<
std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
readValuesForProperty(int32_t propId) const;
// Read the value for the requested property.
::android::base::Result<
std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
readValue(
const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const;
// Read the value for the requested property.
::android::base::Result<
std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const;
// Get all property configs.
std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropConfig> getAllConfigs()
const;
// Get the property config for the requested property.
::android::base::Result<
const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig*>
getConfig(int32_t propId) const;
private:
struct RecordId {
int32_t area;
int64_t token;
bool operator==(const RecordId& other) const;
bool operator<(const RecordId& other) const;
std::string toString() const;
};
struct Record {
::aidl::android::hardware::automotive::vehicle::VehiclePropConfig propConfig;
TokenFunction tokenFunction;
std::map<RecordId, ::aidl::android::hardware::automotive::vehicle::VehiclePropValue> values;
};
mutable std::mutex mLock;
std::unordered_map<int32_t, Record> mRecordsByPropId GUARDED_BY(mLock);
const Record* getRecordLocked(int32_t propId) const;
Record* getRecordLocked(int32_t propId);
RecordId getRecordIdLocked(
const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue,
const Record& record) const;
::android::base::Result<
std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
readValueLocked(const RecordId& recId, const Record& record) const;
};
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android
#endif // android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_

View File

@@ -0,0 +1,231 @@
/*
* 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.
*/
#define LOG_TAG "VehiclePropertyStore"
#include <utils/Log.h>
#include "VehiclePropertyStore.h"
#include <VehicleUtils.h>
#include <android-base/format.h>
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::android::base::Result;
bool VehiclePropertyStore::RecordId::operator==(const VehiclePropertyStore::RecordId& other) const {
return area == other.area && token == other.token;
}
bool VehiclePropertyStore::RecordId::operator<(const VehiclePropertyStore::RecordId& other) const {
return area < other.area || (area == other.area && token < other.token);
}
std::string VehiclePropertyStore::RecordId::toString() const {
return ::fmt::format("RecordID{{.areaId={:d}, .token={:d}}}", area, token);
}
const VehiclePropertyStore::Record* VehiclePropertyStore::getRecordLocked(int32_t propId) const
REQUIRES(mLock) {
auto RecordIt = mRecordsByPropId.find(propId);
return RecordIt == mRecordsByPropId.end() ? nullptr : &RecordIt->second;
}
VehiclePropertyStore::Record* VehiclePropertyStore::getRecordLocked(int32_t propId)
REQUIRES(mLock) {
auto RecordIt = mRecordsByPropId.find(propId);
return RecordIt == mRecordsByPropId.end() ? nullptr : &RecordIt->second;
}
VehiclePropertyStore::RecordId VehiclePropertyStore::getRecordIdLocked(
const VehiclePropValue& propValue, const VehiclePropertyStore::Record& record) const
REQUIRES(mLock) {
VehiclePropertyStore::RecordId recId{
.area = isGlobalProp(propValue.prop) ? 0 : propValue.areaId, .token = 0};
if (record.tokenFunction != nullptr) {
recId.token = record.tokenFunction(propValue);
}
return recId;
}
Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValueLocked(
const RecordId& recId, const Record& record) const REQUIRES(mLock) {
auto it = record.values.find(recId);
if (it == record.values.end()) {
return Errorf("Record ID: {} is not found", recId.toString());
}
return std::make_unique<VehiclePropValue>(it->second);
}
void VehiclePropertyStore::registerProperty(const VehiclePropConfig& config,
VehiclePropertyStore::TokenFunction tokenFunc) {
std::lock_guard<std::mutex> g(mLock);
mRecordsByPropId[config.prop] = Record{
.propConfig = config,
.tokenFunction = tokenFunc,
};
}
Result<void> VehiclePropertyStore::writeValue(const VehiclePropValue& propValue) {
std::lock_guard<std::mutex> g(mLock);
VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop);
if (record == nullptr) {
return Errorf("property: {:d} not registered", propValue.prop);
}
if (!isGlobalProp(propValue.prop) && getAreaConfig(propValue, record->propConfig) == nullptr) {
return Errorf("no config for property: {:d} area: {:d}", propValue.prop, propValue.areaId);
}
VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record);
auto it = record->values.find(recId);
if (it == record->values.end()) {
record->values[recId] = propValue;
return {};
}
VehiclePropValue* valueToUpdate = &(it->second);
// propValue is outdated and drops it.
if (valueToUpdate->timestamp > propValue.timestamp) {
return Errorf("outdated timestamp: {:d}", propValue.timestamp);
}
// Update the propertyValue.
// The timestamp in propertyStore should only be updated by the server side. It indicates
// the time when the event is generated by the server.
valueToUpdate->timestamp = propValue.timestamp;
valueToUpdate->value = propValue.value;
valueToUpdate->status = propValue.status;
return {};
}
void VehiclePropertyStore::removeValue(const VehiclePropValue& propValue) {
std::lock_guard<std::mutex> g(mLock);
VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop);
if (record == nullptr) {
return;
}
VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record);
if (auto it = record->values.find(recId); it != record->values.end()) {
record->values.erase(it);
}
}
void VehiclePropertyStore::removeValuesForProperty(int32_t propId) {
std::lock_guard<std::mutex> g(mLock);
VehiclePropertyStore::Record* record = getRecordLocked(propId);
if (record == nullptr) {
return;
}
record->values.clear();
}
std::vector<VehiclePropValue> VehiclePropertyStore::readAllValues() const {
std::lock_guard<std::mutex> g(mLock);
std::vector<VehiclePropValue> allValues;
for (auto const& [_, record] : mRecordsByPropId) {
for (auto const& [_, value] : record.values) {
allValues.push_back(value);
}
}
return allValues;
}
Result<std::vector<VehiclePropValue>> VehiclePropertyStore::readValuesForProperty(
int32_t propId) const {
std::lock_guard<std::mutex> g(mLock);
std::vector<VehiclePropValue> values;
const VehiclePropertyStore::Record* record = getRecordLocked(propId);
if (record == nullptr) {
return Errorf("property: {:d} not registered", propId);
}
for (auto const& [_, value] : record->values) {
values.push_back(value);
}
return values;
}
Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValue(
const VehiclePropValue& propValue) const {
std::lock_guard<std::mutex> g(mLock);
const VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop);
if (record == nullptr) {
return Errorf("property: {:d} not registered", propValue.prop);
}
VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record);
return readValueLocked(recId, *record);
}
Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValue(int32_t propId,
int32_t areaId,
int64_t token) const {
std::lock_guard<std::mutex> g(mLock);
const VehiclePropertyStore::Record* record = getRecordLocked(propId);
if (record == nullptr) {
return Errorf("property: {:d} not registered", propId);
}
VehiclePropertyStore::RecordId recId{.area = isGlobalProp(propId) ? 0 : areaId, .token = token};
return readValueLocked(recId, *record);
}
std::vector<VehiclePropConfig> VehiclePropertyStore::getAllConfigs() const {
std::lock_guard<std::mutex> g(mLock);
std::vector<VehiclePropConfig> configs;
configs.reserve(mRecordsByPropId.size());
for (auto& [_, config] : mRecordsByPropId) {
configs.push_back(config.propConfig);
}
return configs;
}
Result<const VehiclePropConfig*> VehiclePropertyStore::getConfig(int32_t propId) const {
std::lock_guard<std::mutex> g(mLock);
const VehiclePropertyStore::Record* record = getRecordLocked(propId);
if (record == nullptr) {
return Errorf("property: {:d} not registered", propId);
}
return &record->propConfig;
}
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android

View File

@@ -22,7 +22,11 @@ cc_test {
name: "VehicleHalVehicleUtilsTest",
srcs: ["*.cpp"],
vendor: true,
static_libs: ["VehicleHalUtils"],
static_libs: [
"VehicleHalUtils",
"libgtest",
"libgmock",
],
defaults: ["VehicleHalDefaults"],
test_suites: ["general-tests"],
}

View File

@@ -0,0 +1,314 @@
/*
* 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.
*/
#include <PropertyUtils.h>
#include <VehiclePropertyStore.h>
#include <VehicleUtils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
namespace {
using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::android::base::Result;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::WhenSortedBy;
constexpr int INVALID_PROP_ID = 0;
struct PropValueCmp {
bool operator()(const VehiclePropValue& a, const VehiclePropValue& b) const {
return (a.prop < b.prop) || ((a.prop == b.prop) && (a.value < b.value)) ||
((a.prop == b.prop) && (a.value == b.value) && (a.areaId < b.areaId));
}
} propValueCmp;
int64_t timestampToken(const VehiclePropValue& value) {
return value.timestamp;
}
} // namespace
class VehiclePropertyStoreTest : public ::testing::Test {
protected:
void SetUp() override {
mConfigFuelCapacity = {
.prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
.access = VehiclePropertyAccess::READ,
.changeMode = VehiclePropertyChangeMode::STATIC,
};
VehiclePropConfig configTirePressure = {
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.access = VehiclePropertyAccess::READ,
.changeMode = VehiclePropertyChangeMode::CONTINUOUS,
.areaConfigs = {VehicleAreaConfig{.areaId = WHEEL_FRONT_LEFT},
VehicleAreaConfig{.areaId = WHEEL_FRONT_RIGHT},
VehicleAreaConfig{.areaId = WHEEL_REAR_LEFT},
VehicleAreaConfig{.areaId = WHEEL_REAR_RIGHT}},
};
mStore.registerProperty(mConfigFuelCapacity);
mStore.registerProperty(configTirePressure);
}
VehiclePropertyStore mStore;
VehiclePropConfig mConfigFuelCapacity;
};
TEST_F(VehiclePropertyStoreTest, testGetAllConfigs) {
std::vector<VehiclePropConfig> configs = mStore.getAllConfigs();
ASSERT_EQ(configs.size(), static_cast<size_t>(2));
}
TEST_F(VehiclePropertyStoreTest, testGetConfig) {
Result<const VehiclePropConfig*> result =
mStore.getConfig(toInt(VehicleProperty::INFO_FUEL_CAPACITY));
ASSERT_RESULT_OK(result);
ASSERT_EQ(*(result.value()), mConfigFuelCapacity);
}
TEST_F(VehiclePropertyStoreTest, testGetConfigWithInvalidPropId) {
Result<const VehiclePropConfig*> result = mStore.getConfig(INVALID_PROP_ID);
ASSERT_FALSE(result.ok()) << "expect error when getting a config for an invalid property ID";
}
std::vector<VehiclePropValue> getTestPropValues() {
VehiclePropValue fuelCapacity = {
.prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
.value = {.floatValues = {1.0}},
};
VehiclePropValue leftTirePressure = {
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.value = {.floatValues = {170.0}},
.areaId = WHEEL_FRONT_LEFT,
};
VehiclePropValue rightTirePressure = {
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.value = {.floatValues = {180.0}},
.areaId = WHEEL_FRONT_RIGHT,
};
return {fuelCapacity, leftTirePressure, rightTirePressure};
}
TEST_F(VehiclePropertyStoreTest, testWriteValueOk) {
auto values = getTestPropValues();
ASSERT_RESULT_OK(mStore.writeValue(values[0]));
}
TEST_F(VehiclePropertyStoreTest, testReadAllValues) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
auto gotValues = mStore.readAllValues();
ASSERT_THAT(gotValues, WhenSortedBy(propValueCmp, Eq(values)));
}
TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyOneValue) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
auto result = mStore.readValuesForProperty(toInt(VehicleProperty::INFO_FUEL_CAPACITY));
ASSERT_RESULT_OK(result);
ASSERT_THAT(result.value(), ElementsAre(values[0]));
}
TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyMultipleValues) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
auto result = mStore.readValuesForProperty(toInt(VehicleProperty::TIRE_PRESSURE));
ASSERT_RESULT_OK(result);
ASSERT_THAT(result.value(), WhenSortedBy(propValueCmp, ElementsAre(values[1], values[2])));
}
TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyError) {
auto result = mStore.readValuesForProperty(INVALID_PROP_ID);
ASSERT_FALSE(result.ok()) << "expect error when reading values for an invalid property";
}
TEST_F(VehiclePropertyStoreTest, testReadValueOk) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
VehiclePropValue requestValue = {
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.areaId = WHEEL_FRONT_LEFT,
};
auto result = mStore.readValue(requestValue);
ASSERT_RESULT_OK(result);
ASSERT_EQ(*(result.value()), values[1]);
}
TEST_F(VehiclePropertyStoreTest, testReadValueByPropIdOk) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
auto result = mStore.readValue(toInt(VehicleProperty::TIRE_PRESSURE), WHEEL_FRONT_RIGHT);
ASSERT_EQ(*(result.value()), values[2]);
}
TEST_F(VehiclePropertyStoreTest, testReadValueError) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
auto result = mStore.readValue(toInt(VehicleProperty::TIRE_PRESSURE), WHEEL_REAR_LEFT);
ASSERT_FALSE(result.ok()) << "expect error when reading a value that has not been written";
}
TEST_F(VehiclePropertyStoreTest, testWriteValueError) {
ASSERT_FALSE(mStore.writeValue({
.prop = INVALID_PROP_ID,
.value = {.floatValues = {1.0}},
})
.ok())
<< "expect error when writing value for an invalid property ID";
}
TEST_F(VehiclePropertyStoreTest, testWriteValueNoAreaConfig) {
ASSERT_FALSE(mStore.writeValue({
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.value = {.floatValues = {180.0}},
// There is no config for ALL_WHEELS.
.areaId = ALL_WHEELS,
})
.ok())
<< "expect error when writing value for an area without config";
}
TEST_F(VehiclePropertyStoreTest, testWriteOutdatedValue) {
ASSERT_RESULT_OK(mStore.writeValue({
.timestamp = 1,
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.value = {.floatValues = {180.0}},
.areaId = WHEEL_FRONT_LEFT,
}));
// Write an older value.
ASSERT_FALSE(mStore.writeValue({
.timestamp = 0,
.prop = toInt(VehicleProperty::TIRE_PRESSURE),
.value = {.floatValues = {180.0}},
.areaId = WHEEL_FRONT_LEFT,
})
.ok())
<< "expect error when writing an outdated value";
}
TEST_F(VehiclePropertyStoreTest, testToken) {
int propId = toInt(VehicleProperty::INFO_FUEL_CAPACITY);
VehiclePropConfig config = {
.prop = propId,
};
// Replace existing config.
mStore.registerProperty(config, timestampToken);
VehiclePropValue fuelCapacityValueToken1 = {
.timestamp = 1,
.prop = propId,
.value = {.floatValues = {1.0}},
};
VehiclePropValue fuelCapacityValueToken2 = {
.timestamp = 2,
.prop = propId,
.value = {.floatValues = {2.0}},
};
ASSERT_RESULT_OK(mStore.writeValue(fuelCapacityValueToken1));
ASSERT_RESULT_OK(mStore.writeValue(fuelCapacityValueToken2));
auto result = mStore.readValuesForProperty(propId);
ASSERT_RESULT_OK(result);
ASSERT_EQ(result.value().size(), static_cast<size_t>(2));
auto tokenResult = mStore.readValue(propId, /*areaId=*/0, /*token=*/2);
ASSERT_RESULT_OK(tokenResult);
ASSERT_EQ(*(tokenResult.value()), fuelCapacityValueToken2);
}
TEST_F(VehiclePropertyStoreTest, testRemoveValue) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
mStore.removeValue(values[0]);
ASSERT_FALSE(mStore.readValue(values[0]).ok()) << "expect error when reading a removed value";
auto leftTirePressureResult = mStore.readValue(values[1]);
ASSERT_RESULT_OK(leftTirePressureResult);
ASSERT_EQ(*(leftTirePressureResult.value()), values[1]);
}
TEST_F(VehiclePropertyStoreTest, testRemoveValuesForProperty) {
auto values = getTestPropValues();
for (const auto& value : values) {
ASSERT_RESULT_OK(mStore.writeValue(value));
}
mStore.removeValuesForProperty(toInt(VehicleProperty::INFO_FUEL_CAPACITY));
mStore.removeValuesForProperty(toInt(VehicleProperty::TIRE_PRESSURE));
auto gotValues = mStore.readAllValues();
ASSERT_TRUE(gotValues.empty());
}
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android