Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / src / app / clusters / ias-zone-server / ias-zone-server.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 This is the source for the plugin used to
37  *add an IAS Zone cluster server to a project.  This
38  *source handles zone enrollment and storing of
39  * attributes from a CIE device, and provides an API
40  *for different plugins to post updated zone status
41  *values.
42  *******************************************************************************
43  ******************************************************************************/
44
45 // *****************************************************************************
46 // * ias-zone-server.c
47 // *
48 // *
49 // *
50 // * Copyright 2015 Silicon Laboratories, Inc.                              *80*
51 // *****************************************************************************
52
53 #include "ias-zone-server.h"
54 #include <app/util/af-event.h>
55 #include <app/util/af.h>
56 #include <app/util/binding-table.h>
57 #include <system/SystemLayer.h>
58
59 #include "gen/att-storage.h"
60 #include "gen/attribute-id.h"
61 #include "gen/attribute-type.h"
62 #include "gen/callback.h"
63 #include "gen/cluster-id.h"
64 #include "gen/command-id.h"
65
66 using namespace chip;
67
68 #define UNDEFINED_ZONE_ID 0xFF
69 #define DELAY_TIMER_MS (1 * MILLISECOND_TICKS_PER_SECOND)
70 #define IAS_ZONE_SERVER_PAYLOAD_COMMAND_IDX 0x02
71 #define ZCL_FRAME_CONTROL_IDX 0x00
72
73 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
74 #if defined(EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER)
75 #define NUM_QUEUE_ENTRIES EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER_QUEUE_SIZE
76 #else
77 #define NUM_QUEUE_ENTRIES EMBER_AF_PLUGIN_IAS_ZONE_SERVER_QUEUE_DEPTH
78 #endif
79 #else
80 #define NUM_QUEUE_ENTRIES 0
81 #endif
82
83 #define DEFAULT_ENROLLMENT_METHOD EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_REQUEST
84
85 // TODO: Need to figure out what needs to happen wrt HAL tokens here, but for
86 // now define ESZP_HOST to disable it.  See
87 // https://github.com/project-chip/connectedhomeip/issues/3275
88 #define EZSP_HOST
89
90 typedef struct
91 {
92     EndpointId endpoint;
93     uint16_t status;
94     uint32_t eventTimeMs;
95 } IasZoneStatusQueueEntry;
96
97 typedef struct
98 {
99     uint8_t entriesInQueue;
100     uint8_t startIdx;
101     uint8_t lastIdx;
102     IasZoneStatusQueueEntry buffer[NUM_QUEUE_ENTRIES];
103 } IasZoneStatusQueue;
104
105 //-----------------------------------------------------------------------------
106 // Globals
107
108 EmberEventControl emberAfPluginIasZoneServerManageQueueEventControl;
109 static EmberAfIasZoneEnrollmentMode enrollmentMethod;
110
111 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
112 IasZoneStatusQueue messageQueue;
113
114 // Status queue retry parameters
115 typedef struct
116 {
117     IasZoneStatusQueueRetryConfig config;
118     uint32_t currentBackoffTimeSec;
119     uint8_t currentRetryCount;
120 } IasZoneStatusQueueRetryParameters;
121
122 // Set up status queue retry parameters.
123 IasZoneStatusQueueRetryParameters queueRetryParams = {
124     .config = { .firstBackoffTimeSec   = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC,
125                 .backoffSeqCommonRatio = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_BACKOFF_SEQUENCE_COMMON_RATIO,
126                 .maxBackoffTimeSec     = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_BACKOFF_TIME_SEC,
127 #ifdef EMBER_AF_PLUGIN_IAS_ZONE_SERVER_UNLIMITED_RETRIES
128                 .unlimitedRetries = true,
129 #else
130                 .unlimitedRetries = false,
131 #endif
132                 .maxRetryAttempts = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_RETRY_ATTEMPTS },
133     .currentBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC,
134     .currentRetryCount     = 0,
135 };
136
137 static void resetCurrentQueueRetryParams(void)
138 {
139     queueRetryParams.currentRetryCount     = 0;
140     queueRetryParams.currentBackoffTimeSec = queueRetryParams.config.firstBackoffTimeSec;
141 }
142
143 #endif // EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE
144
145 //-----------------------------------------------------------------------------
146 // Forward declarations
147
148 static void setZoneId(EndpointId endpoint, uint8_t zoneId);
149 static bool areZoneServerAttributesTokenized(EndpointId endpoint);
150 static bool isValidEnrollmentMode(EmberAfIasZoneEnrollmentMode method);
151 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
152 static uint16_t computeElapsedTimeQs(IasZoneStatusQueueEntry * entry);
153 static void bufferInit(IasZoneStatusQueue * ring);
154 static int16_t copyToBuffer(IasZoneStatusQueue * ring, const IasZoneStatusQueueEntry * entry);
155 static int16_t popFromBuffer(IasZoneStatusQueue * ring, IasZoneStatusQueueEntry * entry);
156 #endif
157
158 // TODO: https://github.com/project-chip/connectedhomeip/issues/3276 needs to be
159 // fixed to implement this for real.
160 EmberNetworkStatus emberAfNetworkState(void)
161 {
162     return EMBER_JOINED_NETWORK;
163 }
164
165 //-----------------------------------------------------------------------------
166 // Functions
167
168 static EmberStatus sendToClient(EndpointId endpoint)
169 {
170     EmberStatus status;
171
172     // If the device is not a network, there is no one to send to, so do nothing
173     if (emberAfNetworkState() != EMBER_JOINED_NETWORK)
174     {
175         return EMBER_NETWORK_DOWN;
176     }
177
178     // Remote endpoint need not be set, since it will be provided by the call to
179     // emberAfSendCommandUnicastToBindings()
180     emberAfSetCommandEndpoints(endpoint, 0);
181
182     // A binding table entry is created on Zone Enrollment for each endpoint, so
183     // a simple call to SendCommandUnicastToBinding will handle determining the
184     // destination endpoint, address, etc for us.
185     status = emberAfSendCommandUnicastToBindings();
186
187     if (EMBER_SUCCESS != status)
188     {
189         return status;
190     }
191
192     return status;
193 }
194
195 static void enrollWithClient(EndpointId endpoint)
196 {
197     EmberStatus status;
198     emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT), ZCL_IAS_ZONE_CLUSTER_ID,
199                               ZCL_ZONE_ENROLL_REQUEST_COMMAND_ID, "vv", EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ZONE_TYPE,
200                               EMBER_AF_MANUFACTURER_CODE);
201     status = sendToClient(endpoint);
202     if (status == EMBER_SUCCESS)
203     {
204         emberAfIasZoneClusterPrintln("Sent enroll request to IAS Zone client.");
205     }
206     else
207     {
208         emberAfIasZoneClusterPrintln("Error sending enroll request: 0x%x\n", status);
209     }
210 }
211
212 EmberAfStatus emberAfIasZoneClusterServerPreAttributeChangedCallback(EndpointId endpoint, AttributeId attributeId,
213                                                                      EmberAfAttributeType attributeType, uint8_t size,
214                                                                      uint8_t * value)
215 {
216     uint8_t i;
217     bool zeroAddress;
218     EmberBindingTableEntry bindingEntry;
219     EmberBindingTableEntry currentBind;
220     NodeId destNodeId;
221     uint8_t ieeeAddress[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
222
223     // If this is not a CIE Address write, the CIE address has already been
224     // written, or the IAS Zone server is already enrolled, do nothing.
225     if (attributeId != ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID || emberAfCurrentCommand() == NULL)
226     {
227         return EMBER_ZCL_STATUS_SUCCESS;
228     }
229
230     memcpy(&destNodeId, value, sizeof(NodeId));
231
232     // Create the binding table entry
233
234     // This code assumes that the endpoint and device that is setting the CIE
235     // address is the CIE device itself, and as such the remote endpoint to bind
236     // to is the endpoint that generated the attribute change.  This
237     // assumption is made based on analysis of the behavior of CIE devices
238     // currently existing in the field.
239     bindingEntry.type      = EMBER_UNICAST_BINDING;
240     bindingEntry.local     = endpoint;
241     bindingEntry.clusterId = ZCL_IAS_ZONE_CLUSTER_ID;
242     bindingEntry.remote    = emberAfCurrentCommand()->apsFrame->sourceEndpoint;
243     bindingEntry.nodeId    = destNodeId;
244
245     // Cycle through the binding table until we find a valid entry that is not
246     // being used, then use the created entry to make the bind.
247     for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++)
248     {
249         if (emberGetBinding(i, &currentBind) != EMBER_SUCCESS)
250         {
251             // break out of the loop to ensure that an error message still prints
252             break;
253         }
254         if (currentBind.type != EMBER_UNUSED_BINDING)
255         {
256             // If the binding table entry created based on the response already exists
257             // do nothing.
258             if ((currentBind.local == bindingEntry.local) && (currentBind.clusterId == bindingEntry.clusterId) &&
259                 (currentBind.remote == bindingEntry.remote) && (currentBind.type == bindingEntry.type))
260             {
261                 break;
262             }
263             // If this spot in the binding table already exists, move on to the next
264             continue;
265         }
266         else
267         {
268             emberSetBinding(i, &bindingEntry);
269             break;
270         }
271     }
272
273     zeroAddress = true;
274     emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID, (uint8_t *) ieeeAddress, 8);
275     for (i = 0; i < 8; i++)
276     {
277         if (ieeeAddress[i] != 0)
278         {
279             zeroAddress = false;
280         }
281     }
282     emberAfAppPrint("\nzero address: %d\n", zeroAddress);
283
284     if ((zeroAddress == true) && (enrollmentMethod == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_REQUEST))
285     {
286         // Only send the enrollment request if the mode is AUTO-ENROLL-REQUEST.
287         // We need to delay to get around a bug where we can't send a command
288         // at this point because then the Write Attributes response will not
289         // be sent.  But we also delay to give the client time to configure us.
290         emberAfIasZoneClusterPrintln("Sending enrollment after %d ms", DELAY_TIMER_MS);
291         emberAfScheduleServerTickExtended(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, DELAY_TIMER_MS, EMBER_AF_SHORT_POLL,
292                                           EMBER_AF_STAY_AWAKE);
293     }
294
295     return EMBER_ZCL_STATUS_SUCCESS;
296 }
297
298 EmberAfStatus emberAfPluginIasZoneClusterSetEnrollmentMethod(EndpointId endpoint, EmberAfIasZoneEnrollmentMode method)
299 {
300     EmberAfStatus status;
301
302     if (emberAfIasZoneClusterAmIEnrolled(endpoint))
303     {
304         emberAfIasZoneClusterPrintln("Error: Already enrolled");
305         status = EMBER_ZCL_STATUS_NOT_AUTHORIZED;
306     }
307     else if (!isValidEnrollmentMode(method))
308     {
309         emberAfIasZoneClusterPrintln("Invalid IAS Zone Server Enrollment Mode: %d", method);
310         status = EMBER_ZCL_STATUS_INVALID_VALUE;
311     }
312     else
313     {
314         enrollmentMethod = method;
315 #ifndef EZSP_HOST
316         halCommonSetToken(TOKEN_PLUGIN_IAS_ZONE_SERVER_ENROLLMENT_METHOD, &enrollmentMethod);
317 #endif
318         emberAfIasZoneClusterPrintln("IAS Zone Server Enrollment Mode: %d", method);
319         status = EMBER_ZCL_STATUS_SUCCESS;
320     }
321     return status;
322 }
323
324 static bool isValidEnrollmentMode(EmberAfIasZoneEnrollmentMode method)
325 {
326     return ((method == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_TRIP_TO_PAIR) ||
327             (method == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_AUTO_ENROLLMENT_RESPONSE) ||
328             (method == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_REQUEST));
329 }
330
331 bool emberAfIasZoneClusterAmIEnrolled(EndpointId endpoint)
332 {
333     EmberAfIasZoneState zoneState = EMBER_ZCL_IAS_ZONE_STATE_NOT_ENROLLED; // Clear this out completely.
334     EmberAfStatus status;
335     status =
336         emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATE_ATTRIBUTE_ID, (unsigned char *) &zoneState,
337                                    1); // uint8_t size
338
339     return (status == EMBER_ZCL_STATUS_SUCCESS && zoneState == EMBER_ZCL_IAS_ZONE_STATE_ENROLLED);
340 }
341
342 static void updateEnrollState(EndpointId endpoint, bool enrolled)
343 {
344     EmberAfIasZoneState zoneState = (enrolled ? EMBER_ZCL_IAS_ZONE_STATE_ENROLLED : EMBER_ZCL_IAS_ZONE_STATE_NOT_ENROLLED);
345
346     emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATE_ATTRIBUTE_ID, (uint8_t *) &zoneState,
347                                 ZCL_INT8U_ATTRIBUTE_TYPE);
348     emberAfIasZoneClusterPrintln("IAS Zone Server State: %pEnrolled", (enrolled ? "" : "NOT "));
349 }
350
351 bool emberAfIasZoneClusterZoneEnrollResponseCallback(uint8_t enrollResponseCode, uint8_t zoneId)
352 {
353     EndpointId endpoint;
354     uint8_t epZoneId;
355     EmberAfStatus status;
356
357     endpoint = emberAfCurrentEndpoint();
358     status   = emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, &epZoneId, sizeof(uint8_t));
359     if (status == EMBER_ZCL_STATUS_SUCCESS)
360     {
361         if (enrollResponseCode == EMBER_ZCL_IAS_ENROLL_RESPONSE_CODE_SUCCESS)
362         {
363             updateEnrollState(endpoint, true);
364             setZoneId(endpoint, zoneId);
365         }
366         else
367         {
368             updateEnrollState(endpoint, false);
369             setZoneId(endpoint, UNDEFINED_ZONE_ID);
370         }
371
372         return true;
373     }
374
375     emberAfAppPrintln("ERROR: IAS Zone Server unable to read zone ID attribute");
376     return true;
377 }
378
379 static EmberStatus sendZoneUpdate(uint16_t zoneStatus, uint16_t timeSinceStatusOccurredQs, EndpointId endpoint)
380 {
381     EmberStatus status;
382
383     if (!emberAfIasZoneClusterAmIEnrolled(endpoint))
384     {
385         return EMBER_INVALID_CALL;
386     }
387     emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT), ZCL_IAS_ZONE_CLUSTER_ID,
388                               ZCL_ZONE_STATUS_CHANGE_NOTIFICATION_COMMAND_ID, "vuuv", zoneStatus,
389                               0 /*extended status, must be zero per spec*/, emberAfPluginIasZoneServerGetZoneId(endpoint),
390                               timeSinceStatusOccurredQs /* called "delay" in the spec */);
391     status = sendToClient(endpoint);
392
393     return status;
394 }
395
396 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
397 static void addNewEntryToQueue(const IasZoneStatusQueueEntry * newEntry)
398 {
399     emberAfIasZoneClusterPrintln("Adding new entry to queue");
400     copyToBuffer(&messageQueue, newEntry);
401 }
402 #endif
403
404 EmberStatus emberAfPluginIasZoneServerUpdateZoneStatus(EndpointId endpoint, uint16_t newStatus, uint16_t timeSinceStatusOccurredQs)
405 {
406 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
407     IasZoneStatusQueueEntry newBufferEntry;
408     newBufferEntry.endpoint    = endpoint;
409     newBufferEntry.status      = newStatus;
410     newBufferEntry.eventTimeMs = System::Layer::GetClock_MonotonicMS();
411 #endif
412     EmberStatus sendStatus;
413
414     emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATUS_ATTRIBUTE_ID, (uint8_t *) &newStatus,
415                                 ZCL_INT16U_ATTRIBUTE_TYPE);
416
417     if (enrollmentMethod == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_TRIP_TO_PAIR)
418     {
419         // If unenrolled, send Zone Enroll Request command.
420         if (!emberAfIasZoneClusterAmIEnrolled(endpoint))
421         {
422             emberAfScheduleServerTick(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, DELAY_TIMER_MS);
423             // Don't send the zone status update since not enrolled.
424             return EMBER_SUCCESS;
425         }
426     }
427
428 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
429     // If there are items in the queue waiting to send, this event should not
430     // be transmitted, as that could cause the client to receive the events out
431     // of order.  Instead, just add the device to the queue
432     if (messageQueue.entriesInQueue == 0)
433     {
434         sendStatus = sendZoneUpdate(newStatus, timeSinceStatusOccurredQs, endpoint);
435     }
436     else
437     {
438         // Add a new element to the status queue and depending on the network state
439         // either try to resend the first element in the queue immediately or try to
440         // restart the parent research pattern.
441         addNewEntryToQueue(&newBufferEntry);
442
443         EmberNetworkStatus networkState = emberAfNetworkState();
444
445         if (networkState == EMBER_JOINED_NETWORK_NO_PARENT)
446         {
447             emberAfStartMoveCallback();
448         }
449         else if (networkState == EMBER_JOINED_NETWORK)
450         {
451             resetCurrentQueueRetryParams();
452             emberEventControlSetActive(&emberAfPluginIasZoneServerManageQueueEventControl);
453         }
454
455         return EMBER_SUCCESS;
456     }
457
458 #else
459     sendStatus = sendZoneUpdate(newStatus, timeSinceStatusOccurredQs, endpoint);
460 #endif
461
462     if (sendStatus == EMBER_SUCCESS)
463     {
464 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
465         // Add a new entry to the zoneUpdate buffer
466         addNewEntryToQueue(&newBufferEntry);
467 #endif
468     }
469     else
470     {
471         // If we're not on a network and never were, we don't need to do anything.
472         // If we used to be on a network and can't talk to our parent, we should
473         // try to rejoin the network and add the message to the queue
474         if (emberAfNetworkState() == EMBER_JOINED_NETWORK_NO_PARENT)
475         {
476             emberAfStartMoveCallback();
477 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
478             // Add a new entry to the zoneUpdate buffer
479             addNewEntryToQueue(&newBufferEntry);
480 #endif
481         }
482         emberAfIasZoneClusterPrintln("Failed to send IAS Zone update. Err 0x%x", sendStatus);
483     }
484     return sendStatus;
485 }
486
487 void emberAfPluginIasZoneServerManageQueueEventHandler(void)
488 {
489 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
490     IasZoneStatusQueueEntry * bufferStart;
491     uint16_t status;
492     uint16_t elapsedTimeQs;
493     uint16_t airTimeRemainingMs;
494
495     // If the queue was emptied without our interaction, do nothing
496     if (messageQueue.entriesInQueue == 0)
497     {
498         emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
499         return;
500     }
501
502     // Otherwise, pull out the first item and attempt to retransmit it.  The
503     // message complete callback will handle removing items from the queue
504
505     // To prevent an activity storm from flooding with retry requests, only
506     // re-send a message if it's been at least
507     // EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MIN_OTA_TIME_MS since it was sent.
508     bufferStart   = &(messageQueue.buffer[messageQueue.startIdx]);
509     elapsedTimeQs = computeElapsedTimeQs(bufferStart);
510
511     if (elapsedTimeQs < (EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MIN_OTA_TIME_MS / (MILLISECOND_TICKS_PER_SECOND / 4)))
512     {
513         airTimeRemainingMs = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MIN_OTA_TIME_MS - (elapsedTimeQs * MILLISECOND_TICKS_PER_SECOND / 4);
514         emberAfIasZoneClusterPrintln("Not enough time passed for a retry, sleeping %d more mS", airTimeRemainingMs);
515         emberEventControlSetDelayMS(emberAfPluginIasZoneServerManageQueueEventControl, airTimeRemainingMs);
516     }
517     else
518     {
519         status = bufferStart->status;
520         emberAfIasZoneClusterPrintln("Attempting to resend a queued zone status update (status: 0x%02X, "
521                                      "event time (s): %d) with time of %d. Retry count: %d",
522                                      bufferStart->status, bufferStart->eventTimeMs / MILLISECOND_TICKS_PER_SECOND, elapsedTimeQs,
523                                      queueRetryParams.currentRetryCount);
524         sendZoneUpdate(status, elapsedTimeQs, bufferStart->endpoint);
525         emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
526     }
527 #else
528     emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
529 #endif
530 }
531
532 void emberAfIasZoneClusterServerInitCallback(EndpointId endpoint)
533 {
534     EmberAfIasZoneType zoneType;
535     if (!areZoneServerAttributesTokenized(endpoint))
536     {
537         emberAfAppPrint("WARNING: ATTRIBUTES ARE NOT BEING STORED IN FLASH! ");
538         emberAfAppPrintln("DEVICE WILL NOT FUNCTION PROPERLY AFTER REBOOTING!!");
539     }
540
541 #ifndef EZSP_HOST
542     halCommonGetToken(&enrollmentMethod, TOKEN_PLUGIN_IAS_ZONE_SERVER_ENROLLMENT_METHOD);
543 #else
544     enrollmentMethod = DEFAULT_ENROLLMENT_METHOD;
545 #endif
546     if (!isValidEnrollmentMode(enrollmentMethod))
547     {
548         // Default Enrollment Method to AUTO-ENROLL-REQUEST.
549         enrollmentMethod = DEFAULT_ENROLLMENT_METHOD;
550     }
551
552 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
553     bufferInit(&messageQueue);
554 #endif
555
556     zoneType = (EmberAfIasZoneType) EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ZONE_TYPE;
557     emberAfWriteAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_TYPE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, (uint8_t *) &zoneType,
558                           ZCL_INT16U_ATTRIBUTE_TYPE);
559
560     emberAfPluginIasZoneServerUpdateZoneStatus(endpoint,
561                                                0,  // status: All alarms cleared
562                                                0); // time since status occurred
563 }
564
565 void emberAfIasZoneClusterServerTickCallback(EndpointId endpoint)
566 {
567     enrollWithClient(endpoint);
568 }
569
570 uint8_t emberAfPluginIasZoneServerGetZoneId(EndpointId endpoint)
571 {
572     uint8_t zoneId = UNDEFINED_ZONE_ID;
573     emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, &zoneId,
574                                emberAfGetDataSize(ZCL_INT8U_ATTRIBUTE_TYPE));
575     return zoneId;
576 }
577
578 //------------------------------------------------------------------------------
579 //
580 // This function will verify that all attributes necessary for the IAS zone
581 // server to properly retain functionality through a power failure are
582 // tokenized.
583 //
584 //------------------------------------------------------------------------------
585 static bool areZoneServerAttributesTokenized(EndpointId endpoint)
586 {
587     EmberAfAttributeMetadata * metadata;
588
589     metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID,
590                                               CLUSTER_MASK_SERVER, EMBER_AF_NULL_MANUFACTURER_CODE);
591     if (!emberAfAttributeIsTokenized(metadata))
592     {
593         return false;
594     }
595
596     metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
597                                               EMBER_AF_NULL_MANUFACTURER_CODE);
598     if (!emberAfAttributeIsTokenized(metadata))
599     {
600         return false;
601     }
602
603     metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_TYPE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
604                                               EMBER_AF_NULL_MANUFACTURER_CODE);
605     if (!emberAfAttributeIsTokenized(metadata))
606     {
607         return false;
608     }
609
610     metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
611                                               EMBER_AF_NULL_MANUFACTURER_CODE);
612     if (!emberAfAttributeIsTokenized(metadata))
613     {
614         return false;
615     }
616
617     return true;
618 }
619
620 static void setZoneId(EndpointId endpoint, uint8_t zoneId)
621 {
622     emberAfIasZoneClusterPrintln("IAS Zone Server Zone ID: 0x%X", zoneId);
623     emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, &zoneId, ZCL_INT8U_ATTRIBUTE_TYPE);
624 }
625
626 static void unenrollSecurityDevice(EndpointId endpoint)
627 {
628     uint8_t ieeeAddress[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
629     uint16_t zoneType     = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ZONE_TYPE;
630
631     emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID, (uint8_t *) ieeeAddress,
632                                 ZCL_IEEE_ADDRESS_ATTRIBUTE_TYPE);
633
634     emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_TYPE_ATTRIBUTE_ID, (uint8_t *) &zoneType,
635                                 ZCL_INT16U_ATTRIBUTE_TYPE);
636
637     setZoneId(endpoint, UNDEFINED_ZONE_ID);
638     // Restore the enrollment method back to its default value.
639     emberAfPluginIasZoneClusterSetEnrollmentMethod(endpoint, DEFAULT_ENROLLMENT_METHOD);
640     updateEnrollState(endpoint, false); // enrolled?
641 }
642
643 // If you leave the network, unenroll yourself.
644 void emberAfPluginIasZoneServerStackStatusCallback(EmberStatus status)
645 {
646     EndpointId endpoint;
647     uint8_t networkIndex;
648     uint8_t i;
649
650     // If the device has left the network, unenroll all endpoints on the device
651     // that are servers of the IAS Zone Cluster
652     if (status == EMBER_NETWORK_DOWN && emberAfNetworkState() == EMBER_NO_NETWORK)
653     {
654         for (i = 0; i < emberAfEndpointCount(); i++)
655         {
656             endpoint     = emberAfEndpointFromIndex(i);
657             networkIndex = emberAfNetworkIndexFromEndpointIndex(i);
658             if (networkIndex == 0 /* emberGetCurrentNetwork() */ && emberAfContainsServer(endpoint, ZCL_IAS_ZONE_CLUSTER_ID))
659             {
660                 unenrollSecurityDevice(endpoint);
661             }
662         }
663     }
664     else if (status == EMBER_NETWORK_UP)
665     {
666 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
667         // If we're reconnecting, send any items still in the queue
668         emberAfIasZoneClusterPrintln("Rejoined network, retransmiting any queued event");
669         emberEventControlSetActive(&emberAfPluginIasZoneServerManageQueueEventControl);
670 #endif
671     }
672 }
673
674 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
675 EmberStatus emberAfIasZoneServerConfigStatusQueueRetryParams(IasZoneStatusQueueRetryConfig * retryConfig)
676 {
677     if (!(retryConfig->firstBackoffTimeSec) || (!retryConfig->backoffSeqCommonRatio) ||
678         (retryConfig->maxBackoffTimeSec < retryConfig->firstBackoffTimeSec) ||
679         (retryConfig->maxBackoffTimeSec > IAS_ZONE_STATUS_QUEUE_RETRY_ABS_MAX_BACKOFF_TIME_SEC) || (!retryConfig->maxRetryAttempts))
680     {
681         return EMBER_BAD_ARGUMENT;
682     }
683
684     queueRetryParams.config.firstBackoffTimeSec   = retryConfig->firstBackoffTimeSec;
685     queueRetryParams.config.backoffSeqCommonRatio = retryConfig->backoffSeqCommonRatio;
686     queueRetryParams.config.maxBackoffTimeSec     = retryConfig->maxBackoffTimeSec;
687     queueRetryParams.config.unlimitedRetries      = retryConfig->unlimitedRetries;
688     queueRetryParams.config.maxRetryAttempts      = retryConfig->maxRetryAttempts;
689
690     queueRetryParams.currentBackoffTimeSec = retryConfig->firstBackoffTimeSec;
691     queueRetryParams.currentRetryCount     = 0;
692
693     return EMBER_SUCCESS;
694 }
695
696 void emberAfIasZoneServerSetStatusQueueRetryParamsToDefault(void)
697 {
698     queueRetryParams.config.firstBackoffTimeSec   = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC;
699     queueRetryParams.config.backoffSeqCommonRatio = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_BACKOFF_SEQUENCE_COMMON_RATIO;
700     queueRetryParams.config.maxBackoffTimeSec     = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_BACKOFF_TIME_SEC;
701 #ifdef EMBER_AF_PLUGIN_IAS_ZONE_SERVER_UNLIMITED_RETRIES
702     queueRetryParams.config.unlimitedRetries = true;
703 #else
704     queueRetryParams.config.unlimitedRetries = false;
705 #endif
706     queueRetryParams.config.maxRetryAttempts = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_RETRY_ATTEMPTS;
707
708     queueRetryParams.currentBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC;
709     queueRetryParams.currentRetryCount     = 0;
710 }
711
712 void emberAfIasZoneServerDiscardPendingEventsInStatusQueue(void)
713 {
714     emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
715     bufferInit(&messageQueue);
716     resetCurrentQueueRetryParams();
717 }
718
719 #if defined(EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER)
720 EmberStatus emberAfWwahAppEventRetryManagerConfigBackoffParamsCallback(uint8_t firstBackoffTimeSeconds,
721                                                                        uint8_t backoffSeqCommonRatio,
722                                                                        uint32_t maxBackoffTimeSeconds,
723                                                                        uint8_t maxRedeliveryAttempts)
724 {
725     IasZoneStatusQueueRetryConfig retryConfig = { firstBackoffTimeSeconds, backoffSeqCommonRatio, maxBackoffTimeSeconds,
726                                                   (maxRedeliveryAttempts == 0xFF), maxRedeliveryAttempts };
727
728     // Setting up retry parameters
729     return emberAfIasZoneServerConfigStatusQueueRetryParams(&retryConfig);
730 }
731
732 void emberAfWwahAppEventRetryManagerSetBackoffParamsToDefault(void)
733 {
734     emberAfIasZoneServerSetStatusQueueRetryParamsToDefault();
735 }
736 #endif // defined(EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER)
737
738 void emberAfPluginIasZoneServerPrintQueue(void)
739 {
740     emberAfIasZoneClusterPrintln("%d/%d entries", messageQueue.entriesInQueue, NUM_QUEUE_ENTRIES);
741     for (int i = 0; i < messageQueue.entriesInQueue; i++)
742     {
743         emberAfIasZoneClusterPrintln("Entry %d: Endpoint: %d Status: %d EventTimeMs: %d", i, messageQueue.buffer[i].endpoint,
744                                      messageQueue.buffer[i].status, messageQueue.buffer[i].eventTimeMs);
745     }
746 }
747
748 void emberAfPluginIasZoneServerPrintQueueConfig(void)
749 {
750     emberAfCorePrintln("First backoff time (sec): %d", queueRetryParams.config.firstBackoffTimeSec);
751     emberAfCorePrintln("Backoff sequence common ratio: %d", queueRetryParams.config.backoffSeqCommonRatio);
752     emberAfCorePrintln("Max backoff time (sec): %d", queueRetryParams.config.maxBackoffTimeSec);
753     emberAfCorePrintln("Max redelivery attempts: %d", queueRetryParams.config.maxRetryAttempts);
754 }
755
756 #endif // defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
757
758 // This callback will be generated any time the node receives an ACK or a NAK
759 // for a message transmitted for the IAS Zone Cluster Server.  Note that this
760 // will not be called in the case that the message was not delivered to the
761 // destination when the destination is the only router the node is joined to.
762 // In that case, the command will never have been sent, as the device will have
763 // had no router by which to send the command.
764 void emberAfIasZoneClusterServerMessageSentCallback(EmberOutgoingMessageType type, uint64_t indexOrDestination,
765                                                     EmberApsFrame * apsFrame, uint16_t msgLen, uint8_t * message,
766                                                     EmberStatus status)
767 {
768 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
769     uint8_t frameControl;
770     CommandId commandId;
771
772     IasZoneStatusQueueEntry dummyEntry;
773
774     // Verify that this response is for a ZoneStatusChangeNotification command
775     // by checking the message length, the command and direction bits of the
776     // Frame Control byte, and the command ID
777     if (msgLen < IAS_ZONE_SERVER_PAYLOAD_COMMAND_IDX)
778     {
779         return;
780     }
781
782     frameControl = message[ZCL_FRAME_CONTROL_IDX];
783     if (!(frameControl & ZCL_CLUSTER_SPECIFIC_COMMAND) || !(frameControl & ZCL_FRAME_CONTROL_SERVER_TO_CLIENT))
784     {
785         return;
786     }
787
788     commandId = message[IAS_ZONE_SERVER_PAYLOAD_COMMAND_IDX];
789     if (commandId != ZCL_ZONE_STATUS_CHANGE_NOTIFICATION_COMMAND_ID)
790     {
791         return;
792     }
793
794     // If a change status change notification command is not received by the
795     // client, delay the option specified amount of time and try to resend it.
796     // The event handler will perform the retransmit per the preset queue retry
797     // parameteres, and the original send request will handle populating the buffer.
798     // Do not try to retransmit again if the maximum number of retries attempts
799     // is reached, this is however discarded if configured for unlimited retries.
800     if ((status == EMBER_DELIVERY_FAILED) &&
801         (queueRetryParams.config.unlimitedRetries ||
802          (queueRetryParams.currentRetryCount < queueRetryParams.config.maxRetryAttempts)))
803     {
804         queueRetryParams.currentRetryCount++;
805
806         emberAfIasZoneClusterPrintln("Status command update failed to send... Retrying in %d seconds...",
807                                      queueRetryParams.currentBackoffTimeSec);
808
809         // Delay according to the current retransmit backoff time.
810         emberEventControlSetDelayMS(emberAfPluginIasZoneServerManageQueueEventControl,
811                                     queueRetryParams.currentBackoffTimeSec * MILLISECOND_TICKS_PER_SECOND);
812
813         // The backoff time needs to be increased if the maximum backoff time is not reached yet.
814         if ((queueRetryParams.currentBackoffTimeSec * queueRetryParams.config.backoffSeqCommonRatio) <=
815             queueRetryParams.config.maxBackoffTimeSec)
816         {
817             queueRetryParams.currentBackoffTimeSec *= queueRetryParams.config.backoffSeqCommonRatio;
818         }
819     }
820     else
821     {
822         // If a command message was sent or max redelivery attempts were reached,
823         // remove it from the queue and move on to the next queued message until the queue is empty.
824         if (status == EMBER_SUCCESS)
825         {
826             emberAfIasZoneClusterPrintln("\nZone update successful, remove entry from queue");
827         }
828         else
829         {
830             emberAfIasZoneClusterPrintln("\nZone update unsuccessful, max retry attempts reached, remove entry from queue");
831         }
832         popFromBuffer(&messageQueue, &dummyEntry);
833
834         // Reset queue retry parameters.
835         resetCurrentQueueRetryParams();
836
837         if (messageQueue.entriesInQueue)
838         {
839             emberEventControlSetActive(&emberAfPluginIasZoneServerManageQueueEventControl);
840         }
841     }
842 #endif
843 }
844
845 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
846 static void bufferInit(IasZoneStatusQueue * ring)
847 {
848     ring->entriesInQueue = 0;
849     ring->startIdx       = 0;
850     ring->lastIdx        = NUM_QUEUE_ENTRIES - 1;
851 }
852
853 // Add the entry to the buffer by copying, returning the index at which it was
854 // added.  If the buffer is full, return -1, but still copy the entry over the
855 // last item of the buffer, to ensure that the last item in the buffer is
856 // always representative of the last known device state.
857 static int16_t copyToBuffer(IasZoneStatusQueue * ring, const IasZoneStatusQueueEntry * entry)
858 {
859     if (ring->entriesInQueue == NUM_QUEUE_ENTRIES)
860     {
861         ring->buffer[ring->lastIdx] = *entry;
862         return -1;
863     }
864
865     // Increment the last pointer.  If it rolls over the size, circle it back to
866     // zero.
867     ring->lastIdx++;
868     if (ring->lastIdx >= NUM_QUEUE_ENTRIES)
869     {
870         ring->lastIdx = 0;
871     }
872
873     ring->buffer[ring->lastIdx].endpoint    = entry->endpoint;
874     ring->buffer[ring->lastIdx].status      = entry->status;
875     ring->buffer[ring->lastIdx].eventTimeMs = entry->eventTimeMs;
876
877     ring->entriesInQueue++;
878     return ring->lastIdx;
879 }
880
881 // Return the idx of the popped entry, or -1 if the buffer was empty.
882 static int16_t popFromBuffer(IasZoneStatusQueue * ring, IasZoneStatusQueueEntry * entry)
883 {
884     int16_t retVal;
885
886     if (ring->entriesInQueue == 0)
887     {
888         return -1;
889     }
890
891     // Copy out the first entry, then increment the start pointer.  If it rolls
892     // over, circle it back to zero.
893     *entry = ring->buffer[ring->startIdx];
894     retVal = ring->startIdx;
895
896     ring->startIdx++;
897     if (ring->startIdx >= NUM_QUEUE_ENTRIES)
898     {
899         ring->startIdx = 0;
900     }
901
902     ring->entriesInQueue--;
903
904     return retVal;
905 }
906
907 uint16_t computeElapsedTimeQs(IasZoneStatusQueueEntry * entry)
908 {
909     uint32_t currentTimeMs = System::Layer::GetClock_MonotonicMS();
910     int64_t deltaTimeMs    = currentTimeMs - entry->eventTimeMs;
911
912     if (deltaTimeMs < 0)
913     {
914         deltaTimeMs = -deltaTimeMs + (0xFFFFFFFF - currentTimeMs);
915     }
916
917     return deltaTimeMs / MILLISECOND_TICKS_PER_QUARTERSECOND;
918 }
919 #endif