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,78 @@ 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 kMaxValueSize = 16 ; // ipv6adr
267
+ if (valueSize > kMaxValueSize )
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[kMaxValueSize ];
283
+ // Cast to uint16_t is safe, because we checked valueSize <= kMaxValueSize 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
+ // The first byte of the buffer is the string length, and
296
+ // oldLength/newLength refer to the number of bytes after that. We want
297
+ // to include that first byte in our comparison, because null and empty
298
+ // string have different values there but both return 0 from
299
+ // emberAfStringLength.
300
+ *isChanging = (oldLength != newLength) || (memcmp (oldValueBuffer, newValueData, oldLength + 1 ) != 0 );
301
+ }
302
+ else if (emberAfIsLongStringAttributeType (attributeType))
303
+ {
304
+ size_t oldLength = emberAfLongStringLength (oldValueBuffer);
305
+ size_t newLength = emberAfLongStringLength (newValueData);
306
+ // The first two bytes of the buffer are the string length, and
307
+ // oldLength/newLength refer to the number of bytes after that. We want
308
+ // to include those first two bytes in our comparison, because null and
309
+ // empty string have different values there but both return 0 from
310
+ // emberAfLongStringLength.
311
+ *isChanging = (oldLength != newLength) || (memcmp (oldValueBuffer, newValueData, oldLength + 2 ) != 0 );
312
+ }
313
+ else
314
+ {
315
+ *isChanging = (memcmp (newValueData, oldValueBuffer, valueSize) != 0 );
316
+ }
317
+
318
+ return Status::Success;
319
+ }
320
+
210
321
Status emAfWriteAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * data,
211
- EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType)
322
+ EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty )
212
323
{
213
324
const EmberAfAttributeMetadata * metadata = nullptr ;
214
325
EmberAfAttributeSearchRecord record;
@@ -286,12 +397,25 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
286
397
}
287
398
}
288
399
400
+ // Check whether anything is actually changing, before we do any work here.
401
+ bool valueChanging;
402
+ Status imStatus = AttributeValueIsChanging (endpoint, cluster, attributeID, metadata, data, &valueChanging);
403
+ if (imStatus != Status::Success)
404
+ {
405
+ return imStatus;
406
+ }
407
+
408
+ if (!valueChanging)
409
+ {
410
+ // Just do nothing.
411
+ return Status::Success;
412
+ }
413
+
289
414
const app::ConcreteAttributePath attributePath (endpoint, cluster, attributeID);
290
415
291
416
// Pre write attribute callback for all attribute changes,
292
417
// regardless of cluster.
293
- Protocols::InteractionModel::Status imStatus =
294
- MatterPreAttributeChangeCallback (attributePath, dataType, emberAfAttributeSize (metadata), data);
418
+ imStatus = MatterPreAttributeChangeCallback (attributePath, dataType, emberAfAttributeSize (metadata), data);
295
419
if (imStatus != Protocols::InteractionModel::Status::Success)
296
420
{
297
421
return imStatus;
@@ -328,7 +452,10 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
328
452
// The callee will weed out attributes that do not need to be stored.
329
453
emAfSaveAttributeToStorageIfNeeded (data, endpoint, cluster, metadata);
330
454
331
- MatterReportingAttributeChangeCallback (endpoint, cluster, attributeID);
455
+ if (markDirty != MarkAttributeDirty::kNo )
456
+ {
457
+ MatterReportingAttributeChangeCallback (endpoint, cluster, attributeID);
458
+ }
332
459
333
460
// Post write attribute callback for all attributes changes, regardless
334
461
// of cluster.
@@ -341,6 +468,8 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
341
468
return Status::Success;
342
469
}
343
470
471
+ } // anonymous namespace
472
+
344
473
Status emberAfReadAttribute (EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr, uint16_t readLength)
345
474
{
346
475
const EmberAfAttributeMetadata * metadata = nullptr ;
0 commit comments