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

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

Add permission check and heartbeat event to VHAL.

Test: atest DefaultVehicleHalTest
Bug: 200737967
Change-Id: I5ee4209a59dd63173060fb52a69a80bfbb3522c9
parent 4299bb32
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -126,6 +126,8 @@ class DefaultVehicleHal final : public ::aidl::android::hardware::automotive::ve
    // TODO(b/214605968): define TIMEOUT_IN_NANO in IVehicle and allow getValues/setValues/subscribe
    // to specify custom timeouts.
    static constexpr int64_t TIMEOUT_IN_NANO = 30'000'000'000;
    // heart beat event interval: 3s
    static constexpr int64_t HEART_BEAT_INTERVAL_IN_NANO = 3'000'000'000;
    const std::shared_ptr<IVehicleHardware> mVehicleHardware;

    // mConfigsByPropId and mConfigFile are only modified during initialization, so no need to
@@ -146,6 +148,8 @@ class DefaultVehicleHal final : public ::aidl::android::hardware::automotive::ve
            GUARDED_BY(mLock);
    // SubscriptionClients is thread-safe.
    std::shared_ptr<SubscriptionClients> mSubscriptionClients;
    // RecurrentTimer is thread-safe.
    RecurrentTimer mRecurrentTimer;

    ::android::base::Result<void> checkProperty(
            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
@@ -162,6 +166,16 @@ class DefaultVehicleHal final : public ::aidl::android::hardware::automotive::ve
            const std::vector<::aidl::android::hardware::automotive::vehicle::SubscribeOptions>&
                    options);

    ::android::base::Result<void> checkReadPermission(
            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;

    ::android::base::Result<void> checkWritePermission(
            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;

    ::android::base::Result<
            const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig*>
    getConfig(int32_t propId) const;

    template <class T>
    static std::shared_ptr<T> getOrCreateClient(
            std::unordered_map<CallbackType, std::shared_ptr<T>>* clients,
@@ -178,6 +192,9 @@ class DefaultVehicleHal final : public ::aidl::android::hardware::automotive::ve
            const std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
                    updatedValues);

    static void checkHealth(std::weak_ptr<IVehicleHardware> hardware,
                            std::weak_ptr<SubscriptionManager> subscriptionManager);

    // Test-only
    // Set the default timeout for pending requests.
    void setTimeout(int64_t timeoutInNano);
+151 −25
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include <android-base/result.h>
#include <android-base/stringprintf.h>
#include <utils/Log.h>
#include <utils/SystemClock.h>

#include <inttypes.h>
#include <set>
@@ -51,7 +52,10 @@ using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfigs;
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::VehiclePropertyStatus;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::android::automotive::car_binder_lib::LargeParcelableBase;
using ::android::base::Error;
@@ -128,6 +132,13 @@ DefaultVehicleHal::DefaultVehicleHal(std::unique_ptr<IVehicleHardware> hardware)
                    [subscriptionManagerCopy](std::vector<VehiclePropValue> updatedValues) {
                        onPropertyChangeEvent(subscriptionManagerCopy, updatedValues);
                    }));

    // Register heartbeat event.
    mRecurrentTimer.registerTimerCallback(
            HEART_BEAT_INTERVAL_IN_NANO,
            std::make_shared<std::function<void()>>([hardwareCopy, subscriptionManagerCopy]() {
                checkHealth(hardwareCopy, subscriptionManagerCopy);
            }));
}

