Line data Source code
1 : /*
2 : * Copyright (c) 2025 Project CHIP Authors
3 : *
4 : * Licensed under the Apache License, Version 2.0 (the "License");
5 : * you may not use this file except in compliance with the License.
6 : * You may obtain a copy of the License at
7 : *
8 : * http://www.apache.org/licenses/LICENSE-2.0
9 : *
10 : * Unless required by applicable law or agreed to in writing, software
11 : * distributed under the License is distributed on an "AS IS" BASIS,
12 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : * See the License for the specific language governing permissions and
14 : * limitations under the License.
15 : */
16 : #pragma once
17 :
18 : #include <app/AttributeValueDecoder.h>
19 : #include <app/ConcreteAttributePath.h>
20 : #include <app/data-model-provider/ActionReturnStatus.h>
21 : #include <app/data-model/Decode.h>
22 : #include <app/data-model/Encode.h>
23 : #include <app/persistence/AttributePersistenceProvider.h>
24 : #include <app/persistence/String.h>
25 : #include <lib/core/TLV.h>
26 :
27 : #include <type_traits>
28 :
29 : namespace chip::app {
30 :
31 : /// Provides functionality for handling attribute persistence via
32 : /// an AttributePersistenceProvider.
33 : ///
34 : /// AttributePersistenceProvider works with raw bytes, however attributes
35 : /// have known (strong) types and their load/decode logic is often
36 : /// similar and reusable. This class implements the logic of handling
37 : /// such attributes, so that it can be reused across cluster implementations.
38 : class AttributePersistence
39 : {
40 : public:
41 280 : AttributePersistence(AttributePersistenceProvider & provider) : mProvider(provider) {}
42 :
43 : /// Loads a native-endianness stored value of type `T` into `value` from the persistence provider.
44 : ///
45 : /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`.
46 : ///
47 : /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the
48 : /// reason for the load failure).
49 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr>
50 179 : bool LoadNativeEndianValue(const ConcreteAttributePath & path, T & value, const T & valueOnLoadFailure)
51 : {
52 179 : return InternalRawLoadNativeEndianValue(path, &value, &valueOnLoadFailure, sizeof(T));
53 : }
54 :
55 : /// Nullable
56 : /// Loads a native-endianness stored value of type `T` into `value` from the persistence provider.
57 : ///
58 : /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`.
59 : ///
60 : /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the
61 : /// reason for the load failure).
62 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr>
63 124 : bool LoadNativeEndianValue(const ConcreteAttributePath & path, DataModel::Nullable<T> & value,
64 : const DataModel::Nullable<T> & valueOnLoadFailure)
65 : {
66 : typename NumericAttributeTraits<T>::StorageType storageReadValue;
67 : typename NumericAttributeTraits<T>::StorageType storageDefaultValue;
68 :
69 124 : NullableToStorage(valueOnLoadFailure, storageDefaultValue);
70 124 : bool success = InternalRawLoadNativeEndianValue(path, &storageReadValue, &storageDefaultValue, sizeof(T));
71 124 : StorageToNullable(storageReadValue, value);
72 :
73 124 : return success;
74 : }
75 :
76 : /// Performs all the steps of:
77 : /// - decode the given raw data
78 : /// - validate that the decoded value is different from the current one
79 : /// - write to storage
80 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
81 5 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
82 : AttributeValueDecoder & decoder, T & value)
83 : {
84 5 : T decodedValue{};
85 5 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
86 5 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
87 4 : value = decodedValue;
88 4 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(value) });
89 : }
90 :
91 : /// Nullable type handling
92 : /// Performs all the steps of:
93 : /// - decode the given raw data
94 : /// - validate that the decoded value is different from the current one
95 : /// - write to storage
96 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
97 10 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
98 : AttributeValueDecoder & decoder, DataModel::Nullable<T> & value)
99 : {
100 10 : DataModel::Nullable<T> decodedValue{};
101 10 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
102 10 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
103 8 : value = decodedValue;
104 :
105 : typename NumericAttributeTraits<T>::StorageType storageValue;
106 8 : NullableToStorage(value, storageValue);
107 :
108 8 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) });
109 : }
110 :
111 : // Specialization for enums
112 : // - decode the given data
113 : // - verifies that it is a valid enum value
114 : // - validate that the decoded value is different from the current one
115 : // - writes to storage
116 : template <typename T, typename std::enable_if_t<std::is_enum_v<T>> * = nullptr>
117 6 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
118 : AttributeValueDecoder & decoder, T & value)
119 : {
120 6 : T decodedValue = T::kUnknownEnumValue;
121 6 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
122 6 : VerifyOrReturnError(decodedValue != T::kUnknownEnumValue, CHIP_IM_GLOBAL_STATUS(ConstraintError));
123 5 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
124 4 : value = decodedValue;
125 4 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(value) });
126 : }
127 :
128 : // Nullable
129 : // Specialization for enums
130 : // - decode the given data
131 : // - verifies that it is a valid enum value
132 : // - validate that the decoded value is different from the current one
133 : // - writes to storage
134 : template <typename T, typename std::enable_if_t<std::is_enum_v<T>> * = nullptr>
135 19 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
136 : AttributeValueDecoder & decoder, DataModel::Nullable<T> & value)
137 : {
138 19 : DataModel::Nullable<T> decodedValue{};
139 19 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
140 19 : VerifyOrReturnError(decodedValue.IsNull() || decodedValue.Value() != T::kUnknownEnumValue,
141 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
142 16 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
143 14 : value = decodedValue;
144 :
145 : typename NumericAttributeTraits<T>::StorageType storageValue;
146 14 : NullableToStorage(value, storageValue);
147 :
148 14 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) });
149 : }
150 :
151 : /// Load the given string from concrete storage.
152 : ///
153 : /// NOTE: `value` is take as an internal short string to avoid the templates that Storage::String
154 : /// implies, however callers are generally expected to pass in a `Storage::String` value and
155 : /// not use internal classes directly.
156 : ///
157 : /// Returns true on success, false on failure. On failure the string is reset to empty.
158 : bool LoadString(const ConcreteAttributePath & path, Storage::Internal::ShortString & value);
159 :
160 : /// Store the given string in persistent storage.
161 : ///
162 : /// NOTE: `value` is take as an internal short string to avoid the templates that Storage::String
163 : /// implies, however callers are generally expected to pass in a `Storage::String` value and
164 : /// not use internal classes directly.
165 : CHIP_ERROR StoreString(const ConcreteAttributePath & path, const Storage::Internal::ShortString & value);
166 :
167 : /// Writes a TLV-encodable value (using DataModel::Encode) to the attribute storage.
168 : /// Uses the provided buffer for TLV encoding.
169 : ///
170 : /// The encoding format is:
171 : /// Structure (Anonymous Tag)
172 : /// <Value> (Context Tag 1)
173 : /// EndContainer
174 : ///
175 : /// This wrapper ensures valid top-level TLV elements and allows future extensibility.
176 : template <typename T>
177 10 : CHIP_ERROR StoreTLV(const ConcreteAttributePath & path, const T & value, MutableByteSpan buffer)
178 : {
179 20 : return InternalStoreTLV(path, buffer, &value, [](const void * context, TLV::TLVWriter & writer) -> CHIP_ERROR {
180 10 : return DataModel::Encode(writer, kTLVEncodingTag, *static_cast<const T *>(context));
181 10 : });
182 : }
183 :
184 : /// Stack-allocating overload for convenience.
185 : template <size_t kMaxBufferSize, typename T>
186 8 : CHIP_ERROR StoreTLV(const ConcreteAttributePath & path, const T & value)
187 : {
188 : uint8_t buffer[kMaxBufferSize];
189 8 : return StoreTLV(path, value, MutableByteSpan(buffer));
190 : }
191 :
192 : /// Loads a TLV value from storage using the provided buffer.
193 : ///
194 : /// WARNING: If T contains views (e.g. Spans, DataModel::List), they will point into `buffer`.
195 : /// The `buffer` MUST outlive the usage of `value`.
196 : template <typename T>
197 11 : CHIP_ERROR LoadTLV(const ConcreteAttributePath & path, T & value, MutableByteSpan buffer)
198 : {
199 25 : return InternalLoadTLV(path, buffer, &value, [](void * context, TLV::TLVReader & reader) -> CHIP_ERROR {
200 4 : return DataModel::Decode(reader, *static_cast<T *>(context));
201 11 : });
202 : }
203 :
204 : private:
205 : static constexpr TLV::Tag kTLVEncodingTag = TLV::ContextTag(1);
206 : AttributePersistenceProvider & mProvider;
207 :
208 : /// Loads a raw value of size `size` into the memory pointed to by `data`.
209 : /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`.
210 : ///
211 : /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the
212 : /// reason for the load failure).
213 : bool InternalRawLoadNativeEndianValue(const ConcreteAttributePath & path, void * data, const void * valueOnLoadFailure,
214 : size_t size);
215 :
216 : using TLVEncoderCallback = CHIP_ERROR (*)(const void * context, TLV::TLVWriter & writer);
217 : using TLVDecoderCallback = CHIP_ERROR (*)(void * context, TLV::TLVReader & reader);
218 :
219 : CHIP_ERROR InternalStoreTLV(const ConcreteAttributePath & path, MutableByteSpan buffer, const void * context,
220 : TLVEncoderCallback encoder);
221 : CHIP_ERROR InternalLoadTLV(const ConcreteAttributePath & path, MutableByteSpan buffer, void * context,
222 : TLVDecoderCallback decoder);
223 : };
224 :
225 : } // namespace chip::app
|