Reverted the AttributePersistenceProvider and added a SafeAttributePersistenceProvider (#28302)
* Renamed AttributePersistenceProvider to SafeAttributePersistenceProvider. Reverted to the previous AttributePersistenceProvider. Updated the tests and examples that used the AttributePersistenceProvider.
* Restyled by whitespace
* Restyled by clang-format
* Restyled by gn
* Fixed the key generated for safe attributes.
Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com>
* Apply documentation suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Removed unused aPath and import.
* Fixed some docs. Removed size from the SafeAttributePersistenceProvider::SafeReadValue method.
* Replaced the aType input var from the SafeAttributePersistenceProvider::SafeReadValue method with documentation on the expectet types.
* Replaced the SafeAttributePersistenceProvider::GetNullValueForNullableType logic with that used in NumericAttributeTraits::GetNullValue. Mode the GetNullValueForNullableType methods private and commented out the relevant unit tests.
* Restyled by clang-format
* Replaced the SafeAttributePersistenceProvider::GetNullValueForNullableType methods with NumericAttributeTraits.
* Apply documentation suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Restyled by clang-format
* Refactored SafeAttributePersistenceProvider reducing the number of templated functions.
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Fixed minor bug in suggestions.
* Restyled by whitespace
---------
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/AttributePersistenceProvider.h b/src/app/AttributePersistenceProvider.h
index de2ae5d..5275829 100644
--- a/src/app/AttributePersistenceProvider.h
+++ b/src/app/AttributePersistenceProvider.h
@@ -16,19 +16,17 @@
#pragma once
#include <app/ConcreteAttributePath.h>
-#include <app/data-model/Nullable.h>
#include <app/util/attribute-metadata.h>
-#include <cstring>
-#include <inttypes.h>
-#include <lib/support/BufferReader.h>
-#include <lib/support/BufferWriter.h>
#include <lib/support/Span.h>
namespace chip {
namespace app {
/**
- * Interface for persisting attribute values.
+ * Interface for persisting attribute values. This will write attributes in storage with platform endianness for scalars
+ * and uses a different key space from SafeAttributePersistenceProvider.
+ * When storing cluster attributes that are managed via the AttributeAccessInterface, it is recommended to
+ * use SafeAttributePersistenceProvider.
*/
class AttributePersistenceProvider
@@ -61,171 +59,17 @@
* Read an attribute value from non-volatile memory.
*
* @param [in] aPath the attribute path for the data being persisted.
- * @param [in] aType the attribute type.
- * @param [in] aSize the attribute size.
+ * @param [in] aMetadata the attribute metadata, as a convenience.
* @param [in,out] aValue where to place the data. The size of the buffer
- * will be equal to `size`.
+ * will be equal to `size` member of aMetadata.
*
* 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, EmberAfAttributeType aType, size_t aSize,
+ virtual CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
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 <typename T, std::enable_if_t<std::is_same<bool, T>::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 <typename T, std::enable_if_t<std::is_unsigned<T>::value && !std::is_same<bool, T>::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 <typename T, std::enable_if_t<std::is_signed<T>::value && !std::is_same<bool, T>::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 <typename T, std::enable_if_t<std::is_integral<T>::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 <typename T, std::enable_if_t<std::is_integral<T>::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 <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
- CHIP_ERROR WriteScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable<T> & aValue)
- {
- if (aValue.IsNull())
- {
- auto nullVal = GetNullValueForNullableType<T>();
- 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 <typename T, std::enable_if_t<std::is_integral<T>::value && !std::is_same<bool, T>::value, bool> = true>
- CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable<T> & aValue)
- {
- T tempIntegral;
-
- CHIP_ERROR err = ReadScalarValue(aPath, tempIntegral);
- if (err != CHIP_NO_ERROR)
- {
- return err;
- }
-
- if (tempIntegral == GetNullValueForNullableType<T>())
- {
- 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 <typename T, std::enable_if_t<std::is_same<bool, T>::value, bool> = true>
- CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable<T> & aValue)
- {
- uint8_t tempIntegral;
-
- CHIP_ERROR err = ReadScalarValue(aPath, tempIntegral);
- if (err != CHIP_NO_ERROR)
- {
- return err;
- }
-
- if (tempIntegral == GetNullValueForNullableType<T>())
- {
- 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 aade188..83ffe63 100644
--- a/src/app/BUILD.gn
+++ b/src/app/BUILD.gn
@@ -183,6 +183,7 @@
"ReadHandler.cpp",
"RequiredPrivilege.cpp",
"RequiredPrivilege.h",
+ "SafeAttributePersistenceProvider.h",
"StatusResponse.cpp",
"StatusResponse.h",
"SubscriptionResumptionStorage.h",
diff --git a/src/app/DefaultAttributePersistenceProvider.cpp b/src/app/DefaultAttributePersistenceProvider.cpp
index 164eba6..3649770 100644
--- a/src/app/DefaultAttributePersistenceProvider.cpp
+++ b/src/app/DefaultAttributePersistenceProvider.cpp
@@ -22,7 +22,7 @@
namespace chip {
namespace app {
-CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue)
+CHIP_ERROR DefaultAttributePersistenceProvider::InternalWriteValue(const StorageKeyName & aKey, const ByteSpan & aValue)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
@@ -33,20 +33,16 @@
{
return CHIP_ERROR_BUFFER_TOO_SMALL;
}
- return mStorage->SyncSetKeyValue(
- DefaultStorageKeyAllocator::AttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId).KeyName(),
- aValue.data(), static_cast<uint16_t>(aValue.size()));
+ return mStorage->SyncSetKeyValue(aKey.KeyName(), aValue.data(), static_cast<uint16_t>(aValue.size()));
}
-CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType,
- size_t aSize, MutableByteSpan & aValue)
+CHIP_ERROR DefaultAttributePersistenceProvider::InternalReadValue(const StorageKeyName & aKey, EmberAfAttributeType aType,
+ size_t aSize, MutableByteSpan & aValue)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
uint16_t size = static_cast<uint16_t>(min(aValue.size(), static_cast<size_t>(UINT16_MAX)));
- ReturnErrorOnFailure(mStorage->SyncGetKeyValue(
- DefaultStorageKeyAllocator::AttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId).KeyName(),
- aValue.data(), size));
+ ReturnErrorOnFailure(mStorage->SyncGetKeyValue(aKey.KeyName(), aValue.data(), size));
EmberAfAttributeType type = aType;
if (emberAfIsStringAttributeType(type))
{
@@ -71,12 +67,45 @@
return CHIP_NO_ERROR;
}
+CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue)
+{
+ return InternalWriteValue(DefaultStorageKeyAllocator::AttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
+ aValue);
+}
+
+CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath,
+ const EmberAfAttributeMetadata * aMetadata, MutableByteSpan & aValue)
+{
+ return InternalReadValue(DefaultStorageKeyAllocator::AttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
+ aMetadata->attributeType, aMetadata->size, aValue);
+}
+
+CHIP_ERROR DefaultAttributePersistenceProvider::SafeWriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue)
+{
+ return InternalWriteValue(
+ DefaultStorageKeyAllocator::SafeAttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId), aValue);
+}
+
+CHIP_ERROR DefaultAttributePersistenceProvider::SafeReadValue(const ConcreteAttributePath & aPath, MutableByteSpan & aValue)
+{
+ return InternalReadValue(
+ DefaultStorageKeyAllocator::SafeAttributeValue(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId), 0x20,
+ aValue.size(), aValue);
+}
+
namespace {
AttributePersistenceProvider * gAttributeSaver = nullptr;
} // anonymous namespace
+/**
+ * Gets the global attribute saver.
+ *
+ * Note: When storing cluster attributes that are managed via AttributeAccessInterface, it is recommended to
+ * use SafeAttributePersistenceProvider. See AttributePersistenceProvider and SafeAttributePersistenceProvider
+ * class documentation for more information.
+ */
AttributePersistenceProvider * GetAttributePersistenceProvider()
{
return gAttributeSaver;
@@ -90,5 +119,27 @@
}
}
+namespace {
+
+SafeAttributePersistenceProvider * gSafeAttributeSaver = nullptr;
+
+} // anonymous namespace
+
+/**
+ * Gets the global attribute safe saver.
+ */
+SafeAttributePersistenceProvider * GetSafeAttributePersistenceProvider()
+{
+ return gSafeAttributeSaver;
+}
+
+void SetSafeAttributePersistenceProvider(SafeAttributePersistenceProvider * aProvider)
+{
+ if (aProvider != nullptr)
+ {
+ gSafeAttributeSaver = aProvider;
+ }
+}
+
} // namespace app
} // namespace chip
diff --git a/src/app/DefaultAttributePersistenceProvider.h b/src/app/DefaultAttributePersistenceProvider.h
index ef57cd4..4db77e8 100644
--- a/src/app/DefaultAttributePersistenceProvider.h
+++ b/src/app/DefaultAttributePersistenceProvider.h
@@ -16,6 +16,7 @@
#pragma once
#include <app/AttributePersistenceProvider.h>
+#include <app/SafeAttributePersistenceProvider.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
@@ -30,7 +31,7 @@
* of this class, since it can't be constructed automatically without knowing
* what PersistentStorageDelegate is to be used.
*/
-class DefaultAttributePersistenceProvider : public AttributePersistenceProvider
+class DefaultAttributePersistenceProvider : public AttributePersistenceProvider, public SafeAttributePersistenceProvider
{
public:
DefaultAttributePersistenceProvider() {}
@@ -50,11 +51,19 @@
// AttributePersistenceProvider implementation.
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override;
- CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, size_t aSize,
+ CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
MutableByteSpan & aValue) override;
+ // SafeAttributePersistenceProvider implementation.
+ CHIP_ERROR SafeWriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override;
+ CHIP_ERROR SafeReadValue(const ConcreteAttributePath & aPath, MutableByteSpan & aValue) override;
+
protected:
PersistentStorageDelegate * mStorage;
+
+private:
+ CHIP_ERROR InternalWriteValue(const StorageKeyName & aKey, const ByteSpan & aValue);
+ CHIP_ERROR InternalReadValue(const StorageKeyName & aKey, EmberAfAttributeType aType, size_t aSize, MutableByteSpan & aValue);
};
} // namespace app
diff --git a/src/app/DeferredAttributePersistenceProvider.cpp b/src/app/DeferredAttributePersistenceProvider.cpp
index 46c1996..62d5c44 100644
--- a/src/app/DeferredAttributePersistenceProvider.cpp
+++ b/src/app/DeferredAttributePersistenceProvider.cpp
@@ -57,10 +57,10 @@
return mPersister.WriteValue(aPath, aValue);
}
-CHIP_ERROR DeferredAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType,
- size_t aSize, MutableByteSpan & aValue)
+CHIP_ERROR DeferredAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath,
+ const EmberAfAttributeMetadata * aMetadata, MutableByteSpan & aValue)
{
- return mPersister.ReadValue(aPath, aType, aSize, aValue);
+ return mPersister.ReadValue(aPath, aMetadata, aValue);
}
void DeferredAttributePersistenceProvider::FlushAndScheduleNext()
diff --git a/src/app/DeferredAttributePersistenceProvider.h b/src/app/DeferredAttributePersistenceProvider.h
index 43f6653..c1023e9 100644
--- a/src/app/DeferredAttributePersistenceProvider.h
+++ b/src/app/DeferredAttributePersistenceProvider.h
@@ -67,7 +67,7 @@
* For other attributes, immediately pass the write operation to the decorated persister.
*/
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override;
- CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, EmberAfAttributeType aType, size_t aSize,
+ CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
MutableByteSpan & aValue) override;
private:
diff --git a/src/app/SafeAttributePersistenceProvider.h b/src/app/SafeAttributePersistenceProvider.h
new file mode 100644
index 0000000..c6cba4e
--- /dev/null
+++ b/src/app/SafeAttributePersistenceProvider.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2021 Project CHIP Authors
+ *
+ * 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.
+ */
+#pragma once
+
+#include <app/ConcreteAttributePath.h>
+#include <app/data-model/Nullable.h>
+#include <app/util/attribute-metadata.h>
+#include <cstring>
+#include <inttypes.h>
+#include <lib/support/BufferReader.h>
+#include <lib/support/BufferWriter.h>
+#include <lib/support/Span.h>
+
+namespace chip {
+namespace app {
+
+/**
+ * Interface for persisting attribute values. This will always write attributes in storage as little-endian
+ * and uses a different key space from AttributePersistenceProvider.
+ */
+
+class SafeAttributePersistenceProvider
+{
+public:
+ virtual ~SafeAttributePersistenceProvider() = default;
+ SafeAttributePersistenceProvider() = default;
+
+ // 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 <typename T, std::enable_if_t<std::is_integral<T>::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 SafeWriteValue(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 <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+ CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, T & aValue)
+ {
+ uint8_t attrData[sizeof(T)];
+ MutableByteSpan tempVal(attrData);
+ auto err = SafeReadValue(aPath, 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 to non-volatile memory.
+ *
+ * @param [in] aPath the attribute path for the data being written.
+ * @param [in] aValue the data to write.
+ */
+ template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+ CHIP_ERROR WriteScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable<T> & aValue)
+ {
+ typename NumericAttributeTraits<T>::StorageType storageValue;
+ if (aValue.IsNull())
+ {
+ NumericAttributeTraits<T>::SetNull(storageValue);
+ }
+ else
+ {
+ NumericAttributeTraits<T>::WorkingToStorage(aValue.Value(), storageValue);
+ }
+ return WriteScalarValue(aPath, storageValue);
+ }
+
+ /**
+ * 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 <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+ CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable<T> & aValue)
+ {
+ typename NumericAttributeTraits<T>::StorageType storageValue;
+ CHIP_ERROR err = ReadScalarValue(aPath, storageValue);
+ if (err != CHIP_NO_ERROR)
+ {
+ return err;
+ }
+
+ if (NumericAttributeTraits<T>::IsNullValue(storageValue))
+ {
+ aValue.SetNull();
+ return CHIP_NO_ERROR;
+ }
+
+ // Consider checking CanRepresentValue here, so we don't produce invalid data
+ // if the storage hands us invalid values.
+ aValue.SetNonNull(NumericAttributeTraits<T>::StorageToWorking(storageValue));
+ return CHIP_NO_ERROR;
+ }
+
+protected:
+ /**
+ * Write an attribute value from the attribute store (i.e. not a struct or
+ * list) to non-volatile memory.
+ *
+ * @param [in] aPath the attribute path for the data being written.
+ * @param [in] aValue the data to write. The value should be stored as-is.
+ */
+ virtual CHIP_ERROR SafeWriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) = 0;
+
+ /**
+ * Read an attribute value from non-volatile memory.
+ * It can be assumed that this method will never be called upon to read
+ * an attribute of type string or long-string.
+ *
+ * @param [in] aPath the attribute path for the data being persisted.
+ * @param [in,out] aValue where to place the data. The size of the buffer
+ * will be equal to `aValue.size()`. The callee is expected to adjust
+ * aValue's size to the actual number of bytes read.
+ */
+ virtual CHIP_ERROR SafeReadValue(const ConcreteAttributePath & aPath, MutableByteSpan & aValue) = 0;
+};
+
+/**
+ * Instance getter for the global SafeAttributePersistenceProvider.
+ *
+ * Callers have to externally synchronize usage of this function.
+ *
+ * @return The global SafeAttributePersistenceProvider. This must never be null.
+ */
+SafeAttributePersistenceProvider * GetSafeAttributePersistenceProvider();
+
+/**
+ * Instance setter for the global SafeAttributePersistenceProvider.
+ *
+ * Callers have to externally synchronize usage of this function.
+ *
+ * If the `provider` is nullptr, the value is not changed.
+ *
+ * @param[in] aProvider the SafeAttributePersistenceProvider implementation to use.
+ */
+void SetSafeAttributePersistenceProvider(SafeAttributePersistenceProvider * aProvider);
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/mode-base-server/mode-base-server.cpp b/src/app/clusters/mode-base-server/mode-base-server.cpp
index f4ec6a4..d2e56f1 100644
--- a/src/app/clusters/mode-base-server/mode-base-server.cpp
+++ b/src/app/clusters/mode-base-server/mode-base-server.cpp
@@ -17,8 +17,8 @@
*/
#include <app-common/zap-generated/attributes/Accessors.h>
-#include <app/AttributePersistenceProvider.h>
#include <app/InteractionModelEngine.h>
+#include <app/SafeAttributePersistenceProvider.h>
#include <app/clusters/mode-base-server/mode-base-server.h>
#include <app/clusters/on-off-server/on-off-server.h>
#include <app/reporting/reporting.h>
@@ -60,7 +60,7 @@
{
// Load Current Mode
uint8_t tempCurrentMode;
- CHIP_ERROR err = GetAttributePersistenceProvider()->ReadScalarValue(
+ CHIP_ERROR err = GetSafeAttributePersistenceProvider()->ReadScalarValue(
ConcreteAttributePath(mEndpointId, mClusterId, Attributes::CurrentMode::Id), tempCurrentMode);
if (err == CHIP_NO_ERROR)
{
@@ -83,7 +83,7 @@
// Load Start-Up Mode
DataModel::Nullable<uint8_t> tempStartUpMode;
- err = GetAttributePersistenceProvider()->ReadScalarValue(
+ err = GetSafeAttributePersistenceProvider()->ReadScalarValue(
ConcreteAttributePath(mEndpointId, mClusterId, Attributes::StartUpMode::Id), tempStartUpMode);
if (err == CHIP_NO_ERROR)
{
@@ -111,8 +111,8 @@
// Load On Mode
DataModel::Nullable<uint8_t> tempOnMode;
- err = GetAttributePersistenceProvider()->ReadScalarValue(ConcreteAttributePath(mEndpointId, mClusterId, Attributes::OnMode::Id),
- tempOnMode);
+ err = GetSafeAttributePersistenceProvider()->ReadScalarValue(
+ ConcreteAttributePath(mEndpointId, mClusterId, Attributes::OnMode::Id), tempOnMode);
if (err == CHIP_NO_ERROR)
{
Status status = UpdateOnMode(tempOnMode);
@@ -398,7 +398,7 @@
{
// Write new value to persistent storage.
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, mClusterId, Attributes::CurrentMode::Id);
- GetAttributePersistenceProvider()->WriteScalarValue(path, mCurrentMode);
+ GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mCurrentMode);
MatterReportingAttributeChangeCallback(path);
}
return Protocols::InteractionModel::Status::Success;
@@ -419,7 +419,7 @@
{
// Write new value to persistent storage.
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, mClusterId, Attributes::StartUpMode::Id);
- GetAttributePersistenceProvider()->WriteScalarValue(path, mStartUpMode);
+ GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mStartUpMode);
MatterReportingAttributeChangeCallback(path);
}
return Protocols::InteractionModel::Status::Success;
@@ -440,7 +440,7 @@
{
// Write new value to persistent storage.
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, mClusterId, Attributes::OnMode::Id);
- GetAttributePersistenceProvider()->WriteScalarValue(path, mOnMode);
+ GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mOnMode);
MatterReportingAttributeChangeCallback(path);
}
return Protocols::InteractionModel::Status::Success;
diff --git a/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp b/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp
index c568746..8b2f9ea 100644
--- a/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp
+++ b/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp
@@ -17,11 +17,11 @@
*/
#include <app/AttributeAccessInterface.h>
-#include <app/AttributePersistenceProvider.h>
#include <app/CommandHandlerInterface.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteClusterPath.h>
#include <app/InteractionModelEngine.h>
+#include <app/SafeAttributePersistenceProvider.h>
#include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
#include <app/clusters/resource-monitoring-server/resource-monitoring-server.h>
#include <app/data-model/Nullable.h>
@@ -116,7 +116,7 @@
mLastChangedTime = aNewLastChangedTime;
if (mLastChangedTime != oldLastchangedTime)
{
- chip::app::GetAttributePersistenceProvider()->WriteScalarValue(
+ chip::app::GetSafeAttributePersistenceProvider()->WriteScalarValue(
ConcreteAttributePath(mEndpointId, mClusterId, Attributes::LastChangedTime::Id), mLastChangedTime);
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::LastChangedTime::Id);
}
@@ -351,7 +351,7 @@
void Instance::LoadPersistentAttributes()
{
- CHIP_ERROR err = chip::app::GetAttributePersistenceProvider()->ReadScalarValue(
+ CHIP_ERROR err = chip::app::GetSafeAttributePersistenceProvider()->ReadScalarValue(
ConcreteAttributePath(mEndpointId, mClusterId, Attributes::LastChangedTime::Id), mLastChangedTime);
if (err == CHIP_NO_ERROR)
{
diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp
index 96847ac..a4c8b95 100644
--- a/src/app/server/Server.cpp
+++ b/src/app/server/Server.cpp
@@ -150,6 +150,7 @@
// handler.
SuccessOrExit(err = mAttributePersister.Init(mDeviceStorage));
SetAttributePersistenceProvider(&mAttributePersister);
+ SetSafeAttributePersistenceProvider(&mAttributePersister);
{
FabricTable::InitParams fabricTableInitParams;
diff --git a/src/app/tests/TestAttributePersistenceProvider.cpp b/src/app/tests/TestAttributePersistenceProvider.cpp
index 045ef77..9c75715 100644
--- a/src/app/tests/TestAttributePersistenceProvider.cpp
+++ b/src/app/tests/TestAttributePersistenceProvider.cpp
@@ -70,12 +70,12 @@
// Store ByteSpan of size 1
uint8_t valueArray[1] = { 0x42 };
ByteSpan value(valueArray);
- err = persistenceProvider.WriteValue(TestConcretePath, value);
+ err = persistenceProvider.SafeWriteValue(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);
+ err = persistenceProvider.SafeReadValue(TestConcretePath, valueReadBack);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, std::equal(valueReadBack.begin(), valueReadBack.end(), value.begin(), value.end()));
@@ -198,21 +198,6 @@
persistenceProvider.Shutdown();
}
-void TestGetNullFunctions(nlTestSuite * inSuite, void * inContext)
-{
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<bool>() == uint8_t(0xff));
-
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<uint8_t>() == uint8_t(0xff));
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<uint16_t>() == uint16_t(0xffff));
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<uint32_t>() == uint32_t(0xffffffff));
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<uint64_t>() == uint64_t(0xffffffffffffffff));
-
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<int8_t>() == int8_t(0x80));
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<int16_t>() == int16_t(0x8000));
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<int32_t>() == int32_t(0x80000000));
- NL_TEST_ASSERT(inSuite, AttributePersistenceProvider::GetNullValueForNullableType<int64_t>() == 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.
*/
@@ -332,26 +317,26 @@
// Store large data
uint8_t valueArray[9] = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42 };
ByteSpan value(valueArray);
- err = persistenceProvider.WriteValue(TestConcretePath, value);
+ err = persistenceProvider.SafeWriteValue(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);
+ err = persistenceProvider.SafeReadValue(TestConcretePath, 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);
+ err = persistenceProvider.SafeReadValue(TestConcretePath, 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);
+ err = persistenceProvider.SafeReadValue(TestConcretePath, valueReadBackByteSpan8);
NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL);
// Fail to get value as uint8_t
@@ -385,7 +370,6 @@
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),
diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp
index eb873bd..788723a 100644
--- a/src/app/util/attribute-storage.cpp
+++ b/src/app/util/attribute-storage.cpp
@@ -1225,9 +1225,8 @@
{
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->attributeType, am->size, bytes);
+ CHIP_ERROR err = attrStorage->ReadValue(
+ app::ConcreteAttributePath(de->endpoint, cluster->clusterId, am->attributeId), am, bytes);
if (err == CHIP_NO_ERROR)
{
ptr = attrData;
diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h
index 9adc8fd..65cd42c 100644
--- a/src/lib/support/DefaultStorageKeyAllocator.h
+++ b/src/lib/support/DefaultStorageKeyAllocator.h
@@ -170,6 +170,14 @@
return StorageKeyName::Formatted("g/a/%x/%" PRIx32 "/%" PRIx32, endpointId, clusterId, attributeId);
}
+ // Returns the key for Safely stored attributes.
+ static StorageKeyName SafeAttributeValue(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId)
+ {
+ // Needs at most 26 chars: 6 for "s/a///", 4 for the endpoint id, 8 each
+ // for the cluster and attribute ids.
+ return StorageKeyName::Formatted("g/sa/%x/%" PRIx32 "/%" PRIx32, endpointId, clusterId, attributeId);
+ }
+
// TODO: Should store fabric-specific parts of the binding list under keys
// starting with "f/%x/".
static StorageKeyName BindingTable() { return StorageKeyName::FromConst("g/bt"); }