diff --git a/src/app/AttributePersistenceProvider.h b/src/app/AttributePersistenceProvider.h index 2f4403570ff459..de2ae5db8fe2e8 100644 --- a/src/app/AttributePersistenceProvider.h +++ b/src/app/AttributePersistenceProvider.h @@ -16,7 +16,12 @@ #pragma once #include +#include #include +#include +#include +#include +#include #include namespace chip { @@ -56,17 +61,171 @@ class AttributePersistenceProvider * Read an attribute value from non-volatile memory. * * @param [in] aPath the attribute path for the data being persisted. - * @param [in] aMetadata the attribute metadata, as a convenience. + * @param [in] aType the attribute type. + * @param [in] aSize the attribute size. * @param [in,out] aValue where to place the data. The size of the buffer - * will be equal to `size` member of aMetadata. + * will be equal to `size`. * * The data is expected to be in native endianness for * integers and floats. For strings, see the string * representation description in the WriteValue * documentation. */ - virtual CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata, + virtual CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, size_t aSize, MutableByteSpan & aValue) = 0; + + /** + * Get the KVS representation of null for the given type. + * @tparam T The type for which the null representation should be returned. + * @return A value of type T that in the KVS represents null. + */ + template ::value, bool> = true> + static uint8_t GetNullValueForNullableType() + { + return 0xff; + } + + /** + * Get the KVS representation of null for the given type. + * @tparam T The type for which the null representation should be returned. + * @return A value of type T that in the KVS represents null. + */ + template ::value && !std::is_same::value, bool> = true> + static T GetNullValueForNullableType() + { + T nullValue = 0; + nullValue = T(~nullValue); + return nullValue; + } + + /** + * Get the KVS representation of null for the given type. + * @tparam T The type for which the null representation should be returned. + * @return A value of type T that in the KVS represents null. + */ + template ::value && !std::is_same::value, bool> = true> + static T GetNullValueForNullableType() + { + T shiftBit = 1; + return T(shiftBit << ((sizeof(T) * 8) - 1)); + } + + // The following API provides helper functions to simplify the access of commonly used types. + // The API may not be complete. + // Currently implemented write and read types are: uint8_t, uint16_t, uint32_t, unit64_t and + // their nullable varieties, and bool. + + /** + * Write an attribute value of type intX, uintX or bool to non-volatile memory. + * + * @param [in] aPath the attribute path for the data being written. + * @param [in] aValue the data to write. + */ + template ::value, bool> = true> + CHIP_ERROR WriteScalarValue(const ConcreteAttributePath & aPath, T & aValue) + { + uint8_t value[sizeof(T)]; + auto w = Encoding::LittleEndian::BufferWriter(value, sizeof(T)); + w.EndianPut(uint64_t(aValue), sizeof(T)); + + return WriteValue(aPath, ByteSpan(value)); + } + + /** + * Read an attribute of type intX, uintX or bool from non-volatile memory. + * + * @param [in] aPath the attribute path for the data being persisted. + * @param [in,out] aValue where to place the data. + */ + template ::value, bool> = true> + CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, T & aValue) + { + uint8_t attrData[sizeof(T)]; + MutableByteSpan tempVal(attrData); + // **Note** aType in the ReadValue function is only used to check if the value is of a string type. Since this template + // function is only enabled for integral values, we know that this case will not occur, so we can pass the enum of an + // arbitrary integral type. 0x20 is the ZCL enum type for ZCL_INT8U_ATTRIBUTE_TYPE. + auto err = ReadValue(aPath, 0x20, sizeof(T), tempVal); + if (err != CHIP_NO_ERROR) + { + return err; + } + + chip::Encoding::LittleEndian::Reader r(tempVal.data(), tempVal.size()); + r.RawReadLowLevelBeCareful(&aValue); + return r.StatusCode(); + } + + /** + * Write an attribute value of type nullable intX, uintX or bool to non-volatile memory. + * + * @param [in] aPath the attribute path for the data being written. + * @param [in] aValue the data to write. + */ + template ::value, bool> = true> + CHIP_ERROR WriteScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable & aValue) + { + if (aValue.IsNull()) + { + auto nullVal = GetNullValueForNullableType(); + return WriteScalarValue(aPath, nullVal); + } + return WriteScalarValue(aPath, aValue.Value()); + } + + /** + * Read an attribute of type nullable intX, uintX from non-volatile memory. + * + * @param [in] aPath the attribute path for the data being persisted. + * @param [in,out] aValue where to place the data. + */ + template ::value && !std::is_same::value, bool> = true> + CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable & aValue) + { + T tempIntegral; + + CHIP_ERROR err = ReadScalarValue(aPath, tempIntegral); + if (err != CHIP_NO_ERROR) + { + return err; + } + + if (tempIntegral == GetNullValueForNullableType()) + { + aValue.SetNull(); + return CHIP_NO_ERROR; + } + + aValue.SetNonNull(tempIntegral); + return CHIP_NO_ERROR; + } + + /** + * Read an attribute of type nullable bool from non-volatile memory. + * + * @param [in] aPath the attribute path for the data being persisted. + * @param [in,out] aValue where to place the data. + */ + template ::value, bool> = true> + CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable & aValue) + { + uint8_t tempIntegral; + + CHIP_ERROR err = ReadScalarValue(aPath, tempIntegral); + if (err != CHIP_NO_ERROR) + { + return err; + } + + if (tempIntegral == GetNullValueForNullableType()) + { + aValue.SetNull(); + return CHIP_NO_ERROR; + } + + aValue.SetNonNull(tempIntegral); + return CHIP_NO_ERROR; + } }; /** diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 38e534093d1599..30042e6ddfef47 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -88,7 +88,9 @@ static_library("app") { "CommandResponseHelper.h", "CommandSender.cpp", "DefaultAttributePersistenceProvider.cpp", + "DefaultAttributePersistenceProvider.h", "DeferredAttributePersistenceProvider.cpp", + "DeferredAttributePersistenceProvider.h", "DeviceProxy.cpp", "DeviceProxy.h", "EventManagement.cpp", diff --git a/src/app/DefaultAttributePersistenceProvider.cpp b/src/app/DefaultAttributePersistenceProvider.cpp index 5bc7ed9c5cdf7e..164eba6d93fbeb 100644 --- a/src/app/DefaultAttributePersistenceProvider.cpp +++ b/src/app/DefaultAttributePersistenceProvider.cpp @@ -27,8 +27,8 @@ CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttribu VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); // TODO: we may want to have a small cache for values that change a lot, so - // we only write them once a bunch of changes happen or on timer or - // shutdown. + // we only write them once a bunch of changes happen or on timer or + // shutdown. if (!CanCastTo(aValue.size())) { return CHIP_ERROR_BUFFER_TOO_SMALL; @@ -38,8 +38,8 @@ CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttribu aValue.data(), static_cast(aValue.size())); } -CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath, - const EmberAfAttributeMetadata * aMetadata, MutableByteSpan & aValue) +CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, + size_t aSize, MutableByteSpan & aValue) { VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -47,7 +47,7 @@ CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttribut ReturnErrorOnFailure(mStorage->SyncGetKeyValue( DefaultStorageKeyAllocator::AttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId).KeyName(), aValue.data(), size)); - EmberAfAttributeType type = aMetadata->attributeType; + EmberAfAttributeType type = aType; if (emberAfIsStringAttributeType(type)) { // Ensure that we've read enough bytes that we are not ending up with @@ -65,7 +65,7 @@ CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttribut else { // Ensure we got the expected number of bytes for all other types. - VerifyOrReturnError(size == aMetadata->size, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(size == aSize, CHIP_ERROR_INVALID_ARGUMENT); } aValue.reduce_size(size); return CHIP_NO_ERROR; diff --git a/src/app/DefaultAttributePersistenceProvider.h b/src/app/DefaultAttributePersistenceProvider.h index e6397b9d883ed2..ef57cd4fbdf3fd 100644 --- a/src/app/DefaultAttributePersistenceProvider.h +++ b/src/app/DefaultAttributePersistenceProvider.h @@ -50,7 +50,7 @@ class DefaultAttributePersistenceProvider : public AttributePersistenceProvider // AttributePersistenceProvider implementation. CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override; - CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata, + CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, size_t aSize, MutableByteSpan & aValue) override; protected: diff --git a/src/app/DeferredAttributePersistenceProvider.cpp b/src/app/DeferredAttributePersistenceProvider.cpp index 37374e15284689..46c1996b81cc58 100644 --- a/src/app/DeferredAttributePersistenceProvider.cpp +++ b/src/app/DeferredAttributePersistenceProvider.cpp @@ -42,25 +42,25 @@ void DeferredAttribute::Flush(AttributePersistenceProvider & persister) mValue.Release(); } -CHIP_ERROR DeferredAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & path, const ByteSpan & value) +CHIP_ERROR DeferredAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) { for (DeferredAttribute & da : mDeferredAttributes) { - if (da.Matches(path)) + if (da.Matches(aPath)) { - ReturnErrorOnFailure(da.PrepareWrite(System::SystemClock().GetMonotonicTimestamp() + mWriteDelay, value)); + ReturnErrorOnFailure(da.PrepareWrite(System::SystemClock().GetMonotonicTimestamp() + mWriteDelay, aValue)); FlushAndScheduleNext(); return CHIP_NO_ERROR; } } - return mPersister.WriteValue(path, value); + return mPersister.WriteValue(aPath, aValue); } -CHIP_ERROR DeferredAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & path, - const EmberAfAttributeMetadata * metadata, MutableByteSpan & value) +CHIP_ERROR DeferredAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, + size_t aSize, MutableByteSpan & aValue) { - return mPersister.ReadValue(path, metadata, value); + return mPersister.ReadValue(aPath, aType, aSize, aValue); } void DeferredAttributePersistenceProvider::FlushAndScheduleNext() diff --git a/src/app/DeferredAttributePersistenceProvider.h b/src/app/DeferredAttributePersistenceProvider.h index 2482d5c9076b33..43f6653e08408c 100644 --- a/src/app/DeferredAttributePersistenceProvider.h +++ b/src/app/DeferredAttributePersistenceProvider.h @@ -66,9 +66,9 @@ class DeferredAttributePersistenceProvider : public AttributePersistenceProvider * * For other attributes, immediately pass the write operation to the decorated persister. */ - CHIP_ERROR WriteValue(const ConcreteAttributePath & path, const ByteSpan & value) override; - CHIP_ERROR ReadValue(const ConcreteAttributePath & path, const EmberAfAttributeMetadata * metadata, - MutableByteSpan & value) override; + CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override; + CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, size_t aSize, + MutableByteSpan & aValue) override; private: void FlushAndScheduleNext(); diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index bda2852a949ece..22f10f9234cdcb 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -112,6 +112,7 @@ chip_test_suite("tests") { test_sources = [ "TestAclEvent.cpp", "TestAttributePathExpandIterator.cpp", + "TestAttributePersistenceProvider.cpp", "TestAttributeValueDecoder.cpp", "TestAttributeValueEncoder.cpp", "TestBindingTable.cpp", diff --git a/src/app/tests/TestAttributePersistenceProvider.cpp b/src/app/tests/TestAttributePersistenceProvider.cpp new file mode 100644 index 00000000000000..045ef77be69d9f --- /dev/null +++ b/src/app/tests/TestAttributePersistenceProvider.cpp @@ -0,0 +1,405 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * This file implements unit tests for AttributePersistenceProvider + * + */ + +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::TLV; + +const ConcreteAttributePath TestConcretePath = ConcreteAttributePath(1, 1, 1); + +namespace { + +/** + * Set up the test suite. + */ +int Test_Setup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + VerifyOrReturnError(error == CHIP_NO_ERROR, FAILURE); + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +int Test_Teardown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +/** + * Tests the storage and retrival of data from the KVS as ByteSpan + */ +void TestStorageAndRetrivalByteSpans(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + DefaultAttributePersistenceProvider persistenceProvider; + + // Init + ChipError err = persistenceProvider.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Store ByteSpan of size 1 + uint8_t valueArray[1] = { 0x42 }; + ByteSpan value(valueArray); + err = persistenceProvider.WriteValue(TestConcretePath, value); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + uint8_t getArray[1]; + MutableByteSpan valueReadBack(getArray); + err = persistenceProvider.ReadValue(TestConcretePath, 0x20, sizeof(getArray), valueReadBack); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, std::equal(valueReadBack.begin(), valueReadBack.end(), value.begin(), value.end())); + + // Finishing + persistenceProvider.Shutdown(); +} + +/** + * Helper to test the storage and retrival of various types from persistent storage. + * @tparam T The type of the value to store and retrieve + * @param testValue The test value to store and retrieve + */ +template +void testHelperStorageAndRetrivalScalarValues(nlTestSuite * inSuite, DefaultAttributePersistenceProvider & persistenceProvider, + T testValue) +{ + CHIP_ERROR err = persistenceProvider.WriteScalarValue(TestConcretePath, testValue); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + T valueReadBack = 0; + err = persistenceProvider.ReadScalarValue(TestConcretePath, valueReadBack); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, valueReadBack == testValue); +} + +/** + * Helper to test the storage and retrival of various nullable types from persistent storage. + * @tparam T The type of the value to store and retrieve + * @param testValue The test value to store and retrieve + */ +template +void testHelperStorageAndRetrivalScalarValues(nlTestSuite * inSuite, DefaultAttributePersistenceProvider & persistenceProvider, + DataModel::Nullable testValue) +{ + CHIP_ERROR err = persistenceProvider.WriteScalarValue(TestConcretePath, testValue); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + DataModel::Nullable valueReadBack(0); + err = persistenceProvider.ReadScalarValue(TestConcretePath, valueReadBack); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, valueReadBack == testValue); +} + +/** + * Tests the storage and retrival of data from the KVS of types bool, uint8_t, uint16_t, uint32_t, uint64_t. + */ +void TestStorageAndRetrivalScalarValues(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + DefaultAttributePersistenceProvider persistenceProvider; + + // Init + CHIP_ERROR err = persistenceProvider.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Test bool + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, bool(true)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, bool(false)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, bool(true)); + + // Test uint8_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint8_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint8_t(42)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint8_t(0xff)); + + // Test uint16_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint16_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint16_t(0x0101)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint16_t(0xffff)); + + // Test uint32_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint32_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint32_t(0x01ffff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint32_t(0xffffffff)); + + // Test uint64_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint64_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint64_t(0x0100000001)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, uint64_t(0xffffffffffffffff)); + + // Finishing + persistenceProvider.Shutdown(); +} + +/** + * Tests the storage and retrival of data from the KVS of types int8_t, int16_t, int32_t, int64_t. + */ +void TestStorageAndRetrivalSignedScalarValues(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + DefaultAttributePersistenceProvider persistenceProvider; + + // Init + CHIP_ERROR err = persistenceProvider.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Test int8_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int8_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int8_t(42)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int8_t(-127)); + + // Test int16_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int16_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int16_t(0x7fff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int16_t(0x8000)); + + // Test int32_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int32_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int32_t(0x7fffffff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int32_t(0x80000000)); + + // Test int64_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int64_t(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int64_t(0x7fffffffffffffff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, int64_t(0x8000000000000000)); + + // Finishing + persistenceProvider.Shutdown(); +} + +void TestGetNullFunctions(nlTestSuite * inSuite, void * inContext) +{ + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == uint8_t(0xff)); + + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == uint8_t(0xff)); + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == uint16_t(0xffff)); + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == uint32_t(0xffffffff)); + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == uint64_t(0xffffffffffffffff)); + + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == int8_t(0x80)); + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == int16_t(0x8000)); + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == int32_t(0x80000000)); + NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType() == int64_t(0x8000000000000000)); +} + +/** + * Tests the storage and retrival of data from the KVS of DataModel::Nullable types bool, uint8_t, uint16_t, uint32_t, uint64_t. + */ +void TestStorageAndRetrivalNullableScalarValues(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + DefaultAttributePersistenceProvider persistenceProvider; + + // Init + CHIP_ERROR err = persistenceProvider.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Test bool + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(true)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(false)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(true)); + auto nullValBool = DataModel::Nullable(); + nullValBool.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullValBool); + + // Test uint8_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(42)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0xfe)); + auto nullVal8 = DataModel::Nullable(); + nullVal8.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal8); + + // Test uint16_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0x0101)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0xfffe)); + auto nullVal16 = DataModel::Nullable(); + nullVal16.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal16); + + // Test uint32_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0x01ffff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0xfffffffe)); + auto nullVal32 = DataModel::Nullable(); + nullVal32.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal32); + + // Test uint64_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0x0100000001)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0xfffffffffffffffe)); + auto nullVal64 = DataModel::Nullable(); + nullVal64.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal64); + + // Finishing + persistenceProvider.Shutdown(); +} + +/** + * Tests the storage and retrival of data from the KVS of DataModel::Nullable types int8_t, int16_t, int32_t, int64_t. + */ +void TestStorageAndRetrivalSignedNullableScalarValues(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + DefaultAttributePersistenceProvider persistenceProvider; + + // Init + CHIP_ERROR err = persistenceProvider.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Test int8_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(42)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(-127)); + auto nullVal8 = DataModel::Nullable(); + nullVal8.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal8); + + // Test int16_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0x7fff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(-0x7fff)); + auto nullVal16 = DataModel::Nullable(); + nullVal16.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal16); + + // Test int32_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0x7fffffff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(-0x7fffffff)); + auto nullVal32 = DataModel::Nullable(); + nullVal32.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal32); + + // Test int64_t + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(0x7fffffffffffffff)); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, DataModel::Nullable(-0x7fffffffffffffff)); + auto nullVal64 = DataModel::Nullable(); + nullVal64.SetNull(); + testHelperStorageAndRetrivalScalarValues(inSuite, persistenceProvider, nullVal64); + + // Finishing + persistenceProvider.Shutdown(); +} + +/** + * Test that the correct error is given when trying to read a value with a buffer that's too small. + */ +void TestBufferTooSmallErrors(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + DefaultAttributePersistenceProvider persistenceProvider; + + // Init + CHIP_ERROR err = persistenceProvider.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Store large data + uint8_t valueArray[9] = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42 }; + ByteSpan value(valueArray); + err = persistenceProvider.WriteValue(TestConcretePath, value); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Confirm the daya is there + uint8_t getArray[9]; + MutableByteSpan valueReadBack(getArray); + err = persistenceProvider.ReadValue(TestConcretePath, 0x20, sizeof(getArray), valueReadBack); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, std::equal(valueReadBack.begin(), valueReadBack.end(), value.begin(), value.end())); + + // Fail to get data as ByteSpace of size 0 + uint8_t getArray0[0]; + MutableByteSpan valueReadBackByteSpan0(getArray0, 0); + err = persistenceProvider.ReadValue(TestConcretePath, 0x20, 1, valueReadBackByteSpan0); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Fail to get data as ByteSpace of size > 0 but < required + uint8_t getArray8[8]; + MutableByteSpan valueReadBackByteSpan8(getArray8, sizeof(getArray8)); + err = persistenceProvider.ReadValue(TestConcretePath, 0x20, 1, valueReadBackByteSpan8); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Fail to get value as uint8_t + uint8_t valueReadBack8; + err = persistenceProvider.ReadScalarValue(TestConcretePath, valueReadBack8); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Fail to get value as uint16_t + uint16_t valueReadBack16; + err = persistenceProvider.ReadScalarValue(TestConcretePath, valueReadBack16); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Fail to get value as uint32_t + uint32_t valueReadBack32; + err = persistenceProvider.ReadScalarValue(TestConcretePath, valueReadBack32); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Fail to get value as uint64_t + uint64_t valueReadBack64; + err = persistenceProvider.ReadScalarValue(TestConcretePath, valueReadBack64); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + + // Finishing + persistenceProvider.Shutdown(); +} + +} // anonymous namespace + +namespace { +const nlTest sTests[] = { + NL_TEST_DEF("Storage and retrival of ByteSpans", TestStorageAndRetrivalByteSpans), + NL_TEST_DEF("Storage and retrival of unsigned scalar values", TestStorageAndRetrivalScalarValues), + NL_TEST_DEF("Storage and retrival of signed scalar values", TestStorageAndRetrivalSignedScalarValues), + NL_TEST_DEF("Check GetNullValueForNullableType return values", TestGetNullFunctions), + NL_TEST_DEF("Storage and retrival of unsigned nullable scalar values", TestStorageAndRetrivalNullableScalarValues), + NL_TEST_DEF("Storage and retrival of signed nullable scalar values", TestStorageAndRetrivalSignedNullableScalarValues), + NL_TEST_DEF("Small buffer errors", TestBufferTooSmallErrors), + NL_TEST_SENTINEL() +}; +} + +int TestAttributePersistenceProvider() +{ + nlTestSuite theSuite = { "AttributePersistenceProvider", &sTests[0], Test_Setup, Test_Teardown }; + + nlTestRunner(&theSuite, nullptr); + + return (nlTestRunnerStats(&theSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestAttributePersistenceProvider) diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index 13a856b043fcab..34e97069256bc6 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -1215,8 +1215,9 @@ void emAfLoadAttributeDefaults(EndpointId endpoint, bool ignoreStorage, Optional { VerifyOrDie(attrStorage && "Attribute persistence needs a persistence provider"); MutableByteSpan bytes(attrData); - CHIP_ERROR err = attrStorage->ReadValue( - app::ConcreteAttributePath(de->endpoint, cluster->clusterId, am->attributeId), am, bytes); + CHIP_ERROR err = + attrStorage->ReadValue(app::ConcreteAttributePath(de->endpoint, cluster->clusterId, am->attributeId), + am->attributeType, am->size, bytes); if (err == CHIP_NO_ERROR) { ptr = attrData; diff --git a/src/credentials/tests/TestPersistentStorageOpCertStore.cpp b/src/credentials/tests/TestPersistentStorageOpCertStore.cpp index d50d9f8cceb746..12d4ac8b992af6 100644 --- a/src/credentials/tests/TestPersistentStorageOpCertStore.cpp +++ b/src/credentials/tests/TestPersistentStorageOpCertStore.cpp @@ -880,7 +880,7 @@ int TestPersistentStorageOpCertStore() { nlTestSuite theSuite = { "PersistentStorageOpCertStore tests", &sTests[0], Test_Setup, Test_Teardown }; - // Run test suite againt one context. + // Run test suite against one context. nlTestRunner(&theSuite, nullptr); return nlTestRunnerStats(&theSuite); }