void DefaultVehicleHal::onPropertyChangeEvent(
@@ -222,27 +233,35 @@ ScopedAStatus DefaultVehicleHal::getAllPropConfigs(VehiclePropConfigs* output) {
    return ScopedAStatus::ok();
}

Result<void> DefaultVehicleHal::checkProperty(const VehiclePropValue& propValue) {
    int32_t propId = propValue.prop;
Result<const VehiclePropConfig*> DefaultVehicleHal::getConfig(int32_t propId) const {
    auto it = mConfigsByPropId.find(propId);
    if (it == mConfigsByPropId.end()) {
        return Error() << "no config for property, ID: " << propId;
    }
    const VehiclePropConfig& config = it->second;
    const VehicleAreaConfig* areaConfig = getAreaConfig(propValue, config);
    return &(it->second);
}

Result<void> DefaultVehicleHal::checkProperty(const VehiclePropValue& propValue) {
    int32_t propId = propValue.prop;
    auto result = getConfig(propId);
    if (!result.ok()) {
        return result.error();
    }
    const VehiclePropConfig* config = result.value();
    const VehicleAreaConfig* areaConfig = getAreaConfig(propValue, *config);
    if (!isGlobalProp(propId) && areaConfig == nullptr) {
        // Ignore areaId for global property. For non global property, check whether areaId is
        // allowed. areaId must appear in areaConfig.
        return Error() << "invalid area ID: " << propValue.areaId << " for prop ID: " << propId
                       << ", not listed in config";
    }
    if (auto result = checkPropValue(propValue, &config); !result.ok()) {
    if (auto result = checkPropValue(propValue, config); !result.ok()) {
        return Error() << "invalid property value: " << propValue.toString()
                       << ", error: " << result.error().message();
                       << ", error: " << getErrorMsg(result);
    }
    if (auto result = checkValueRange(propValue, areaConfig); !result.ok()) {
        return Error() << "property value out of range: " << propValue.toString()
                       << ", error: " << result.error().message();
                       << ", error: " << getErrorMsg(result);
    }
    return {};
}
@@ -263,9 +282,30 @@ ScopedAStatus DefaultVehicleHal::getValues(const CallbackType& callback,
        ALOGE("getValues: duplicate request ID");
        return toScopedAStatus(maybeRequestIds, StatusCode::INVALID_ARG);
    }

    // A list of failed result we already know before sending to hardware.
    std::vector<GetValueResult> failedResults;
    // The list of requests that we would send to hardware.
    std::vector<GetValueRequest> hardwareRequests;

    for (const auto& request : getValueRequests) {
        if (auto result = checkReadPermission(request.prop); !result.ok()) {
            ALOGW("property does not support reading: %s", getErrorMsg(result).c_str());
            failedResults.push_back(GetValueResult{
                    .requestId = request.requestId,
                    .status = getErrorCode(result),
                    .prop = {},
            });
        } else {
            hardwareRequests.push_back(request);
        }
    }

    // The set of request Ids that we would send to hardware.
    std::unordered_set<int64_t> hardwareRequestIds(maybeRequestIds.value().begin(),
                                                   maybeRequestIds.value().end());
    std::unordered_set<int64_t> hardwareRequestIds;
    for (const auto& request : hardwareRequests) {
        hardwareRequestIds.insert(request.requestId);
    }

    std::shared_ptr<GetValuesClient> client;
    {
@@ -275,12 +315,21 @@ ScopedAStatus DefaultVehicleHal::getValues(const CallbackType& callback,
    // Register the pending hardware requests and also check for duplicate request Ids.
    if (auto addRequestResult = client->addRequests(hardwareRequestIds); !addRequestResult.ok()) {
        ALOGE("getValues[%s]: failed to add pending requests, error: %s",
              toString(hardwareRequestIds).c_str(), addRequestResult.error().message().c_str());
              toString(hardwareRequestIds).c_str(), getErrorMsg(addRequestResult).c_str());
        return toScopedAStatus(addRequestResult);
    }

    if (!failedResults.empty()) {
        // First send the failed results we already know back to the client.
        client->sendResults(failedResults);
    }

    if (hardwareRequests.empty()) {
        return ScopedAStatus::ok();
    }

    if (StatusCode status =
                mVehicleHardware->getValues(client->getResultCallback(), getValueRequests);
                mVehicleHardware->getValues(client->getResultCallback(), hardwareRequests);
        status != StatusCode::OK) {
        // If the hardware returns error, finish all the pending requests for this request because
        // we never expect hardware to call callback for these requests.
@@ -317,9 +366,17 @@ ScopedAStatus DefaultVehicleHal::setValues(const CallbackType& callback,

    for (auto& request : setValueRequests) {
        int64_t requestId = request.requestId;
        if (auto result = checkWritePermission(request.value); !result.ok()) {
            ALOGW("property does not support writing: %s", getErrorMsg(result).c_str());
            failedResults.push_back(SetValueResult{
                    .requestId = requestId,
                    .status = getErrorCode(result),
            });
            continue;
        }
        if (auto result = checkProperty(request.value); !result.ok()) {
            ALOGW("setValues[%" PRId64 "]: property not valid: %s", requestId,
                  result.error().message().c_str());
            ALOGW("setValues[%" PRId64 "]: property is not valid: %s", requestId,
                  getErrorMsg(result).c_str());
            failedResults.push_back(SetValueResult{
                    .requestId = requestId,
                    .status = StatusCode::INVALID_ARG,
@@ -345,7 +402,7 @@ ScopedAStatus DefaultVehicleHal::setValues(const CallbackType& callback,
    // Register the pending hardware requests and also check for duplicate request Ids.
    if (auto addRequestResult = client->addRequests(hardwareRequestIds); !addRequestResult.ok()) {
        ALOGE("setValues[%s], failed to add pending requests, error: %s",
              toString(hardwareRequestIds).c_str(), addRequestResult.error().message().c_str());
              toString(hardwareRequestIds).c_str(), getErrorMsg(addRequestResult).c_str());
        return toScopedAStatus(addRequestResult, StatusCode::INVALID_ARG);
    }

@@ -354,6 +411,10 @@ ScopedAStatus DefaultVehicleHal::setValues(const CallbackType& callback,
        client->sendResults(failedResults);
    }

    if (hardwareRequests.empty()) {
        return ScopedAStatus::ok();
    }

    if (StatusCode status =
                mVehicleHardware->setValues(client->getResultCallback(), hardwareRequests);
        status != StatusCode::OK) {
@@ -413,13 +474,21 @@ Result<void> DefaultVehicleHal::checkSubscribeOptions(
    for (const auto& option : options) {
        int32_t propId = option.propId;
        if (mConfigsByPropId.find(propId) == mConfigsByPropId.end()) {
            return Error() << StringPrintf("no config for property, ID: %" PRId32, propId);
            return Error(toInt(StatusCode::INVALID_ARG))
                   << StringPrintf("no config for property, ID: %" PRId32, propId);
        }
        const VehiclePropConfig& config = mConfigsByPropId[propId];

        if (config.changeMode != VehiclePropertyChangeMode::ON_CHANGE &&
            config.changeMode != VehiclePropertyChangeMode::CONTINUOUS) {
            return Error() << "only support subscribing to ON_CHANGE or CONTINUOUS property";
            return Error(toInt(StatusCode::INVALID_ARG))
                   << "only support subscribing to ON_CHANGE or CONTINUOUS property";
        }

        if (config.access != VehiclePropertyAccess::READ &&
            config.access != VehiclePropertyAccess::READ_WRITE) {
            return Error(toInt(StatusCode::ACCESS_DENIED))
                   << StringPrintf("Property %" PRId32 " has no read access", propId);
        }

        if (config.changeMode == VehiclePropertyChangeMode::CONTINUOUS) {
@@ -427,12 +496,13 @@ Result<void> DefaultVehicleHal::checkSubscribeOptions(
            float minSampleRate = config.minSampleRate;
            float maxSampleRate = config.maxSampleRate;
            if (sampleRate < minSampleRate || sampleRate > maxSampleRate) {
                return Error() << StringPrintf(
                               "sample rate: %f out of range, must be within %f and %f", sampleRate,
                               minSampleRate, maxSampleRate);
                return Error(toInt(StatusCode::INVALID_ARG))
                       << StringPrintf("sample rate: %f out of range, must be within %f and %f",
                                       sampleRate, minSampleRate, maxSampleRate);
            }
            if (!SubscriptionManager::checkSampleRate(sampleRate)) {
                return Error() << "invalid sample rate: " << sampleRate;
                return Error(toInt(StatusCode::INVALID_ARG))
                       << "invalid sample rate: " << sampleRate;
            }
        }

@@ -443,7 +513,8 @@ Result<void> DefaultVehicleHal::checkSubscribeOptions(
        // Non-global property.
        for (int32_t areaId : option.areaIds) {
            if (auto areaConfig = getAreaConfig(propId, areaId, config); areaConfig == nullptr) {
                return Error() << StringPrintf("invalid area ID: %" PRId32 " for prop ID: %" PRId32
                return Error(toInt(StatusCode::INVALID_ARG))
                       << StringPrintf("invalid area ID: %" PRId32 " for prop ID: %" PRId32
                                       ", not listed in config",
                                       areaId, propId);
            }
@@ -457,8 +528,8 @@ ScopedAStatus DefaultVehicleHal::subscribe(const CallbackType& callback,
                                           [[maybe_unused]] int32_t maxSharedMemoryFileCount) {
    // TODO(b/205189110): Use shared memory file count.
    if (auto result = checkSubscribeOptions(options); !result.ok()) {
        ALOGE("subscribe: invalid subscribe options: %s", result.error().message().c_str());
        return toScopedAStatus(result, StatusCode::INVALID_ARG);
        ALOGE("subscribe: invalid subscribe options: %s", getErrorMsg(result).c_str());
        return toScopedAStatus(result);
    }

    std::vector<SubscribeOptions> onChangeSubscriptions;
@@ -513,6 +584,61 @@ IVehicleHardware* DefaultVehicleHal::getHardware() {
    return mVehicleHardware.get();
}

Result<void> DefaultVehicleHal::checkWritePermission(const VehiclePropValue& value) const {
    int32_t propId = value.prop;
    auto result = getConfig(propId);
    if (!result.ok()) {
        return Error(toInt(StatusCode::INVALID_ARG)) << getErrorMsg(result);
    }
    const VehiclePropConfig* config = result.value();

    if (config->access != VehiclePropertyAccess::WRITE &&
        config->access != VehiclePropertyAccess::READ_WRITE) {
        return Error(toInt(StatusCode::ACCESS_DENIED))
               << StringPrintf("Property %" PRId32 " has no write access", propId);
    }
    return {};
}

Result<void> DefaultVehicleHal::checkReadPermission(const VehiclePropValue& value) const {
    int32_t propId = value.prop;
    auto result = getConfig(propId);
    if (!result.ok()) {
        return Error(toInt(StatusCode::INVALID_ARG)) << getErrorMsg(result);
    }
    const VehiclePropConfig* config = result.value();

    if (config->access != VehiclePropertyAccess::READ &&
        config->access != VehiclePropertyAccess::READ_WRITE) {
        return Error(toInt(StatusCode::ACCESS_DENIED))
               << StringPrintf("Property %" PRId32 " has no read access", propId);
    }
    return {};
}

void DefaultVehicleHal::checkHealth(std::weak_ptr<IVehicleHardware> hardware,
                                    std::weak_ptr<SubscriptionManager> subscriptionManager) {
    auto hardwarePtr = hardware.lock();
    if (hardwarePtr == nullptr) {
        ALOGW("the VehicleHardware is destroyed, DefaultVehicleHal is ending");
        return;
    }

    StatusCode status = hardwarePtr->checkHealth();
    if (status != StatusCode::OK) {
        ALOGE("VHAL check health returns non-okay status");
        return;
    }
    std::vector<VehiclePropValue> values = {{
            .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
            .areaId = 0,
            .status = VehiclePropertyStatus::AVAILABLE,
            .value.int64Values = {uptimeMillis()},
    }};
    onPropertyChangeEvent(subscriptionManager, values);
    return;
}

}  // namespace vehicle
}  // namespace automotive
}  // namespace hardware
+111 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/Log.h>
#include <utils/SystemClock.h>

#include <chrono>
#include <list>
@@ -61,6 +62,8 @@ using ::aidl::android::hardware::automotive::vehicle::VehicleAreaWindow;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfigs;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropErrors;
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 ::aidl::android::hardware::automotive::vehicle::VehiclePropValues;
@@ -87,6 +90,10 @@ constexpr int32_t GLOBAL_CONTINUOUS_PROP = 10003 + 0x10000000 + 0x01000000 + 0x0
constexpr int32_t AREA_ON_CHANGE_PROP = 10004 + 0x10000000 + 0x03000000 + 0x00400000;
// VehiclePropertyGroup:SYSTEM,VehicleArea:WINDOW,VehiclePropertyType:INT32
constexpr int32_t AREA_CONTINUOUS_PROP = 10005 + 0x10000000 + 0x03000000 + 0x00400000;
// VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
constexpr int32_t READ_ONLY_PROP = 10006 + 0x10000000 + 0x01000000 + 0x00400000;
// VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
constexpr int32_t WRITE_ONLY_PROP = 10007 + 0x10000000 + 0x01000000 + 0x00400000;

int32_t testInt32VecProp(size_t i) {
    // VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32_VEC
@@ -145,6 +152,15 @@ std::vector<SetValuesInvalidRequestTestCase> getSetValuesInvalidRequestTestCases
                                    .areaId = toInt(VehicleAreaWindow::ROW_1_RIGHT),
                            },
                    .expectedStatus = StatusCode::INVALID_ARG,
            },
            {
                    .name = "no_write_permission",
                    .request =
                            {
                                    .prop = READ_ONLY_PROP,
                                    .value.int32Values = {0},
                            },
                    .expectedStatus = StatusCode::ACCESS_DENIED,
            }};
}

@@ -205,6 +221,7 @@ class DefaultVehicleHalTest : public ::testing::Test {
        for (size_t i = 0; i < 10000; i++) {
            testConfigs.push_back(VehiclePropConfig{
                    .prop = testInt32VecProp(i),
                    .access = VehiclePropertyAccess::READ_WRITE,
                    .areaConfigs =
                            {
                                    {
@@ -218,6 +235,7 @@ class DefaultVehicleHalTest : public ::testing::Test {
        // A property with area config.
        testConfigs.push_back(
                VehiclePropConfig{.prop = INT32_WINDOW_PROP,
                                  .access = VehiclePropertyAccess::READ_WRITE,
                                  .areaConfigs = {{
                                          .areaId = toInt(VehicleAreaWindow::ROW_1_LEFT),
                                          .minInt32Value = 0,
@@ -226,11 +244,13 @@ class DefaultVehicleHalTest : public ::testing::Test {
        // A global on-change property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = GLOBAL_ON_CHANGE_PROP,
                .access = VehiclePropertyAccess::READ_WRITE,
                .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
        });
        // A global continuous property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = GLOBAL_CONTINUOUS_PROP,
                .access = VehiclePropertyAccess::READ_WRITE,
                .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
                .minSampleRate = 0.0,
                .maxSampleRate = 100.0,
@@ -238,6 +258,7 @@ class DefaultVehicleHalTest : public ::testing::Test {
        // A per-area on-change property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = AREA_ON_CHANGE_PROP,
                .access = VehiclePropertyAccess::READ_WRITE,
                .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
                .areaConfigs =
                        {
@@ -257,6 +278,7 @@ class DefaultVehicleHalTest : public ::testing::Test {
        // A per-area continuous property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = AREA_CONTINUOUS_PROP,
                .access = VehiclePropertyAccess::READ_WRITE,
                .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
                .minSampleRate = 0.0,
                .maxSampleRate = 1000.0,
@@ -275,6 +297,28 @@ class DefaultVehicleHalTest : public ::testing::Test {
                                },
                        },
        });
        // A read-only property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = READ_ONLY_PROP,
                .access = VehiclePropertyAccess::READ,
                .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
                .minSampleRate = 0.0,
                .maxSampleRate = 1000.0,
        });
        // A write-only property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = WRITE_ONLY_PROP,
                .access = VehiclePropertyAccess::WRITE,
                .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
                .minSampleRate = 0.0,
                .maxSampleRate = 1000.0,
        });
        // Register the heartbeat event property.
        testConfigs.push_back(VehiclePropConfig{
                .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
                .access = VehiclePropertyAccess::READ,
                .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
        });
        hardware->setPropertyConfigs(testConfigs);
        mHardwarePtr = hardware.get();
        mVhal = ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
@@ -509,6 +553,39 @@ TEST_F(DefaultVehicleHalTest, testGetValuesInvalidLargeParcelableInput) {
    ASSERT_EQ(status.getServiceSpecificError(), toInt(StatusCode::INVALID_ARG));
}

TEST_F(DefaultVehicleHalTest, testGetValuesNoReadPermission) {
    GetValueRequests requests = {
            .sharedMemoryFd = {},
            .payloads =
                    {
                            {
                                    .requestId = 0,
                                    .prop =
                                            {
                                                    .prop = WRITE_ONLY_PROP,
                                            },
                            },
                    },
    };

    auto status = getClient()->getValues(getCallbackClient(), requests);

    ASSERT_TRUE(status.isOk()) << "getValue with no read permission should return okay with error "
                                  "returned from callback"
                               << ", error: " << status.getMessage();
    EXPECT_TRUE(getHardware()->nextGetValueRequests().empty()) << "expect no request to hardware";

    auto maybeResult = getCallback()->nextGetValueResults();
    ASSERT_TRUE(maybeResult.has_value()) << "no results in callback";
    EXPECT_EQ(maybeResult.value().payloads, std::vector<GetValueResult>({
                                                    {
                                                            .requestId = 0,
                                                            .status = StatusCode::ACCESS_DENIED,
                                                    },
                                            }))
            << "expect to get ACCESS_DENIED status if no read permission";
}

TEST_F(DefaultVehicleHalTest, testGetValuesFinishBeforeTimeout) {
    // timeout: 0.1s
    int64_t timeout = 100000000;
@@ -1318,7 +1395,7 @@ INSTANTIATE_TEST_SUITE_P(
            return info.param.name;
        });

TEST_P(SubscribeInvalidOptionsTest, testSubscribeInvalidRequest) {
TEST_P(SubscribeInvalidOptionsTest, testSubscribeInvalidOptions) {
    std::vector<SubscribeOptions> options = {GetParam().option};

    auto status = getClient()->subscribe(getCallbackClient(), options, 0);
@@ -1327,6 +1404,17 @@ TEST_P(SubscribeInvalidOptionsTest, testSubscribeInvalidRequest) {
    ASSERT_EQ(status.getServiceSpecificError(), toInt(StatusCode::INVALID_ARG));
}

TEST_F(DefaultVehicleHalTest, testSubscribeNoReadPermission) {
    std::vector<SubscribeOptions> options = {{
            .propId = WRITE_ONLY_PROP,
    }};

    auto status = getClient()->subscribe(getCallbackClient(), options, 0);

    ASSERT_FALSE(status.isOk()) << "subscribe to a write-only property must fail";
    ASSERT_EQ(status.getServiceSpecificError(), toInt(StatusCode::ACCESS_DENIED));
}

TEST_F(DefaultVehicleHalTest, testUnsubscribeFailure) {
    auto status = getClient()->unsubscribe(getCallbackClient(),
                                           std::vector<int32_t>({GLOBAL_ON_CHANGE_PROP}));
@@ -1335,6 +1423,28 @@ TEST_F(DefaultVehicleHalTest, testUnsubscribeFailure) {
    ASSERT_EQ(status.getServiceSpecificError(), toInt(StatusCode::INVALID_ARG));
}

TEST_F(DefaultVehicleHalTest, testHeartbeatEvent) {
    std::vector<SubscribeOptions> options = {{
            .propId = toInt(VehicleProperty::VHAL_HEARTBEAT),
    }};
    int64_t currentTime = uptimeMillis();
    auto status = getClient()->subscribe(getCallbackClient(), options, 0);

    ASSERT_TRUE(status.isOk()) << "unable to subscribe to heartbeat event: " << status.getMessage();

    // We send out a heartbeat event every 3s, so sleep for 3s.
    std::this_thread::sleep_for(std::chrono::seconds(3));

    auto maybeResults = getCallback()->nextOnPropertyEventResults();
    ASSERT_TRUE(maybeResults.has_value()) << "no results in callback";
    ASSERT_EQ(maybeResults.value().payloads.size(), static_cast<size_t>(1));
    VehiclePropValue gotValue = maybeResults.value().payloads[0];
    ASSERT_EQ(gotValue.prop, toInt(VehicleProperty::VHAL_HEARTBEAT));
    ASSERT_EQ(gotValue.value.int64Values.size(), static_cast<size_t>(1));
    ASSERT_GE(gotValue.value.int64Values[0], currentTime)
            << "expect to get the latest timestamp with the heartbeat event";
}

}  // namespace vehicle
}  // namespace automotive
}  // namespace hardware
+1 −1
Original line number Diff line number Diff line
@@ -185,7 +185,7 @@ TEST_F(SubscriptionManagerTest, testOverrideSubscriptionContinuous) {

    // Theoretically trigger 10 times, but check for at least 9 times to be stable.
    EXPECT_GE(getEvents().size(), static_cast<size_t>(9));
    EXPECT_LE(getEvents().size(), static_cast<size_t>(11));
    EXPECT_LE(getEvents().size(), static_cast<size_t>(15));
}

TEST_F(SubscriptionManagerTest, testSubscribeMultipleAreasContinuous) {