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 524 : 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 1601 : bool LoadNativeEndianValue(const ConcreteAttributePath & path, T & value, const T & valueOnLoadFailure)
51 : {
52 1601 : 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 212 : 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 212 : NullableToStorage(valueOnLoadFailure, storageDefaultValue);
70 212 : bool success = InternalRawLoadNativeEndianValue(path, &storageReadValue, &storageDefaultValue, sizeof(T));
71 212 : StorageToNullable(storageReadValue, value);
72 :
73 212 : return success;
74 : }
75 :
76 : /// Stores a native-endianness value of type `T` into the persistence provider.
77 : /// On success, returns `CHIP_NO_ERROR`.
78 : /// On failure, returns the error code.
79 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr>
80 19 : CHIP_ERROR StoreNativeEndianValue(const ConcreteAttributePath & path, const T & value)
81 : {
82 19 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(T) });
83 : }
84 :
85 : /// Nullable
86 : /// Stores a native-endianness value of type `T` into the persistence provider.
87 : /// On success, returns `CHIP_NO_ERROR`.
88 : /// On failure, returns the error code.
89 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_enum_v<T>> * = nullptr>
90 13 : CHIP_ERROR StoreNativeEndianValue(const ConcreteAttributePath & path, const DataModel::Nullable<T> & value)
91 : {
92 : typename NumericAttributeTraits<T>::StorageType storageValue;
93 13 : NullableToStorage(value, storageValue);
94 13 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) });
95 : }
96 :
97 : /// Performs all the steps of:
98 : /// - decode the given raw data
99 : /// - validate that the decoded value is different from the current one
100 : /// - write to storage
101 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
102 5 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
103 : AttributeValueDecoder & decoder, T & value)
104 : {
105 5 : T decodedValue{};
106 5 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
107 5 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
108 4 : value = decodedValue;
109 4 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(value) });
110 : }
111 :
112 : /// Nullable type handling
113 : /// Performs all the steps of:
114 : /// - decode the given raw data
115 : /// - validate that the decoded value is different from the current one
116 : /// - write to storage
117 : template <typename T, typename std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
118 10 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
119 : AttributeValueDecoder & decoder, DataModel::Nullable<T> & value)
120 : {
121 10 : DataModel::Nullable<T> decodedValue{};
122 10 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
123 10 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
124 8 : value = decodedValue;
125 :
126 : typename NumericAttributeTraits<T>::StorageType storageValue;
127 8 : NullableToStorage(value, storageValue);
128 :
129 8 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) });
130 : }
131 :
132 : // Specialization for enums
133 : // - decode the given data
134 : // - verifies that it is a valid enum value
135 : // - validate that the decoded value is different from the current one
136 : // - writes to storage
137 : template <typename T, typename std::enable_if_t<std::is_enum_v<T>> * = nullptr>
138 6 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
139 : AttributeValueDecoder & decoder, T & value)
140 : {
141 6 : T decodedValue = T::kUnknownEnumValue;
142 6 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
143 6 : VerifyOrReturnError(decodedValue != T::kUnknownEnumValue, CHIP_IM_GLOBAL_STATUS(ConstraintError));
144 5 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
145 4 : value = decodedValue;
146 4 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&value), sizeof(value) });
147 : }
148 :
149 : // Nullable
150 : // Specialization for enums
151 : // - decode the given data
152 : // - verifies that it is a valid enum value
153 : // - validate that the decoded value is different from the current one
154 : // - writes to storage
155 : template <typename T, typename std::enable_if_t<std::is_enum_v<T>> * = nullptr>
156 19 : DataModel::ActionReturnStatus DecodeAndStoreNativeEndianValue(const ConcreteAttributePath & path,
157 : AttributeValueDecoder & decoder, DataModel::Nullable<T> & value)
158 : {
159 19 : DataModel::Nullable<T> decodedValue{};
160 19 : ReturnErrorOnFailure(decoder.Decode(decodedValue));
161 19 : VerifyOrReturnError(decodedValue.IsNull() || decodedValue.Value() != T::kUnknownEnumValue,
162 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
163 16 : VerifyOrReturnValue(decodedValue != value, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
164 14 : value = decodedValue;
165 :
166 : typename NumericAttributeTraits<T>::StorageType storageValue;
167 14 : NullableToStorage(value, storageValue);
168 :
169 14 : return mProvider.WriteValue(path, { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) });
170 : }
171 :
172 : /// Load the given string from concrete storage.
173 : ///
174 : /// NOTE: `value` is take as an internal short string to avoid the templates that Storage::String
175 : /// implies, however callers are generally expected to pass in a `Storage::String` value and
176 : /// not use internal classes directly.
177 : ///
178 : /// Returns true on success, false on failure. On failure the string is reset to empty.
179 : bool LoadString(const ConcreteAttributePath & path, Storage::Internal::ShortString & value);
180 :
181 : /// Store the given string in persistent storage.
182 : ///
183 : /// NOTE: `value` is take as an internal short string to avoid the templates that Storage::String
184 : /// implies, however callers are generally expected to pass in a `Storage::String` value and
185 : /// not use internal classes directly.
186 : CHIP_ERROR StoreString(const ConcreteAttributePath & path, const Storage::Internal::ShortString & value);
187 :
188 : /// Writes a TLV-encodable value (using DataModel::Encode) to the attribute storage.
189 : /// Uses the provided buffer for TLV encoding.
190 : ///
191 : /// The encoding format is:
192 : /// Structure (Anonymous Tag)
193 : /// <Value> (Context Tag 1)
194 : /// EndContainer
195 : ///
196 : /// This wrapper ensures valid top-level TLV elements and allows future extensibility.
197 : template <typename T>
198 10 : CHIP_ERROR StoreTLV(const ConcreteAttributePath & path, const T & value, MutableByteSpan buffer)
199 : {
200 20 : return InternalStoreTLV(path, buffer, &value, [](const void * context, TLV::TLVWriter & writer) -> CHIP_ERROR {
201 10 : return DataModel::Encode(writer, kTLVEncodingTag, *static_cast<const T *>(context));
202 10 : });
203 : }
204 :
205 : /// Stack-allocating overload for convenience.
206 : template <size_t kMaxBufferSize, typename T>
207 8 : CHIP_ERROR StoreTLV(const ConcreteAttributePath & path, const T & value)
208 : {
209 : uint8_t buffer[kMaxBufferSize];
210 8 : return StoreTLV(path, value, MutableByteSpan(buffer));
211 : }
212 :
213 : /// Loads a TLV value from storage using the provided buffer.
214 : ///
215 : /// WARNING: If T contains views (e.g. Spans, DataModel::List), they will point into `buffer`.
216 : /// The `buffer` MUST outlive the usage of `value`.
217 : template <typename T>
218 11 : CHIP_ERROR LoadTLV(const ConcreteAttributePath & path, T & value, MutableByteSpan buffer)
219 : {
220 25 : return InternalLoadTLV(path, buffer, &value, [](void * context, TLV::TLVReader & reader) -> CHIP_ERROR {
221 4 : return DataModel::Decode(reader, *static_cast<T *>(context));
222 11 : });
223 : }
224 :
225 : private:
226 : static constexpr TLV::Tag kTLVEncodingTag = TLV::ContextTag(1);
227 : AttributePersistenceProvider & mProvider;
228 :
229 : /// Loads a raw value of size `size` into the memory pointed to by `data`.
230 : /// If load fails, `false` is returned and data is filled with `valueOnLoadFailure`.
231 : ///
232 : /// Error reason for load failure is logged (or nothing logged in case "Value not found" is the
233 : /// reason for the load failure).
234 : bool InternalRawLoadNativeEndianValue(const ConcreteAttributePath & path, void * data, const void * valueOnLoadFailure,
235 : size_t size);
236 :
237 : using TLVEncoderCallback = CHIP_ERROR (*)(const void * context, TLV::TLVWriter & writer);
238 : using TLVDecoderCallback = CHIP_ERROR (*)(void * context, TLV::TLVReader & reader);
239 :
240 : CHIP_ERROR InternalStoreTLV(const ConcreteAttributePath & path, MutableByteSpan buffer, const void * context,
241 : TLVEncoderCallback encoder);
242 : CHIP_ERROR InternalLoadTLV(const ConcreteAttributePath & path, MutableByteSpan buffer, void * context,
243 : TLVDecoderCallback decoder);
244 : };
245 :
246 : } // namespace chip::app
|