Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / src / app / reporting / reporting.cpp
1 /**
2  *
3  *    Copyright (c) 2020 Project CHIP Authors
4  *
5  *    Licensed under the Apache License, Version 2.0 (the "License");
6  *    you may not use this file except in compliance with the License.
7  *    You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *    Unless required by applicable law or agreed to in writing, software
12  *    distributed under the License is distributed on an "AS IS" BASIS,
13  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *    See the License for the specific language governing permissions and
15  *    limitations under the License.
16  */
17
18 /**
19  *
20  *    Copyright (c) 2020 Silicon Labs
21  *
22  *    Licensed under the Apache License, Version 2.0 (the "License");
23  *    you may not use this file except in compliance with the License.
24  *    You may obtain a copy of the License at
25  *
26  *        http://www.apache.org/licenses/LICENSE-2.0
27  *
28  *    Unless required by applicable law or agreed to in writing, software
29  *    distributed under the License is distributed on an "AS IS" BASIS,
30  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31  *    See the License for the specific language governing permissions and
32  *    limitations under the License.
33  */
34 /****************************************************************************
35  * @file
36  * @brief Routines for the Reporting plugin, which
37  *sends asynchronous reports when a ZCL attribute's
38  *value has changed.
39  *******************************************************************************
40  ******************************************************************************/
41
42 #include "reporting.h"
43 #include <app/util/af-event.h>
44 #include <app/util/af.h>
45 #include <app/util/attribute-storage.h>
46 #include <app/util/binding-table.h>
47 #include <app/util/common.h>
48 #include <support/SafeInt.h>
49 #include <system/SystemLayer.h>
50
51 #include "gen/attribute-type.h"
52 #include "gen/cluster-id.h"
53 #include "gen/command-id.h"
54
55 using namespace chip;
56
57 // TODO: Need to figure out what needs to happen wrt HAL tokens here, but for
58 // now define ESZP_HOST to disable it.  See
59 // https://github.com/project-chip/connectedhomeip/issues/3275
60 #define EZSP_HOST
61
62 #ifdef ATTRIBUTE_LARGEST
63 #define READ_DATA_SIZE ATTRIBUTE_LARGEST
64 #else
65 #define READ_DATA_SIZE 8 // max size if attributes aren't present
66 #endif
67
68 #define NULL_INDEX 0xFF
69
70 static void conditionallySendReport(EndpointId endpoint, ClusterId clusterId);
71 static void scheduleTick(void);
72 static void removeConfiguration(uint8_t index);
73 static void removeConfigurationAndScheduleTick(uint8_t index);
74 static EmberAfStatus configureReceivedAttribute(const EmberAfClusterCommand * cmd, AttributeId attributeId, uint8_t mask,
75                                                 uint16_t timeout);
76 static void putReportableChangeInResp(const EmberAfPluginReportingEntry * entry, EmberAfAttributeType dataType);
77 static void retrySendReport(EmberOutgoingMessageType type, uint64_t indexOrDestination, EmberApsFrame * apsFrame, uint16_t msgLen,
78                             uint8_t * message, EmberStatus status);
79 static uint32_t computeStringHash(uint8_t * data, uint8_t length);
80
81 EmberEventControl emberAfPluginReportingTickEventControl;
82
83 EmAfPluginReportVolatileData emAfPluginReportVolatileData[REPORT_TABLE_SIZE];
84
85 /** @brief Configured
86  *
87  * This callback is called by the Reporting plugin whenever a reporting entry
88  * is configured, including when entries are deleted or updated. The
89  * application can use this callback for scheduling readings or measurements
90  * based on the minimum and maximum reporting interval for the entry. The
91  * application should return EMBER_ZCL_STATUS_SUCCESS if it can support the
92  * configuration or an error status otherwise. Note: attribute reporting is
93  * required for many clusters and attributes, so rejecting a reporting
94  * configuration may violate ZigBee specifications.
95  *
96  * @param entry   Ver.: always
97  */
98 EmberAfStatus emberAfPluginReportingConfiguredCallback(const EmberAfPluginReportingEntry * entry)
99 {
100     return EMBER_ZCL_STATUS_SUCCESS;
101 }
102
103 static void retrySendReport(EmberOutgoingMessageType type, uint64_t indexOrDestination, EmberApsFrame * apsFrame, uint16_t msgLen,
104                             uint8_t * message, EmberStatus status)
105 {
106     // Retry once, and do so by unicasting without a pointer to this callback
107     if (status != EMBER_SUCCESS)
108     {
109         emberAfSendUnicast(type, indexOrDestination, apsFrame, msgLen, message);
110     }
111 }
112
113 // Implementation based on public domain Fowler/Noll/Vo FNV-1a hash function:
114 // http://isthe.com/chongo/tech/comp/fnv/
115 // https://tools.ietf.org/html/draft-eastlake-fnv-14
116 // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
117 //
118 // Used to save and compare hashes of CHAR and OCTET string values in order to detect
119 // reportable changes. The strings themselves are longer than the storage size.
120 #define FNV1_OFFSET_BASIS (2166136261)
121 #define FNV1_PRIME (16777619)
122 static uint32_t computeStringHash(uint8_t * data, uint8_t length)
123 {
124     // FNV-1a, 32-bit hash
125     uint32_t hash = FNV1_OFFSET_BASIS;
126     for (int i = 0; i < length; ++i)
127     {
128         hash ^= data[i];
129         hash *= FNV1_PRIME; // Or, hash += (hash<<1) + (hash<<4) + (hash<<7) + (hash<<8) + (hash<<24);
130     }
131     return hash;
132 }
133
134 #ifdef EZSP_HOST
135 #if REPORT_TABLE_SIZE != 0
136 static EmberAfPluginReportingEntry table[REPORT_TABLE_SIZE];
137 #endif
138 void emAfPluginReportingGetEntry(uint8_t index, EmberAfPluginReportingEntry * result)
139 {
140 #if REPORT_TABLE_SIZE != 0
141     memmove(result, &table[index], sizeof(EmberAfPluginReportingEntry));
142 #endif
143 }
144 void emAfPluginReportingSetEntry(uint8_t index, EmberAfPluginReportingEntry * value)
145 {
146 #if REPORT_TABLE_SIZE != 0
147     memmove(&table[index], value, sizeof(EmberAfPluginReportingEntry));
148 #endif
149 }
150 #else
151 void emAfPluginReportingGetEntry(uint8_t index, EmberAfPluginReportingEntry * result)
152 {
153     // TODO: Once https://github.com/project-chip/connectedhomeip/issues/2470 is
154     // fixed this manual marking of the entry as unused can probably be removed.
155     result->endpoint = EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID;
156     halCommonGetIndexedToken(result, TOKEN_REPORT_TABLE, index);
157 }
158 void emAfPluginReportingSetEntry(uint8_t index, EmberAfPluginReportingEntry * value)
159 {
160     halCommonSetIndexedToken(TOKEN_REPORT_TABLE, index, value);
161 }
162 #endif
163
164 void emberAfPluginReportingStackStatusCallback(EmberStatus status)
165 {
166     if (status == EMBER_NETWORK_UP)
167     {
168         // Load default reporting configurations
169         emberAfPluginReportingLoadReportingConfigDefaults();
170
171         scheduleTick();
172     }
173 }
174
175 void emberAfPluginReportingInitCallback(void)
176 {
177     // On device initialization, any attributes that have been set up to report
178     // should generate an attribute report.
179     for (uint8_t i = 0; i < REPORT_TABLE_SIZE; i++)
180     {
181         EmberAfPluginReportingEntry entry;
182         emAfPluginReportingGetEntry(i, &entry);
183         if (entry.endpoint != EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID &&
184             entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED)
185         {
186             emAfPluginReportVolatileData[i].reportableChange = true;
187         }
188     }
189
190     scheduleTick();
191 }
192
193 void emberAfPluginReportingTickEventHandler(void)
194 {
195     EmberApsFrame * apsFrame = NULL;
196     EmberAfStatus status;
197     EmberAfAttributeType dataType;
198     uint16_t manufacturerCode = 0;
199     uint8_t readData[READ_DATA_SIZE];
200     uint8_t i;
201     uint16_t dataSize;
202     bool clientToServer = false;
203     EmberBindingTableEntry bindingEntry;
204     // reportSize needs to be able to fit a sum of dataSize and some other stuff
205     // without overflowing.
206     uint32_t reportSize;
207     uint8_t index, currentPayloadMaxLength = 0, smallestPayloadMaxLength = 0;
208
209     for (i = 0; i < REPORT_TABLE_SIZE; i++)
210     {
211         EmberAfPluginReportingEntry entry;
212         // Not initializing entry.mask causes errors even if wrapped with GCC diagnostic ignored
213         entry.mask = CLUSTER_MASK_SERVER;
214         uint32_t elapsedMs;
215         emAfPluginReportingGetEntry(i, &entry);
216         // We will only send reports for active reported attributes and only if a
217         // reportable change has occurred and the minimum interval has elapsed or
218         // if the maximum interval is set and has elapsed.
219         elapsedMs =
220             elapsedTimeInt32u(emAfPluginReportVolatileData[i].lastReportTimeMs, chip::System::Layer::GetClock_MonotonicMS());
221         if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID ||
222             entry.direction != EMBER_ZCL_REPORTING_DIRECTION_REPORTED ||
223             (elapsedMs < entry.data.reported.minInterval * MILLISECOND_TICKS_PER_SECOND) ||
224             (!emAfPluginReportVolatileData[i].reportableChange &&
225              (entry.data.reported.maxInterval == 0 ||
226               (elapsedMs < (entry.data.reported.maxInterval * MILLISECOND_TICKS_PER_SECOND)))))
227         {
228             continue;
229         }
230
231         status = emAfReadAttribute(entry.endpoint, entry.clusterId, entry.attributeId, entry.mask, entry.manufacturerCode,
232                                    (uint8_t *) &readData, READ_DATA_SIZE, &dataType);
233         if (status != EMBER_ZCL_STATUS_SUCCESS)
234         {
235             emberAfReportingPrintln("ERR: reading cluster 0x%2x attribute 0x%2x: 0x%x", entry.clusterId, entry.attributeId, status);
236             continue;
237         }
238         if (emberAfIsLongStringAttributeType(dataType))
239         {
240             // LONG string types are rarely used and even more rarely (never?)
241             // reported; ignore and leave ensuing handling of other types unchanged.
242             emberAfReportingPrintln("ERR: reporting of LONG string attribute type not supported: cluster 0x%2x attribute 0x%2x",
243                                     entry.clusterId, entry.attributeId);
244             continue;
245         }
246
247         // find size of current report
248         dataSize   = emberAfAttributeValueSize(dataType, readData);
249         reportSize = static_cast<uint32_t>(sizeof(entry.attributeId) + sizeof(dataType) + dataSize);
250
251         // If we have already started a report for a different attribute or
252         // destination, or if the current entry is too big for current report, send it and create a new one.
253         if (apsFrame != NULL &&
254             (!(entry.endpoint == apsFrame->sourceEndpoint && entry.clusterId == apsFrame->clusterId &&
255                emberAfClusterIsClient(&entry) == clientToServer && entry.manufacturerCode == manufacturerCode) ||
256              (appResponseLength + reportSize > smallestPayloadMaxLength)))
257         {
258             if (appResponseLength + reportSize > smallestPayloadMaxLength)
259             {
260                 emberAfReportingPrintln("Reporting Entry Full - creating new report");
261             }
262             conditionallySendReport(apsFrame->sourceEndpoint, apsFrame->clusterId);
263             apsFrame = NULL;
264         }
265
266         // If we haven't made the message header, make it.
267         if (apsFrame == NULL)
268         {
269             apsFrame       = emberAfGetCommandApsFrame();
270             clientToServer = emberAfClusterIsClient(&entry);
271             // The manufacturer-specfic version of the fill API only creates a
272             // manufacturer-specfic command if the manufacturer code is set.  For
273             // non-manufacturer-specfic reports, the manufacturer code is unset, so
274             // we can get away with using this API for both cases.
275             emberAfFillExternalManufacturerSpecificBuffer(
276                 (clientToServer
277                      ? (ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS)
278                      : (ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS)),
279                 entry.clusterId, entry.manufacturerCode, ZCL_REPORT_ATTRIBUTES_COMMAND_ID, "");
280             apsFrame->sourceEndpoint = entry.endpoint;
281             apsFrame->options        = EMBER_AF_DEFAULT_APS_OPTIONS;
282             manufacturerCode         = entry.manufacturerCode;
283
284             // EMAPPFWKV2-1327: Reporting plugin does not account for reporting too many attributes
285             //                  in the same ZCL:ReportAttributes message
286
287             // find smallest maximum payload that the destination can receive for this cluster and source endpoint
288             smallestPayloadMaxLength = MAX_INT8U_VALUE;
289             for (index = 0; index < EMBER_BINDING_TABLE_SIZE; index++)
290             {
291                 status = (EmberAfStatus) emberGetBinding(index, &bindingEntry);
292                 if (status == (EmberAfStatus) EMBER_SUCCESS && bindingEntry.local == entry.endpoint &&
293                     bindingEntry.clusterId == entry.clusterId)
294                 {
295                     currentPayloadMaxLength =
296                         emberAfMaximumApsPayloadLength(bindingEntry.type, bindingEntry.networkIndex, apsFrame);
297                     if (currentPayloadMaxLength < smallestPayloadMaxLength)
298                     {
299                         smallestPayloadMaxLength = currentPayloadMaxLength;
300                     }
301                 }
302             }
303         }
304
305         // Payload is [attribute id:2] [type:1] [data:N].
306         emberAfPutInt16uInResp(entry.attributeId);
307         emberAfPutInt8uInResp(dataType);
308
309 #if (BIGENDIAN_CPU)
310         if (isThisDataTypeSentLittleEndianOTA(dataType))
311         {
312             uint8_t i;
313             for (i = 0; i < dataSize; i++)
314             {
315                 emberAfPutInt8uInResp(readData[dataSize - i - 1]);
316             }
317         }
318         else
319         {
320             emberAfPutBlockInResp(readData, dataSize);
321         }
322 #else
323         emberAfPutBlockInResp(readData, dataSize);
324 #endif
325
326         // Store the last reported time and value so that we can track intervals
327         // and changes.  We only track changes for data types that are small enough
328         // for us to compare. For CHAR and OCTET strings, we substitute a 32-bit hash.
329         emAfPluginReportVolatileData[i].reportableChange = false;
330         emAfPluginReportVolatileData[i].lastReportTimeMs = static_cast<uint32_t>(chip::System::Layer::GetClock_MonotonicMS());
331         uint32_t stringHash                              = 0;
332         uint8_t * copyData                               = readData;
333         uint16_t copySize                                = dataSize;
334         if (dataType == ZCL_OCTET_STRING_ATTRIBUTE_TYPE || dataType == ZCL_CHAR_STRING_ATTRIBUTE_TYPE)
335         {
336             // dataSize was set above to count the string's length byte, in addition to string length.
337             // Compute hash on string value only.  Note that string length fits
338             // in one byte, so dataSize can't be larger than 256 right now.
339             stringHash = computeStringHash(readData + 1, static_cast<uint8_t>(dataSize - 1));
340             copyData   = (uint8_t *) &stringHash;
341             copySize   = sizeof(stringHash);
342         }
343         if (copySize <= sizeof(emAfPluginReportVolatileData[i].lastReportValue))
344         {
345             emAfPluginReportVolatileData[i].lastReportValue = 0;
346 #if (BIGENDIAN_CPU)
347             memmove(((uint8_t *) &emAfPluginReportVolatileData[i].lastReportValue +
348                      sizeof(emAfPluginReportVolatileData[i].lastReportValue) - copySize),
349                     copyData, copySize);
350 #else
351             memmove(&emAfPluginReportVolatileData[i].lastReportValue, copyData, copySize);
352 #endif
353         }
354     }
355
356     if (apsFrame != NULL)
357     {
358         conditionallySendReport(apsFrame->sourceEndpoint, apsFrame->clusterId);
359     }
360     scheduleTick();
361 }
362
363 static void conditionallySendReport(EndpointId endpoint, ClusterId clusterId)
364 {
365     EmberStatus status;
366     if (emberAfIsDeviceEnabled(endpoint) || clusterId == ZCL_IDENTIFY_CLUSTER_ID)
367     {
368         status = emberAfSendCommandUnicastToBindingsWithCallback((EmberAfMessageSentFunction)(&retrySendReport));
369
370         // If the callback table is full, attempt to send the message with no
371         // callback.  Note that this could lead to a message failing to transmit
372         // with no notification to the user for any number of reasons (ex: hitting
373         // the message queue limit), but is better than not sending the message at
374         // all because the system hits its callback queue limit.
375         if (status == EMBER_TABLE_FULL)
376         {
377             emberAfSendCommandUnicastToBindings();
378         }
379
380 #ifdef EMBER_AF_PLUGIN_REPORTING_ENABLE_GROUP_BOUND_REPORTS
381         emberAfSendCommandMulticastToBindings();
382 #endif // EMBER_AF_PLUGIN_REPORTING_ENABLE_GROUP_BOUND_REPORTS
383     }
384 }
385
386 bool emberAfConfigureReportingCommandCallback(const EmberAfClusterCommand * cmd)
387 {
388     EmberStatus sendStatus;
389     uint16_t bufIndex = cmd->payloadStartIndex;
390     uint8_t frameControl, mask;
391     bool failures = false;
392
393     emberAfReportingPrint("%p: ", "CFG_RPT");
394     emberAfReportingDebugExec(emberAfDecodeAndPrintClusterWithMfgCode(cmd->apsFrame->clusterId, cmd->mfgCode));
395     emberAfReportingPrintln("");
396     emberAfReportingFlush();
397
398     if (cmd->direction == ZCL_DIRECTION_CLIENT_TO_SERVER)
399     {
400         frameControl = (ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS);
401         mask         = CLUSTER_MASK_SERVER;
402     }
403     else
404     {
405         frameControl = (ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS);
406         mask         = CLUSTER_MASK_CLIENT;
407     }
408
409     // The manufacturer-specfic version of the fill API only creates a
410     // manufacturer-specfic command if the manufacturer code is set.  For non-
411     // manufacturer-specfic reports, the manufacturer code is unset, so we can
412     // get away with using this API for both cases.
413     emberAfFillExternalManufacturerSpecificBuffer(frameControl, cmd->apsFrame->clusterId, cmd->mfgCode,
414                                                   ZCL_CONFIGURE_REPORTING_RESPONSE_COMMAND_ID, "");
415
416     // Each record in the command has at least a one-byte direction and a two-
417     // byte attribute id.  Additional fields are present depending on the value
418     // of the direction field.
419     while (bufIndex + 3 < cmd->bufLen)
420     {
421         AttributeId attributeId;
422         EmberAfReportingDirection direction;
423         EmberAfStatus status;
424
425         direction = (EmberAfReportingDirection) emberAfGetInt8u(cmd->buffer, bufIndex, cmd->bufLen);
426         bufIndex++;
427         attributeId = (AttributeId) emberAfGetInt16u(cmd->buffer, bufIndex, cmd->bufLen);
428         bufIndex    = static_cast<uint16_t>(bufIndex + 2);
429
430         emberAfReportingPrintln(" - direction:%x, attr:%2x", direction, attributeId);
431
432         switch (direction)
433         {
434         case EMBER_ZCL_REPORTING_DIRECTION_REPORTED: {
435             EmberAfAttributeMetadata * metadata;
436             EmberAfAttributeType dataType;
437             uint16_t minInterval, maxInterval;
438             uint32_t reportableChange = 0;
439             EmberAfPluginReportingEntry newEntry;
440
441             dataType = (EmberAfAttributeType) emberAfGetInt8u(cmd->buffer, bufIndex, cmd->bufLen);
442             bufIndex++;
443             minInterval = emberAfGetInt16u(cmd->buffer, bufIndex, cmd->bufLen);
444             bufIndex    = static_cast<uint16_t>(bufIndex + 2);
445             maxInterval = emberAfGetInt16u(cmd->buffer, bufIndex, cmd->bufLen);
446             bufIndex    = static_cast<uint16_t>(bufIndex + 2);
447
448             emberAfReportingPrintln("   type:%x, min:%2x, max:%2x", dataType, minInterval, maxInterval);
449             emberAfReportingFlush();
450
451             if (emberAfGetAttributeAnalogOrDiscreteType(dataType) == EMBER_AF_DATA_TYPE_ANALOG)
452             {
453                 uint8_t dataSize       = emberAfGetDataSize(dataType);
454                 uint64_t currentChange = emberAfGetInt(cmd->buffer, bufIndex, cmd->bufLen, dataSize);
455                 if (chip::CanCastTo<uint32_t>(currentChange))
456                 {
457                     reportableChange = static_cast<uint32_t>(emberAfGetInt(cmd->buffer, bufIndex, cmd->bufLen, dataSize));
458                 }
459                 else
460                 {
461                     status = EMBER_ZCL_STATUS_INVALID_DATA_TYPE;
462                     break;
463                 }
464
465                 emberAfReportingPrint("   change:");
466                 emberAfReportingPrintBuffer(cmd->buffer + bufIndex, dataSize, false);
467                 emberAfReportingPrintln("");
468
469                 bufIndex = static_cast<uint16_t>(bufIndex + dataSize);
470             }
471
472             // emberAfPluginReportingConfigureReportedAttribute handles non-
473             // existent attributes, but does not verify the attribute data type, so
474             // we need to check it here.
475             metadata = emberAfLocateAttributeMetadata(cmd->apsFrame->destinationEndpoint, cmd->apsFrame->clusterId, attributeId,
476                                                       mask, cmd->mfgCode);
477             if (metadata != NULL && metadata->attributeType != dataType)
478             {
479                 status = EMBER_ZCL_STATUS_INVALID_DATA_TYPE;
480             }
481             else
482             {
483                 // Add a reporting entry for a reported attribute.  The reports will
484                 // be sent from us to the source of the Configure Reporting command.
485                 newEntry.endpoint                       = cmd->apsFrame->destinationEndpoint;
486                 newEntry.clusterId                      = cmd->apsFrame->clusterId;
487                 newEntry.attributeId                    = attributeId;
488                 newEntry.mask                           = mask;
489                 newEntry.manufacturerCode               = cmd->mfgCode;
490                 newEntry.data.reported.minInterval      = minInterval;
491                 newEntry.data.reported.maxInterval      = maxInterval;
492                 newEntry.data.reported.reportableChange = reportableChange;
493                 status                                  = emberAfPluginReportingConfigureReportedAttribute(&newEntry);
494             }
495             break;
496         }
497         case EMBER_ZCL_REPORTING_DIRECTION_RECEIVED: {
498             uint16_t timeout = emberAfGetInt16u(cmd->buffer, bufIndex, cmd->bufLen);
499             bufIndex         = static_cast<uint16_t>(bufIndex + 2);
500
501             emberAfReportingPrintln("   timeout:%2x", timeout);
502
503             // Add a reporting entry from a received attribute.  The reports
504             // will be sent to us from the source of the Configure Reporting
505             // command.
506             status = configureReceivedAttribute(cmd, attributeId, mask, timeout);
507             break;
508         }
509         default:
510             // This will abort the processing (see below).
511             status = EMBER_ZCL_STATUS_INVALID_FIELD;
512             break;
513         }
514
515         // If a report cannot be configured, the status, direction, and
516         // attribute are added to the response.  If the failure was due to an
517         // invalid field, we have to abort after this record because we don't
518         // know how to interpret the rest of the data in the request.
519         if (status != EMBER_ZCL_STATUS_SUCCESS)
520         {
521             emberAfPutInt8uInResp(status);
522             emberAfPutInt8uInResp(direction);
523             emberAfPutInt16uInResp(attributeId);
524             failures = true;
525             if (status == EMBER_ZCL_STATUS_INVALID_FIELD)
526             {
527                 break;
528             }
529         }
530     }
531
532     // We just respond with SUCCESS if we made it through without failures.
533     if (!failures)
534     {
535         emberAfPutInt8uInResp(EMBER_ZCL_STATUS_SUCCESS);
536     }
537
538     sendStatus = emberAfSendResponse();
539     if (EMBER_SUCCESS != sendStatus)
540     {
541         emberAfReportingPrintln("Reporting: failed to send %s response: 0x%x", "configure_reporting", sendStatus);
542     }
543     return true;
544 }
545
546 bool emberAfReadReportingConfigurationCommandCallback(const EmberAfClusterCommand * cmd)
547 {
548     EmberStatus sendStatus;
549     uint16_t bufIndex = cmd->payloadStartIndex;
550     uint8_t frameControl, mask;
551
552     emberAfReportingPrint("%p: ", "READ_RPT_CFG");
553     emberAfReportingDebugExec(emberAfDecodeAndPrintClusterWithMfgCode(cmd->apsFrame->clusterId, cmd->mfgCode));
554     emberAfReportingPrintln("");
555     emberAfReportingFlush();
556
557     if (cmd->direction == ZCL_DIRECTION_CLIENT_TO_SERVER)
558     {
559         frameControl = (ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS);
560         mask         = CLUSTER_MASK_SERVER;
561     }
562     else
563     {
564         frameControl = (ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS);
565         mask         = CLUSTER_MASK_CLIENT;
566     }
567
568     // The manufacturer-specfic version of the fill API only creates a
569     // manufacturer-specfic command if the manufacturer code is set.  For non-
570     // manufacturer-specfic reports, the manufacturer code is unset, so we can
571     // get away with using this API for both cases.
572     emberAfFillExternalManufacturerSpecificBuffer(frameControl, cmd->apsFrame->clusterId, cmd->mfgCode,
573                                                   ZCL_READ_REPORTING_CONFIGURATION_RESPONSE_COMMAND_ID, "");
574
575     // Each record in the command has a one-byte direction and a two-byte
576     // attribute id.
577     while (bufIndex + 3 <= cmd->bufLen)
578     {
579         AttributeId attributeId;
580         EmberAfAttributeMetadata * metadata = NULL;
581         EmberAfPluginReportingEntry entry;
582         EmberAfReportingDirection direction;
583         uint8_t i;
584         bool found = false;
585
586         direction = (EmberAfReportingDirection) emberAfGetInt8u(cmd->buffer, bufIndex, cmd->bufLen);
587         bufIndex++;
588         attributeId = (AttributeId) emberAfGetInt16u(cmd->buffer, bufIndex, cmd->bufLen);
589         bufIndex    = static_cast<uint16_t>(bufIndex + 2);
590
591         switch (direction)
592         {
593         case EMBER_ZCL_REPORTING_DIRECTION_REPORTED:
594         case EMBER_ZCL_REPORTING_DIRECTION_RECEIVED:
595             metadata = emberAfLocateAttributeMetadata(cmd->apsFrame->destinationEndpoint, cmd->apsFrame->clusterId, attributeId,
596                                                       mask, cmd->mfgCode);
597             if (metadata == NULL)
598             {
599                 emberAfPutInt8uInResp(EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE);
600                 emberAfPutInt8uInResp(direction);
601                 emberAfPutInt16uInResp(attributeId);
602                 continue;
603             }
604             break;
605         default:
606             emberAfPutInt8uInResp(EMBER_ZCL_STATUS_INVALID_FIELD);
607             emberAfPutInt8uInResp(direction);
608             emberAfPutInt16uInResp(attributeId);
609             continue;
610         }
611
612         // 075123r03 seems to suggest that SUCCESS is returned even if reporting
613         // isn't configured for the requested attribute.  The individual fields
614         // of the response for this attribute get populated with defaults.
615         // CCB 1854 removes the ambiguity and requires NOT_FOUND to be returned in
616         // the status field and all fields except direction and attribute identifier
617         // to be omitted if there is no report configuration found.
618         for (i = 0; i < REPORT_TABLE_SIZE; i++)
619         {
620             emAfPluginReportingGetEntry(i, &entry);
621             if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID)
622             {
623                 continue;
624             }
625             if (entry.direction == direction && entry.endpoint == cmd->apsFrame->destinationEndpoint &&
626                 entry.clusterId == cmd->apsFrame->clusterId && entry.attributeId == attributeId && entry.mask == mask &&
627                 entry.manufacturerCode == cmd->mfgCode &&
628                 (entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED ||
629                  (entry.data.received.source == cmd->source && entry.data.received.endpoint == cmd->apsFrame->sourceEndpoint)))
630             {
631                 found = true;
632                 break;
633             }
634         }
635         // Attribute supported, reportable, no report configuration was found.
636         if (found == false)
637         {
638             emberAfPutInt8uInResp(EMBER_ZCL_STATUS_NOT_FOUND);
639             emberAfPutInt8uInResp(direction);
640             emberAfPutInt16uInResp(attributeId);
641             continue;
642         }
643         // Attribute supported, reportable, report configuration was found.
644         emberAfPutInt8uInResp(EMBER_ZCL_STATUS_SUCCESS);
645         emberAfPutInt8uInResp(direction);
646         emberAfPutInt16uInResp(attributeId);
647         switch (direction)
648         {
649         case EMBER_ZCL_REPORTING_DIRECTION_REPORTED:
650             if (metadata != NULL)
651             {
652                 emberAfPutInt8uInResp(metadata->attributeType);
653                 emberAfPutInt16uInResp(entry.data.reported.minInterval);
654                 emberAfPutInt16uInResp(entry.data.reported.maxInterval);
655                 if (emberAfGetAttributeAnalogOrDiscreteType(metadata->attributeType) == EMBER_AF_DATA_TYPE_ANALOG)
656                 {
657                     putReportableChangeInResp(&entry, metadata->attributeType);
658                 }
659             }
660             break;
661         case EMBER_ZCL_REPORTING_DIRECTION_RECEIVED:
662             emberAfPutInt16uInResp(entry.data.received.timeout);
663             break;
664         }
665     }
666
667     sendStatus = emberAfSendResponse();
668     if (EMBER_SUCCESS != sendStatus)
669     {
670         emberAfReportingPrintln("Reporting: failed to send %s response: 0x%x", "read_reporting_configuration", sendStatus);
671     }
672     return true;
673 }
674
675 EmberStatus emberAfClearReportTableCallback(void)
676 {
677     uint8_t i;
678     for (i = 0; i < REPORT_TABLE_SIZE; i++)
679     {
680         removeConfiguration(i);
681     }
682     emberEventControlSetInactive(&emberAfPluginReportingTickEventControl);
683     return EMBER_SUCCESS;
684 }
685
686 EmberStatus emAfPluginReportingRemoveEntry(uint8_t index)
687 {
688     EmberStatus status = EMBER_INDEX_OUT_OF_RANGE;
689     if (index < REPORT_TABLE_SIZE)
690     {
691         removeConfigurationAndScheduleTick(index);
692         status = EMBER_SUCCESS;
693     }
694     return status;
695 }
696
697 void emberAfReportingAttributeChangeCallback(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId, uint8_t mask,
698                                              uint16_t manufacturerCode, EmberAfAttributeType type, uint8_t * data)
699 {
700     uint8_t i;
701     for (i = 0; i < REPORT_TABLE_SIZE; i++)
702     {
703         EmberAfPluginReportingEntry entry;
704         emAfPluginReportingGetEntry(i, &entry);
705         if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID)
706         {
707             continue;
708         }
709         if (entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED && entry.endpoint == endpoint &&
710             entry.clusterId == clusterId && entry.attributeId == attributeId && entry.mask == mask &&
711             entry.manufacturerCode == manufacturerCode)
712         {
713             // For CHAR and OCTET strings, the string value may be too long to fit into the
714             // lastReportValue field (EmberAfDifferenceType), so instead we save the string's
715             // hash, and detect changes in string value based on unequal hash.
716             uint32_t stringHash = 0;
717             uint8_t dataSize    = emberAfGetDataSize(type);
718             uint8_t * dataRef   = data;
719             if (type == ZCL_OCTET_STRING_ATTRIBUTE_TYPE || type == ZCL_CHAR_STRING_ATTRIBUTE_TYPE)
720             {
721                 stringHash = computeStringHash(data + 1, emberAfStringLength(data));
722                 dataRef    = (uint8_t *) &stringHash;
723                 dataSize   = sizeof(stringHash);
724             }
725             // If we are reporting this particular attribute, we only care whether
726             // the new value meets the reportable change criteria.  If it does, we
727             // mark the entry as ready to report and reschedule the tick.  Whether
728             // the tick will be scheduled for immediate or delayed execution depends
729             // on the minimum reporting interval.  This is handled in the scheduler.
730             EmberAfDifferenceType difference =
731                 emberAfGetDifference(dataRef, emAfPluginReportVolatileData[i].lastReportValue, dataSize);
732             uint8_t analogOrDiscrete = emberAfGetAttributeAnalogOrDiscreteType(type);
733             if ((analogOrDiscrete == EMBER_AF_DATA_TYPE_DISCRETE && difference != 0) ||
734                 (analogOrDiscrete == EMBER_AF_DATA_TYPE_ANALOG && entry.data.reported.reportableChange <= difference))
735             {
736                 emAfPluginReportVolatileData[i].reportableChange = true;
737                 scheduleTick();
738             }
739             break;
740         }
741     }
742 }
743
744 bool emAfPluginReportingDoEntriesMatch(const EmberAfPluginReportingEntry * const entry1,
745                                        const EmberAfPluginReportingEntry * const entry2)
746 {
747     // Verify that the reporting parameters of both entries match.
748     // If the entries are for EMBER_ZCL_REPORTING_DIRECTION_REPORTED, the
749     // reporting configurations do not need to match.  If the direction is
750     // EMBER_ZCL_REPORTING_DIRECTION_RECEIVED, then the source and destination
751     // endpoints need to match.
752     if ((entry1->endpoint == entry2->endpoint) && (entry1->clusterId == entry2->clusterId) &&
753         (entry1->attributeId == entry2->attributeId) && (entry1->mask == entry2->mask) &&
754         (entry1->manufacturerCode == entry2->manufacturerCode) && (entry1->direction == entry2->direction) &&
755         ((entry1->direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED) ||
756          ((entry1->data.received.source == entry2->data.received.source) &&
757           (entry1->data.received.endpoint == entry2->data.received.endpoint))))
758     {
759         return true;
760     }
761     return false;
762 }
763
764 uint8_t emAfPluginReportingAddEntry(EmberAfPluginReportingEntry * newEntry)
765 {
766     uint8_t i;
767     EmberAfPluginReportingEntry oldEntry;
768
769     // If an entry already exists, or exists but with different parameters,
770     // overwrite it with the new entry to prevent pollution of the report table
771     for (i = 0; i < REPORT_TABLE_SIZE; i++)
772     {
773         emAfPluginReportingGetEntry(i, &oldEntry);
774         if (emAfPluginReportingDoEntriesMatch(&oldEntry, newEntry))
775         {
776             emAfPluginReportingSetEntry(i, newEntry);
777             return i;
778         }
779     }
780
781     // If no pre-existing entries were found, copy the new entry into the lowest
782     // indexed free spot in the reporting table
783     for (i = 0; i < REPORT_TABLE_SIZE; i++)
784     {
785         emAfPluginReportingGetEntry(i, &oldEntry);
786         if (oldEntry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID)
787         {
788             emAfPluginReportingSetEntry(i, newEntry);
789             return i;
790         }
791     }
792
793     // If no free spots were found, return the failure indicator
794     return 0xFF;
795 }
796
797 static void scheduleTick(void)
798 {
799     uint32_t delayMs = MAX_INT32U_VALUE;
800     uint8_t i;
801     for (i = 0; i < REPORT_TABLE_SIZE; i++)
802     {
803         EmberAfPluginReportingEntry entry;
804         emAfPluginReportingGetEntry(i, &entry);
805
806         if (entry.endpoint != EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID &&
807             entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED)
808         {
809             uint32_t minIntervalMs = (entry.data.reported.minInterval * MILLISECOND_TICKS_PER_SECOND);
810             uint32_t maxIntervalMs = (entry.data.reported.maxInterval * MILLISECOND_TICKS_PER_SECOND);
811             uint32_t elapsedMs =
812                 elapsedTimeInt32u(emAfPluginReportVolatileData[i].lastReportTimeMs, chip::System::Layer::GetClock_MonotonicMS());
813             uint32_t remainingMs = MAX_INT32U_VALUE;
814             if (emAfPluginReportVolatileData[i].reportableChange)
815             {
816                 remainingMs = (minIntervalMs < elapsedMs ? 0 : minIntervalMs - elapsedMs);
817             }
818             else if (maxIntervalMs)
819             {
820                 remainingMs = (maxIntervalMs < elapsedMs ? 0 : maxIntervalMs - elapsedMs);
821             }
822             if (remainingMs < delayMs)
823             {
824                 delayMs = remainingMs;
825             }
826         }
827     }
828     if (delayMs != MAX_INT32U_VALUE)
829     {
830         emberAfDebugPrintln("sched report event in %d ms", delayMs);
831         emberEventControlSetDelayMS(&emberAfPluginReportingTickEventControl, delayMs);
832     }
833     else
834     {
835         emberAfDebugPrintln("deactivate report event");
836         emberEventControlSetInactive(&emberAfPluginReportingTickEventControl);
837     }
838 }
839
840 static void removeConfiguration(uint8_t index)
841 {
842     EmberAfPluginReportingEntry entry;
843     emAfPluginReportingGetEntry(index, &entry);
844     entry.endpoint = EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID;
845     emAfPluginReportingSetEntry(index, &entry);
846     emberAfPluginReportingConfiguredCallback(&entry);
847 }
848
849 static void removeConfigurationAndScheduleTick(uint8_t index)
850 {
851     removeConfiguration(index);
852     scheduleTick();
853 }
854
855 EmberAfStatus emberAfPluginReportingConfigureReportedAttribute(const EmberAfPluginReportingEntry * newEntry)
856 {
857     EmberAfAttributeMetadata * metadata;
858     EmberAfPluginReportingEntry entry;
859     EmberAfStatus status;
860     uint8_t i, index = NULL_INDEX;
861     bool initialize = true;
862
863     // Verify that we support the attribute and that the data type matches.
864     metadata = emberAfLocateAttributeMetadata(newEntry->endpoint, newEntry->clusterId, newEntry->attributeId, newEntry->mask,
865                                               newEntry->manufacturerCode);
866     if (metadata == NULL)
867     {
868         return EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
869     }
870
871     // Verify the minimum and maximum intervals make sense.
872     if (newEntry->data.reported.maxInterval != 0 && (newEntry->data.reported.maxInterval < newEntry->data.reported.minInterval))
873     {
874         return EMBER_ZCL_STATUS_INVALID_VALUE;
875     }
876
877     // Check the table for an entry that matches this request and also watch for
878     // empty slots along the way.  If a report exists, it will be overwritten
879     // with the new configuration.  Otherwise, a new entry will be created and
880     // initialized.
881     for (i = 0; i < REPORT_TABLE_SIZE; i++)
882     {
883         emAfPluginReportingGetEntry(i, &entry);
884         if (entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED && entry.endpoint == newEntry->endpoint &&
885             entry.clusterId == newEntry->clusterId && entry.attributeId == newEntry->attributeId && entry.mask == newEntry->mask &&
886             entry.manufacturerCode == newEntry->manufacturerCode)
887         {
888             initialize = false;
889             index      = i;
890             break;
891         }
892         else if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID && index == NULL_INDEX)
893         {
894             index = i;
895         }
896     }
897
898     // If the maximum reporting interval is 0xFFFF, the device shall not issue
899     // reports for the attribute and the configuration information for that
900     // attribute need not be maintained.
901     if (newEntry->data.reported.maxInterval == 0xFFFF)
902     {
903         if (!initialize)
904         {
905             removeConfigurationAndScheduleTick(index);
906         }
907         return EMBER_ZCL_STATUS_SUCCESS;
908     }
909     // ZCL v6 Section 2.5.7.1.6 Maximum Reporting Interval Field
910     // If this value is set to 0x0000 and the minimum reporting interval field
911     // equals 0xffff, then the device SHALL revert back to its default reporting
912     // configuration. The reportable change field, if present, SHALL be set to
913     // zero.
914     // Verify special condition to reset the reporting configuration to defaults
915     // if the minimum == 0xFFFF and maximum == 0x0000
916     bool reset = false;
917     if ((newEntry->data.reported.maxInterval == 0x0000) && (newEntry->data.reported.minInterval == 0xFFFF))
918     {
919         // Get the configuration from the default configuration table for this
920         memmove(&entry, newEntry, sizeof(EmberAfPluginReportingEntry));
921         if (emberAfPluginReportingGetReportingConfigDefaults(&entry))
922         {
923             // Then it must be initialise with the default config - explicity
924             initialize = true;
925             reset      = true;
926         }
927     }
928
929     if (index == NULL_INDEX)
930     {
931         return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
932     }
933     else if (initialize)
934     {
935         entry.direction        = EMBER_ZCL_REPORTING_DIRECTION_REPORTED;
936         entry.endpoint         = newEntry->endpoint;
937         entry.clusterId        = newEntry->clusterId;
938         entry.attributeId      = newEntry->attributeId;
939         entry.mask             = newEntry->mask;
940         entry.manufacturerCode = newEntry->manufacturerCode;
941         if (index < REPORT_TABLE_SIZE)
942         {
943             emAfPluginReportVolatileData[index].lastReportTimeMs =
944                 static_cast<uint32_t>(chip::System::Layer::GetClock_MonotonicMS());
945             emAfPluginReportVolatileData[index].lastReportValue = 0;
946         }
947     }
948
949     // For new or updated entries, set the intervals and reportable change.
950     // Updated entries will retain all other settings configured previously.
951     if (false == reset)
952     {
953         entry.data.reported.minInterval      = newEntry->data.reported.minInterval;
954         entry.data.reported.maxInterval      = newEntry->data.reported.maxInterval;
955         entry.data.reported.reportableChange = newEntry->data.reported.reportableChange;
956     }
957     // Give the application a chance to review the configuration that we have
958     // been building up.  If the application rejects it, we just do not save the
959     // record.  If we were supposed to add a new configuration, it will not be
960     // created.  If we were supposed to update an existing configuration, we will
961     // keep the old one and just discard any changes.  So, in either case, life
962     // continues unchanged if the application rejects the configuration.
963     status = emberAfPluginReportingConfiguredCallback(&entry);
964     if (status == EMBER_ZCL_STATUS_SUCCESS)
965     {
966         emAfPluginReportingSetEntry(index, &entry);
967         scheduleTick();
968     }
969     return status;
970 }
971
972 static EmberAfStatus configureReceivedAttribute(const EmberAfClusterCommand * cmd, AttributeId attributeId, uint8_t mask,
973                                                 uint16_t timeout)
974 {
975     EmberAfPluginReportingEntry entry;
976     EmberAfStatus status;
977     uint8_t i, index = NULL_INDEX;
978     bool initialize = true;
979
980     // Check the table for an entry that matches this request and also watch for
981     // empty slots along the way.  If a report exists, it will be overwritten
982     // with the new configuration.  Otherwise, a new entry will be created and
983     // initialized.
984     for (i = 0; i < REPORT_TABLE_SIZE; i++)
985     {
986         emAfPluginReportingGetEntry(i, &entry);
987         if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID)
988         {
989             continue;
990         }
991         if (entry.direction == EMBER_ZCL_REPORTING_DIRECTION_RECEIVED && entry.endpoint == cmd->apsFrame->destinationEndpoint &&
992             entry.clusterId == cmd->apsFrame->clusterId && entry.attributeId == attributeId && entry.mask == mask &&
993             entry.manufacturerCode == cmd->mfgCode && entry.data.received.source == cmd->source &&
994             entry.data.received.endpoint == cmd->apsFrame->sourceEndpoint)
995         {
996             initialize = false;
997             index      = i;
998             break;
999         }
1000         else if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID && index == NULL_INDEX)
1001         {
1002             index = i;
1003         }
1004     }
1005
1006     if (index == NULL_INDEX)
1007     {
1008         return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
1009     }
1010     else if (initialize)
1011     {
1012         entry.direction              = EMBER_ZCL_REPORTING_DIRECTION_RECEIVED;
1013         entry.endpoint               = cmd->apsFrame->destinationEndpoint;
1014         entry.clusterId              = cmd->apsFrame->clusterId;
1015         entry.attributeId            = attributeId;
1016         entry.mask                   = mask;
1017         entry.manufacturerCode       = cmd->mfgCode;
1018         entry.data.received.source   = cmd->source;
1019         entry.data.received.endpoint = cmd->apsFrame->sourceEndpoint;
1020     }
1021
1022     // For new or updated entries, set the timeout.  Updated entries will retain
1023     // all other settings configured previously.
1024     entry.data.received.timeout = timeout;
1025
1026     // Give the application a chance to review the configuration that we have
1027     // been building up.  If the application rejects it, we just do not save the
1028     // record.  If we were supposed to add a new configuration, it will not be
1029     // created.  If we were supposed to update an existing configuration, we will
1030     // keep the old one and just discard any changes.  So, in either case, life
1031     // continues unchanged if the application rejects the configuration.  If the
1032     // application accepts the change, the tick does not have to be rescheduled
1033     // here because we don't do anything with received reports.
1034     status = emberAfPluginReportingConfiguredCallback(&entry);
1035     if (status == EMBER_ZCL_STATUS_SUCCESS)
1036     {
1037         emAfPluginReportingSetEntry(index, &entry);
1038     }
1039     return status;
1040 }
1041
1042 static void putReportableChangeInResp(const EmberAfPluginReportingEntry * entry, EmberAfAttributeType dataType)
1043 {
1044     uint8_t bytes = emberAfGetDataSize(dataType);
1045     if (entry == NULL)
1046     { // default, 0xFF...UL or 0x80...L
1047         for (; bytes > 0; bytes--)
1048         {
1049             uint8_t b = 0xFF;
1050             if (emberAfIsTypeSigned(dataType))
1051             {
1052                 b = (bytes == 1 ? 0x80 : 0x00);
1053             }
1054             emberAfPutInt8uInResp(b);
1055         }
1056     }
1057     else
1058     { // reportable change value
1059         uint32_t value = entry->data.reported.reportableChange;
1060         for (; bytes > 0; bytes--)
1061         {
1062             uint8_t b = EMBER_BYTE_0(value);
1063             emberAfPutInt8uInResp(b);
1064             value >>= 8;
1065         }
1066     }
1067 }
1068
1069 // Conditionally add reporting entry.
1070 // This is required to support setting up default reporting entries for
1071 // reportable attributes.
1072 static bool reportEntryDoesNotExist(const EmberAfPluginReportingEntry * newEntry)
1073 {
1074     uint8_t i;
1075     EmberAfPluginReportingEntry entry;
1076
1077     for (i = 0; i < REPORT_TABLE_SIZE; i++)
1078     {
1079         emAfPluginReportingGetEntry(i, &entry);
1080         if (emAfPluginReportingDoEntriesMatch(&entry, newEntry))
1081         {
1082             return false;
1083         }
1084     }
1085
1086     return true;
1087 }
1088
1089 uint8_t emAfPluginReportingConditionallyAddReportingEntry(EmberAfPluginReportingEntry * newEntry)
1090 {
1091     if (reportEntryDoesNotExist(newEntry))
1092     {
1093         return emAfPluginReportingAddEntry(newEntry);
1094     }
1095     return 0;
1096 }