21
21
#include < app/util/attribute-storage-detail.h>
22
22
#include < app/util/attribute-storage.h>
23
23
#include < app/util/config.h>
24
+ #include < app/util/ember-strings.h>
24
25
#include < app/util/generic-callbacks.h>
25
26
#include < app/util/odd-sized-integers.h>
26
27
#include < lib/core/CHIPConfig.h>
37
38
using chip::Protocols::InteractionModel::Status;
38
39
39
40
using namespace chip ;
41
+ using namespace chip ::app;
40
42
41
43
namespace {
42
-
43
44
// Zigbee spec says types between signed 8 bit and signed 64 bit
44
45
bool emberAfIsTypeSigned (EmberAfAttributeType dataType)
45
46
{
@@ -134,18 +135,58 @@ int8_t emberAfCompareValues(const uint8_t * val1, const uint8_t * val2, uint16_t
134
135
return 0 ;
135
136
}
136
137
137
- } // namespace
138
+ /* *
139
+ * @brief write an attribute, performing all the checks.
140
+ *
141
+ * This function will attempt to write the attribute value from
142
+ * the provided pointer. This function will only check that the
143
+ * attribute exists. If it does it will write the value into
144
+ * the attribute table for the given attribute.
145
+ *
146
+ * This function will not check to see if the attribute is
147
+ * writable since the read only / writable characteristic
148
+ * of an attribute only pertains to external devices writing
149
+ * over the air. Because this function is being called locally
150
+ * it assumes that the device knows what it is doing and has permission
151
+ * to perform the given operation.
152
+ *
153
+ * if true is passed in for overrideReadOnlyAndDataType then the data type is
154
+ * not checked and the read-only flag is ignored. This mode is meant for
155
+ * testing or setting the initial value of the attribute on the device.
156
+ *
157
+ * this returns:
158
+ * - Status::UnsupportedEndpoint: if endpoint isn't supported by the device.
159
+ * - Status::UnsupportedCluster: if cluster isn't supported on the endpoint.
160
+ * - Status::UnsupportedAttribute: if attribute isn't supported in the cluster.
161
+ * - Status::InvalidDataType: if the data type passed in doesnt match the type
162
+ * stored in the attribute table
163
+ * - Status::UnsupportedWrite: if the attribute isnt writable
164
+ * - Status::ConstraintError: if the value is set out of the allowable range for
165
+ * the attribute
166
+ * - Status::Success: if the attribute was found and successfully written
167
+ */
168
+ Status emAfWriteAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * data,
169
+ EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty);
170
+ } // anonymous namespace
138
171
139
172
Status emAfWriteAttributeExternal (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
140
173
EmberAfAttributeType dataType)
141
174
{
142
- return emAfWriteAttribute (endpoint, cluster, attributeID, dataPtr, dataType, false /* override read-only */ );
175
+ return emAfWriteAttribute (endpoint, cluster, attributeID, dataPtr, dataType, false /* override read-only */ ,
176
+ MarkAttributeDirty::kIfChanged );
143
177
}
144
178
145
179
Status emberAfWriteAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
146
180
EmberAfAttributeType dataType)
147
181
{
148
- return emAfWriteAttribute (endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */ );
182
+ return emAfWriteAttribute (endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */ ,
183
+ MarkAttributeDirty::kIfChanged );
184
+ }
185
+
186
+ Status emberAfWriteAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
187
+ EmberAfAttributeType dataType, MarkAttributeDirty markDirty)
188
+ {
189
+ return emAfWriteAttribute (endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */ , markDirty);
149
190
}
150
191
151
192
// ------------------------------------------------------------------------------
@@ -207,8 +248,68 @@ static bool IsNullValue(const uint8_t * data, uint16_t dataLen, bool isAttribute
207
248
return false ;
208
249
}
209
250
251
+ namespace {
252
+
253
+ /* *
254
+ * Helper function to determine whether the attribute value for the given
255
+ * attribute is changing. On success, the isChanging outparam will be set to
256
+ * whether the value is changing.
257
+ */
258
+ Status AttributeValueIsChanging (EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
259
+ const EmberAfAttributeMetadata * metadata, uint8_t * newValueData, bool * isChanging)
260
+ {
261
+ EmberAfAttributeType attributeType = metadata->attributeType ;
262
+
263
+ // We don't know how to size our buffer for strings in general, but if the
264
+ // string happens to fit into our fixed-size buffer, great.
265
+ size_t valueSize = metadata->size ;
266
+ constexpr size_t maxValueSize = 16 ; // ipv6adr
267
+ if (valueSize > maxValueSize)
268
+ {
269
+ if (emberAfIsStringAttributeType (attributeType) || emberAfIsLongStringAttributeType (attributeType))
270
+ {
271
+ // It's a string that may not fit in our buffer. Just claim it's
272
+ // changing, since we have no way to tell.
273
+ *isChanging = true ;
274
+ return Status::Success;
275
+ }
276
+
277
+ // Very much unexpected
278
+ ChipLogError (Zcl, " Attribute type %d has too-large size %u" , attributeType, static_cast <unsigned >(valueSize));
279
+ return Status::ConstraintError;
280
+ }
281
+
282
+ uint8_t oldValueBuffer[maxValueSize];
283
+ // Cast to uint16_t is safe, because we checked valueSize <= maxValueSize above.
284
+ if (emberAfReadAttribute (endpoint, cluster, attributeID, oldValueBuffer, static_cast <uint16_t >(valueSize)) != Status::Success)
285
+ {
286
+ // We failed to read the old value, so flag the value as changing to be safe.
287
+ *isChanging = true ;
288
+ return Status::Success;
289
+ }
290
+
291
+ if (emberAfIsStringAttributeType (attributeType))
292
+ {
293
+ size_t oldLength = emberAfStringLength (oldValueBuffer);
294
+ size_t newLength = emberAfStringLength (newValueData);
295
+ *isChanging = (oldLength != newLength) || (memcmp (oldValueBuffer + 1 , newValueData + 1 , oldLength) != 0 );
296
+ }
297
+ else if (emberAfIsLongStringAttributeType (attributeType))
298
+ {
299
+ size_t oldLength = emberAfLongStringLength (oldValueBuffer);
300
+ size_t newLength = emberAfLongStringLength (newValueData);
301
+ *isChanging = (oldLength != newLength) || (memcmp (oldValueBuffer + 2 , newValueData + 2 , oldLength) != 0 );
302
+ }
303
+ else
304
+ {
305
+ *isChanging = (memcmp (newValueData, oldValueBuffer, valueSize) != 0 );
306
+ }
307
+
308
+ return Status::Success;
309
+ }
310
+
210
311
Status emAfWriteAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * data,
211
- EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType)
312
+ EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty )
212
313
{
213
314
const EmberAfAttributeMetadata * metadata = nullptr ;
214
315
EmberAfAttributeSearchRecord record;
@@ -286,12 +387,25 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
286
387
}
287
388
}
288
389
390
+ // Check whether anything is actually changing, before we do any work here.
391
+ bool valueChanging;
392
+ Status imStatus = AttributeValueIsChanging (endpoint, cluster, attributeID, metadata, data, &valueChanging);
393
+ if (imStatus != Status::Success)
394
+ {
395
+ return imStatus;
396
+ }
397
+
398
+ if (!valueChanging)
399
+ {
400
+ // Just do nothing.
401
+ return Status::Success;
402
+ }
403
+
289
404
const app::ConcreteAttributePath attributePath (endpoint, cluster, attributeID);
290
405
291
406
// Pre write attribute callback for all attribute changes,
292
407
// regardless of cluster.
293
- Protocols::InteractionModel::Status imStatus =
294
- MatterPreAttributeChangeCallback (attributePath, dataType, emberAfAttributeSize (metadata), data);
408
+ imStatus = MatterPreAttributeChangeCallback (attributePath, dataType, emberAfAttributeSize (metadata), data);
295
409
if (imStatus != Protocols::InteractionModel::Status::Success)
296
410
{
297
411
return imStatus;
@@ -328,7 +442,10 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
328
442
// The callee will weed out attributes that do not need to be stored.
329
443
emAfSaveAttributeToStorageIfNeeded (data, endpoint, cluster, metadata);
330
444
331
- MatterReportingAttributeChangeCallback (endpoint, cluster, attributeID);
445
+ if (markDirty != MarkAttributeDirty::kNo )
446
+ {
447
+ MatterReportingAttributeChangeCallback (endpoint, cluster, attributeID);
448
+ }
332
449
333
450
// Post write attribute callback for all attributes changes, regardless
334
451
// of cluster.
@@ -341,6 +458,8 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
341
458
return Status::Success;
342
459
}
343
460
461
+ } // anonymous namespace
462
+
344
463
Status emberAfReadAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr, uint16_t readLength)
345
464
{
346
465
const EmberAfAttributeMetadata * metadata = nullptr ;
0 commit comments