| /* |
| * Copyright (c) 2025 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/AttributeValueDecoder.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/data-model-provider/ActionReturnStatus.h> |
| #include <app/data-model/Decode.h> |
| #include <app/data-model/Encode.h> |
| #include <app/persistence/AttributePersistenceProvider.h> |
| #include <app/persistence/String.h> |
| #include <lib/core/TLV.h> |
| |
| #include <type_traits> |
| |
| namespace chip::app { |
| |
| /// Provides functionality for handling attribute persistence via |
| /// an AttributePersistenceProvider. |
| /// |
| /// AttributePersistenceProvider works with raw bytes, however attributes |
| /// have known (strong) types and their load/decode logic is often |
| /// similar and reusable. This class implements the logic of handling |
| /// such attributes, so that it can be reused across cluster implementations. |
| class AttributePersistence |
| { |
| public: |
| AttributePersistence(AttributePersistenceProvider & provider) : mProvider(provider) {} |
| |
| /// Loads a native-endianness stored value of type `T` into `value` from the persistence provider. |
| /// |
| /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`. |
| /// |
| /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the |
| /// reason for the load failure). |
| template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr> |
| bool LoadNativeEndianValue(const ConcreteAttributePath & path, T & value, const T & valueOnLoadFailure) |
| { |
| return InternalRawLoadNativeEndianValue(path, &value, &valueOnLoadFailure, sizeof(T)); |
| } |
| |
| /// Nullable |
| /// Loads a native-endianness stored value of type `T` into `value` from the persistence provider. |
| /// |
| /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`. |
| /// |
| /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the |
| /// reason for the load failure). |
| template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr> |
| bool LoadNativeEndianValue(const ConcreteAttributePath & path, DataModel::Nullable<T> & value, |
| const DataModel::Nullable<T> & valueOnLoadFailure) |
| { |
| typename NumericAttributeTraits<T>::StorageType storageReadValue; |
| typename NumericAttributeTraits<T>::StorageType storageDefaultValue; |
| |
| NullableToStorage(valueOnLoadFailure, storageDefaultValue); |
| bool success = InternalRawLoadNativeEndianValue(path, &storageReadValue, &storageDefaultValue, sizeof(T)); |
| StorageToNullable(storageReadValue, value); |
| |
| return success; |
| } |
| |
| /// Stores a native-endianness value of type `T` into the persistence provider. |
| /// On success, returns `CHIP_NO_ERROR`. |
| /// On failure, returns the error code. |
| template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr> |
| CHIP_ERROR StoreNativeEndianValue(const ConcreteAttributePath & path, const T & value) |
| { |
| return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(T) }); |
| } |
| |
| /// Nullable |
| /// Stores a native-endianness value of type `T` into the persistence provider. |
| /// On success, returns `CHIP_NO_ERROR`. |
| /// On failure, returns the error code. |
| template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr> |
| CHIP_ERROR StoreNativeEndianValue(const ConcreteAttributePath & path, const DataModel::Nullable<T> & value) |
| { |
| typename NumericAttributeTraits<T>::StorageType storageValue; |
| NullableToStorage(value, storageValue); |
| return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) }); |
| } |
| |
| /// Performs all the steps of: |
| /// - decode the given raw data |
| /// - validate that the decoded value is different from the current one |
| /// - write to storage |
| template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr> |
| DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path, |
| AttributeValueDecoder & decoder, T & value) |
| { |
| T decodedValue{}; |
| ReturnErrorOnFailure(decoder.Decode(decodedValue)); |
| VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| value = decodedValue; |
| return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(value) }); |
| } |
| |
| /// Nullable type handling |
| /// Performs all the steps of: |
| /// - decode the given raw data |
| /// - validate that the decoded value is different from the current one |
| /// - write to storage |
| template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr> |
| DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path, |
| AttributeValueDecoder & decoder, DataModel::Nullable<T> & value) |
| { |
| DataModel::Nullable<T> decodedValue{}; |
| ReturnErrorOnFailure(decoder.Decode(decodedValue)); |
| VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| value = decodedValue; |
| |
| typename NumericAttributeTraits<T>::StorageType storageValue; |
| NullableToStorage(value, storageValue); |
| |
| return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) }); |
| } |
| |
| // Specialization for enums |
| // - decode the given data |
| // - verifies that it is a valid enum value |
| // - validate that the decoded value is different from the current one |
| // - writes to storage |
| template <typename T, typename std::enable_if_t<std::is_enum_v<T>> * = nullptr> |
| DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path, |
| AttributeValueDecoder & decoder, T & value) |
| { |
| T decodedValue = T::kUnknownEnumValue; |
| ReturnErrorOnFailure(decoder.Decode(decodedValue)); |
| VerifyOrReturnError(decodedValue != T::kUnknownEnumValue, CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| value = decodedValue; |
| return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(value) }); |
| } |
| |
| // Nullable |
| // Specialization for enums |
| // - decode the given data |
| // - verifies that it is a valid enum value |
| // - validate that the decoded value is different from the current one |
| // - writes to storage |
| template <typename T, typename std::enable_if_t<std::is_enum_v<T>> * = nullptr> |
| DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path, |
| AttributeValueDecoder & decoder, DataModel::Nullable<T> & value) |
| { |
| DataModel::Nullable<T> decodedValue{}; |
| ReturnErrorOnFailure(decoder.Decode(decodedValue)); |
| VerifyOrReturnError(decodedValue.IsNull() || decodedValue.Value() != T::kUnknownEnumValue, |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| value = decodedValue; |
| |
| typename NumericAttributeTraits<T>::StorageType storageValue; |
| NullableToStorage(value, storageValue); |
| |
| return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) }); |
| } |
| |
| /// Load the given string from concrete storage. |
| /// |
| /// NOTE: `value` is take as an internal short string to avoid the templates that Storage::String |
| /// implies, however callers are generally expected to pass in a `Storage::String` value and |
| /// not use internal classes directly. |
| /// |
| /// Returns true on success, false on failure. On failure the string is reset to empty. |
| bool LoadString(const ConcreteAttributePath & path, Storage::Internal::ShortString & value); |
| |
| /// Store the given string in persistent storage. |
| /// |
| /// NOTE: `value` is take as an internal short string to avoid the templates that Storage::String |
| /// implies, however callers are generally expected to pass in a `Storage::String` value and |
| /// not use internal classes directly. |
| CHIP_ERROR StoreString(const ConcreteAttributePath & path, const Storage::Internal::ShortString & value); |
| |
| /// Writes a TLV-encodable value (using DataModel::Encode) to the attribute storage. |
| /// Uses the provided buffer for TLV encoding. |
| /// |
| /// The encoding format is: |
| /// Structure (Anonymous Tag) |
| /// <Value> (Context Tag 1) |
| /// EndContainer |
| /// |
| /// This wrapper ensures valid top-level TLV elements and allows future extensibility. |
| template <typename T> |
| CHIP_ERROR StoreTLV(const ConcreteAttributePath & path, const T & value, MutableByteSpan buffer) |
| { |
| return InternalStoreTLV(path, buffer, &value, [](const void * context, TLV::TLVWriter & writer) -> CHIP_ERROR { |
| return DataModel::Encode(writer, kTLVEncodingTag, *static_cast<const T *>(context)); |
| }); |
| } |
| |
| /// Stack-allocating overload for convenience. |
| template <size_t kMaxBufferSize, typename T> |
| CHIP_ERROR StoreTLV(const ConcreteAttributePath & path, const T & value) |
| { |
| uint8_t buffer[kMaxBufferSize]; |
| return StoreTLV(path, value, MutableByteSpan(buffer)); |
| } |
| |
| /// Loads a TLV value from storage using the provided buffer. |
| /// |
| /// WARNING: If T contains views (e.g. Spans, DataModel::List), they will point into `buffer`. |
| /// The `buffer` MUST outlive the usage of `value`. |
| template <typename T> |
| CHIP_ERROR LoadTLV(const ConcreteAttributePath & path, T & value, MutableByteSpan buffer) |
| { |
| return InternalLoadTLV(path, buffer, &value, [](void * context, TLV::TLVReader & reader) -> CHIP_ERROR { |
| return DataModel::Decode(reader, *static_cast<T *>(context)); |
| }); |
| } |
| |
| private: |
| static constexpr TLV::Tag kTLVEncodingTag = TLV::ContextTag(1); |
| AttributePersistenceProvider & mProvider; |
| |
| /// Loads a raw value of size `size` into the memory pointed to by `data`. |
| /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`. |
| /// |
| /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the |
| /// reason for the load failure). |
| bool InternalRawLoadNativeEndianValue(const ConcreteAttributePath & path, void * data, const void * valueOnLoadFailure, |
| size_t size); |
| |
| using TLVEncoderCallback = CHIP_ERROR (*)(const void * context, TLV::TLVWriter & writer); |
| using TLVDecoderCallback = CHIP_ERROR (*)(void * context, TLV::TLVReader & reader); |
| |
| CHIP_ERROR InternalStoreTLV(const ConcreteAttributePath & path, MutableByteSpan buffer, const void * context, |
| TLVEncoderCallback encoder); |
| CHIP_ERROR InternalLoadTLV(const ConcreteAttributePath & path, MutableByteSpan buffer, void * context, |
| TLVDecoderCallback decoder); |
| }; |
| |
| } // namespace chip::app |