Files
bacnet_stack/src/bacnet/basic/object/nc.c
T
Tomasz Kazimierz Motyl 4add49b549 Setting logic behind valit transitions check straight (#623)
Co-authored-by: Tomasz Kazimierz Motyl <tomasz.motyl@se.com>
2024-04-22 08:18:16 -05:00

971 lines
37 KiB
C

/**************************************************************************
*
* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Additional changes, Copyright (c) 2018 Ed Hague <edward@bac-test.com>
*
* 2018.06.17 - Attempting to write to Object_Name returned
*UNKNOWN_PROPERTY. Now returns WRITE_ACCESS_DENIED
*
*********************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bacapp.h"
#include "bacnet/bacdest.h"
#include "bacnet/datetime.h"
#include "bacnet/event.h"
#include "bacnet/wp.h"
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/object/nc.h"
#include "bacnet/basic/binding/address.h"
#include "bacnet/basic/services.h"
#include "bacnet/basic/sys/debug.h"
#include "bacnet/basic/tsm/tsm.h"
#include "bacnet/datalink/datalink.h"
#define PRINTF debug_perror
#ifndef MAX_NOTIFICATION_CLASSES
#define MAX_NOTIFICATION_CLASSES 2
#endif
#if defined(INTRINSIC_REPORTING)
static NOTIFICATION_CLASS_INFO NC_Info[MAX_NOTIFICATION_CLASSES];
/* buffer for sending event messages */
static uint8_t Event_Buffer[MAX_APDU];
/* These three arrays are used by the ReadPropertyMultiple handler */
static const int Notification_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_NOTIFICATION_CLASS, PROP_PRIORITY,
PROP_ACK_REQUIRED, PROP_RECIPIENT_LIST, -1 };
static const int Notification_Properties_Optional[] = { PROP_DESCRIPTION, -1 };
static const int Notification_Properties_Proprietary[] = { -1 };
void Notification_Class_Property_Lists(
const int **pRequired, const int **pOptional, const int **pProprietary)
{
if (pRequired)
*pRequired = Notification_Properties_Required;
if (pOptional)
*pOptional = Notification_Properties_Optional;
if (pProprietary)
*pProprietary = Notification_Properties_Proprietary;
return;
}
void Notification_Class_Init(void)
{
uint8_t NotifyIdx = 0;
unsigned i;
for (NotifyIdx = 0; NotifyIdx < MAX_NOTIFICATION_CLASSES; NotifyIdx++) {
/* init with zeros */
memset(&NC_Info[NotifyIdx], 0x00, sizeof(NOTIFICATION_CLASS_INFO));
/* set the basic parameters */
NC_Info[NotifyIdx].Ack_Required = 0;
/* The lowest priority for Normal message = 255 */
NC_Info[NotifyIdx].Priority[TRANSITION_TO_OFFNORMAL] = 255;
NC_Info[NotifyIdx].Priority[TRANSITION_TO_FAULT] = 255;
NC_Info[NotifyIdx].Priority[TRANSITION_TO_NORMAL] = 255;
/* note: default uses wildcard device destination */
for (i = 0; i < NC_MAX_RECIPIENTS; i++) {
BACNET_DESTINATION *destination;
destination = &NC_Info[NotifyIdx].Recipient_List[i];
bacnet_destination_default_init(destination);
}
}
return;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then you need validate that the */
/* given instance exists */
bool Notification_Class_Valid_Instance(uint32_t object_instance)
{
unsigned int index;
index = Notification_Class_Instance_To_Index(object_instance);
if (index < MAX_NOTIFICATION_CLASSES)
return true;
return false;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then count how many you have */
unsigned Notification_Class_Count(void)
{
return MAX_NOTIFICATION_CLASSES;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then you need to return the instance */
/* that correlates to the correct index */
uint32_t Notification_Class_Index_To_Instance(unsigned index)
{
return index;
}
/* we simply have 0-n object instances. Yours might be */
/* more complex, and then you need to return the index */
/* that correlates to the correct instance number */
unsigned Notification_Class_Instance_To_Index(uint32_t object_instance)
{
unsigned index = MAX_NOTIFICATION_CLASSES;
if (object_instance < MAX_NOTIFICATION_CLASSES)
index = object_instance;
return index;
}
bool Notification_Class_Object_Name(
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
{
static char text_string[32] = ""; /* okay for single thread */
unsigned int index;
bool status = false;
index = Notification_Class_Instance_To_Index(object_instance);
if (index < MAX_NOTIFICATION_CLASSES) {
sprintf(text_string, "NOTIFICATION CLASS %lu", (unsigned long)index);
status = characterstring_init_ansi(object_name, text_string);
}
return status;
}
int Notification_Class_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
{
NOTIFICATION_CLASS_INFO *CurrentNotify;
BACNET_CHARACTER_STRING char_string;
BACNET_BIT_STRING bit_string;
uint8_t *apdu = NULL;
uint8_t u8Val;
int idx;
int apdu_len = 0; /* return value */
uint16_t apdu_max = 0;
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
apdu = rpdata->application_data;
apdu_max = rpdata->application_data_len;
CurrentNotify =
&NC_Info[Notification_Class_Instance_To_Index(rpdata->object_instance)];
switch (rpdata->object_property) {
case PROP_OBJECT_IDENTIFIER:
apdu_len = encode_application_object_id(
&apdu[0], OBJECT_NOTIFICATION_CLASS, rpdata->object_instance);
break;
case PROP_OBJECT_NAME:
case PROP_DESCRIPTION:
Notification_Class_Object_Name(
rpdata->object_instance, &char_string);
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_OBJECT_TYPE:
apdu_len = encode_application_enumerated(
&apdu[0], OBJECT_NOTIFICATION_CLASS);
break;
case PROP_NOTIFICATION_CLASS:
apdu_len +=
encode_application_unsigned(&apdu[0], rpdata->object_instance);
break;
case PROP_PRIORITY:
if (rpdata->array_index == 0)
apdu_len += encode_application_unsigned(&apdu[0], 3);
else {
if (rpdata->array_index == BACNET_ARRAY_ALL) {
apdu_len += encode_application_unsigned(&apdu[apdu_len],
CurrentNotify->Priority[TRANSITION_TO_OFFNORMAL]);
apdu_len += encode_application_unsigned(&apdu[apdu_len],
CurrentNotify->Priority[TRANSITION_TO_FAULT]);
apdu_len += encode_application_unsigned(&apdu[apdu_len],
CurrentNotify->Priority[TRANSITION_TO_NORMAL]);
} else if (rpdata->array_index <= MAX_BACNET_EVENT_TRANSITION) {
apdu_len += encode_application_unsigned(&apdu[apdu_len],
CurrentNotify->Priority[rpdata->array_index - 1]);
} else {
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
apdu_len = -1;
}
}
break;
case PROP_ACK_REQUIRED:
u8Val = CurrentNotify->Ack_Required;
bitstring_init(&bit_string);
bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL,
(u8Val & TRANSITION_TO_OFFNORMAL_MASKED) ? true : false);
bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT,
(u8Val & TRANSITION_TO_FAULT_MASKED) ? true : false);
bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL,
(u8Val & TRANSITION_TO_NORMAL_MASKED) ? true : false);
/* encode bitstring */
apdu_len +=
encode_application_bitstring(&apdu[apdu_len], &bit_string);
break;
case PROP_RECIPIENT_LIST:
/* get the size of all entry of Recipient_List */
for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++) {
BACNET_DESTINATION *Destination;
BACNET_RECIPIENT *Recipient;
Destination = &CurrentNotify->Recipient_List[idx];
Recipient = &Destination->Recipient;
if (!bacnet_recipient_device_wildcard(Recipient)) {
apdu_len += bacnet_destination_encode(NULL, Destination);
}
}
if (apdu_len > apdu_max) {
/* Abort response */
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
apdu_len = BACNET_STATUS_ABORT;
break;
}
/* size fits, therefore, encode all entry of Recipient_List */
apdu_len = 0;
for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++) {
BACNET_DESTINATION *Destination;
BACNET_RECIPIENT *Recipient;
Destination = &CurrentNotify->Recipient_List[idx];
Recipient = &Destination->Recipient;
if (!bacnet_recipient_device_wildcard(Recipient)) {
apdu_len +=
bacnet_destination_encode(&apdu[apdu_len], Destination);
}
}
break;
default:
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
apdu_len = -1;
break;
}
/* only array properties can have array options */
if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY) &&
(rpdata->array_index != BACNET_ARRAY_ALL)) {
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
apdu_len = BACNET_STATUS_ERROR;
}
return apdu_len;
}
bool Notification_Class_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
NOTIFICATION_CLASS_INFO *CurrentNotify;
NOTIFICATION_CLASS_INFO TmpNotify;
BACNET_APPLICATION_DATA_VALUE value;
uint8_t TmpPriority[MAX_BACNET_EVENT_TRANSITION]; /* BACnetARRAY[3] of
Unsigned */
bool status = false;
int iOffset;
uint8_t idx;
int len = 0;
CurrentNotify = &NC_Info[Notification_Class_Instance_To_Index(
wp_data->object_instance)];
/* decode some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
if (len < 0) {
/* error while decoding - a value larger than we can handle */
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
return false;
}
if ((wp_data->object_property != PROP_PRIORITY) &&
(wp_data->array_index != BACNET_ARRAY_ALL)) {
/* only array properties can have array options */
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return false;
}
switch (wp_data->object_property) {
case PROP_PRIORITY:
status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
if (status) {
if (wp_data->array_index == 0) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
status = false;
} else if (wp_data->array_index == BACNET_ARRAY_ALL) {
iOffset = 0;
for (idx = 0; idx < MAX_BACNET_EVENT_TRANSITION; idx++) {
len = bacapp_decode_application_data(
&wp_data->application_data[iOffset],
wp_data->application_data_len, &value);
if ((len == 0) ||
(value.tag !=
BACNET_APPLICATION_TAG_UNSIGNED_INT)) {
/* Bad decode, wrong tag or following required
* parameter missing */
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
status = false;
break;
}
if (value.type.Unsigned_Int > 255) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
status = false;
break;
}
TmpPriority[idx] = (uint8_t)value.type.Unsigned_Int;
iOffset += len;
}
if (status == true) {
for (idx = 0; idx < MAX_BACNET_EVENT_TRANSITION; idx++)
CurrentNotify->Priority[idx] = TmpPriority[idx];
}
} else if (wp_data->array_index <= 3) {
if (value.type.Unsigned_Int > 255) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
status = false;
} else
CurrentNotify->Priority[wp_data->array_index - 1] =
value.type.Unsigned_Int;
} else {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
status = false;
}
}
break;
case PROP_ACK_REQUIRED:
status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_BIT_STRING);
if (status) {
if (value.type.Bit_String.bits_used == 3) {
CurrentNotify->Ack_Required =
value.type.Bit_String.value[0];
} else {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
}
}
break;
case PROP_RECIPIENT_LIST:
for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++) {
BACNET_DESTINATION *destination;
destination = &TmpNotify.Recipient_List[idx];
bacnet_destination_default_init(destination);
}
idx = 0;
iOffset = 0;
/* decode all packed */
while (iOffset < wp_data->application_data_len) {
BACNET_DESTINATION *destination;
destination = &TmpNotify.Recipient_List[idx];
len = bacnet_destination_decode(
&wp_data->application_data[iOffset],
wp_data->application_data_len, destination);
if (len == BACNET_STATUS_REJECT) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
return false;
}
iOffset += len;
/* Increasing element of list */
if (++idx >= NC_MAX_RECIPIENTS) {
wp_data->error_class = ERROR_CLASS_RESOURCES;
wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
return false;
}
}
/* Decoded all recipient list */
/* copy elements from temporary object */
for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++) {
BACNET_ADDRESS src = { 0 };
unsigned max_apdu = 0;
uint32_t device_id;
BACNET_DESTINATION *destination;
BACNET_RECIPIENT *recipient;
destination = &CurrentNotify->Recipient_List[idx];
bacnet_destination_copy(
destination, &TmpNotify.Recipient_List[idx]);
recipient = &destination->Recipient;
if (bacnet_recipient_device_valid(recipient)) {
device_id = recipient->type.device.instance;
address_bind_request(device_id, &max_apdu, &src);
} else if (recipient->tag == BACNET_RECIPIENT_TAG_ADDRESS) {
/* nothing to do - we have the address */
}
}
status = true;
break;
case PROP_OBJECT_IDENTIFIER:
case PROP_OBJECT_NAME:
case PROP_OBJECT_TYPE:
case PROP_DESCRIPTION:
case PROP_NOTIFICATION_CLASS:
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
break;
default:
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
break;
}
return status;
}
void Notification_Class_Get_Priorities(
uint32_t Object_Instance, uint32_t *pPriorityArray)
{
NOTIFICATION_CLASS_INFO *CurrentNotify;
uint32_t object_index;
int i;
object_index = Notification_Class_Instance_To_Index(Object_Instance);
if (object_index < MAX_NOTIFICATION_CLASSES)
CurrentNotify = &NC_Info[object_index];
else {
for (i = 0; i < 3; i++)
pPriorityArray[i] = 255;
return; /* unknown object */
}
for (i = 0; i < 3; i++)
pPriorityArray[i] = CurrentNotify->Priority[i];
}
static bool IsRecipientActive(
BACNET_DESTINATION *pBacDest, uint8_t EventToState)
{
BACNET_DATE_TIME DateTime;
/* valid Transitions */
switch (EventToState) {
case EVENT_STATE_OFFNORMAL:
case EVENT_STATE_HIGH_LIMIT:
case EVENT_STATE_LOW_LIMIT:
if (!bitstring_bit(
&pBacDest->Transitions, TRANSITION_TO_OFFNORMAL)) {
return false;
}
break;
case EVENT_STATE_FAULT:
if (!bitstring_bit(&pBacDest->Transitions, TRANSITION_TO_FAULT)) {
return false;
}
break;
case EVENT_STATE_NORMAL:
if (!bitstring_bit(&pBacDest->Transitions, TRANSITION_TO_NORMAL)) {
return false;
}
break;
default:
return false; /* shouldn't happen */
}
/* get actual date and time */
datetime_local(&DateTime.date, &DateTime.time, NULL, NULL);
/* valid Days */
if (!(bitstring_bit(&pBacDest->ValidDays, (DateTime.date.wday - 1)))) {
return false;
}
/* valid FromTime */
if (datetime_compare_time(&DateTime.time, &pBacDest->FromTime) < 0)
return false;
/* valid ToTime */
if (datetime_compare_time(&pBacDest->ToTime, &DateTime.time) < 0)
return false;
return true;
}
void Notification_Class_common_reporting_function(
BACNET_EVENT_NOTIFICATION_DATA *event_data)
{
/* Fill the parameters common for all types of events. */
NOTIFICATION_CLASS_INFO *CurrentNotify;
BACNET_DESTINATION *pBacDest;
uint32_t notify_index;
uint8_t index;
notify_index =
Notification_Class_Instance_To_Index(event_data->notificationClass);
if (notify_index < MAX_NOTIFICATION_CLASSES)
CurrentNotify = &NC_Info[notify_index];
else
return;
/* Initiating Device Identifier */
event_data->initiatingObjectIdentifier.type = OBJECT_DEVICE;
event_data->initiatingObjectIdentifier.instance =
Device_Object_Instance_Number();
/* Priority and AckRequired */
switch (event_data->toState) {
case EVENT_STATE_NORMAL:
event_data->priority =
CurrentNotify->Priority[TRANSITION_TO_NORMAL];
event_data->ackRequired =
(CurrentNotify->Ack_Required & TRANSITION_TO_NORMAL_MASKED)
? true
: false;
break;
case EVENT_STATE_FAULT:
event_data->priority = CurrentNotify->Priority[TRANSITION_TO_FAULT];
event_data->ackRequired =
(CurrentNotify->Ack_Required & TRANSITION_TO_FAULT_MASKED)
? true
: false;
break;
case EVENT_STATE_OFFNORMAL:
case EVENT_STATE_HIGH_LIMIT:
case EVENT_STATE_LOW_LIMIT:
event_data->priority =
CurrentNotify->Priority[TRANSITION_TO_OFFNORMAL];
event_data->ackRequired =
(CurrentNotify->Ack_Required & TRANSITION_TO_OFFNORMAL_MASKED)
? true
: false;
break;
default: /* shouldn't happen */
break;
}
/* send notifications for active recipients */
PRINTF("Notification Class[%u]: send notifications\n",
event_data->notificationClass);
/* pointer to first recipient */
pBacDest = &CurrentNotify->Recipient_List[0];
for (index = 0; index < NC_MAX_RECIPIENTS; index++, pBacDest++) {
if (bacnet_recipient_device_wildcard(&pBacDest->Recipient)) {
continue;
}
if (IsRecipientActive(pBacDest, event_data->toState)) {
BACNET_ADDRESS dest;
uint32_t device_id;
unsigned max_apdu;
/* Process Identifier */
event_data->processIdentifier = pBacDest->ProcessIdentifier;
/* send notification */
if (pBacDest->Recipient.tag == BACNET_RECIPIENT_TAG_DEVICE) {
/* send notification to the specified device */
device_id = pBacDest->Recipient.type.device.instance;
PRINTF("Notification Class[%u]: send notification to %u\n",
event_data->notificationClass, (unsigned)device_id);
if (pBacDest->ConfirmedNotify == true)
Send_CEvent_Notify(device_id, event_data);
else if (address_get_by_device(device_id, &max_apdu, &dest))
Send_UEvent_Notify(Event_Buffer, event_data, &dest);
} else if (pBacDest->Recipient.tag ==
BACNET_RECIPIENT_TAG_ADDRESS) {
PRINTF("Notification Class[%u]: send notification to ADDR\n",
event_data->notificationClass);
/* send notification to the address indicated */
if (pBacDest->ConfirmedNotify == true) {
if (address_get_device_id(&dest, &device_id))
Send_CEvent_Notify(device_id, event_data);
} else {
dest = pBacDest->Recipient.type.address;
Send_UEvent_Notify(Event_Buffer, event_data, &dest);
}
}
}
}
}
/* This function tries to find the addresses of the defined devices. */
/* It should be called periodically (example once per minute). */
void Notification_Class_find_recipient(void)
{
NOTIFICATION_CLASS_INFO *notification;
BACNET_DESTINATION *destination;
BACNET_RECIPIENT *recipient;
BACNET_ADDRESS src = { 0 };
unsigned max_apdu = 0;
uint32_t device_id;
unsigned i, j;
for (i = 0; i < MAX_NOTIFICATION_CLASSES; i++) {
notification = &NC_Info[i];
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
destination = &notification->Recipient_List[j];
recipient = &destination->Recipient;
if (bacnet_recipient_device_valid(recipient)) {
device_id = recipient->type.device.instance;
if (!address_bind_request(device_id, &max_apdu, &src)) {
/* Send who_ is request only when
address of device is unknown. */
Send_WhoIs(device_id, device_id);
}
}
}
}
}
/**
* @brief AddListElement from an object list property
* @ingroup ObjHelpers
* @param list_element [in] Pointer to the BACnet_List_Element_Data structure,
* which is packed with the information from the request.
* @return #BACNET_STATUS_OK or #BACNET_STATUS_ERROR or
* #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT.
* @note 15.1.2 Service Procedure
* After verifying the validity of the request, the responding BACnet-user
* shall attempt to modify the object identified in the
* 'Object Identifier' parameter. If the identified object exists
* and has the property specified in the 'Property Identifier' parameter,
* and, if present, has the array element specified in the
* 'Property Array Index' parameter, an attempt shall be made to add all of
* the elements specified in the 'List of Elements' parameter to the
* specified property or array element of the property. If this
* attempt is successful, a 'Result(+)' primitive shall be issued.
*
* When comparing elements in the 'List of Elements' parameter with
* elements in the specified property or array element of the
* property, the complete element shall be compared unless the
* property description specifies otherwise. If one or more of the
* elements is already present in the BACnetLIST, it shall be updated
* with the provided element, that is, the existing element is
* over-written with the provided element. Optionally, if the provided
* element is exactly the same as the existing element in every
* way, it can be ignored, that is, not added to the BACnetLIST.
* Ignoring an element that already exists shall not cause the
* service to fail.
*
* If the specified object does not exist, the specified property
* does not exist, the specified array element does not exist, or the
* specified property or array element is not a BACnetLIST, then
* the service shall fail and a 'Result(-)' response primitive shall
* be issued. If one or more elements cannot be added to, or updated
* in, the BACnetLIST, a 'Result(-)' response primitive shall be
* issued and no elements shall be added to, or updated in, the BACnetLIST.
*
* The effect of this service shall be to add to, or update in,
* the BACnetLIST all of the specified elements, or to neither add nor
* update any elements at all.
*/
int Notification_Class_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
{
NOTIFICATION_CLASS_INFO *notification = NULL;
BACNET_DESTINATION recipient_list[NC_MAX_RECIPIENTS] = { 0 };
uint8_t *application_data = NULL;
int application_data_len = 0, len = 0;
uint32_t notify_index = 0;
unsigned index = 0;
unsigned element_count = 0, new_element_count = 0;
unsigned added_element_count = 0, same_element_count = 0;
bool same_element = false;
unsigned i = 0, j = 0;
if (!list_element) {
return BACNET_STATUS_ABORT;
}
if (list_element->object_property != PROP_RECIPIENT_LIST) {
list_element->error_class = ERROR_CLASS_SERVICES;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
return BACNET_STATUS_ERROR;
}
if (list_element->array_index != BACNET_ARRAY_ALL) {
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return BACNET_STATUS_ERROR;
}
notify_index =
Notification_Class_Instance_To_Index(list_element->object_instance);
if (notify_index < MAX_NOTIFICATION_CLASSES) {
notification = &NC_Info[notify_index];
} else {
list_element->error_class = ERROR_CLASS_OBJECT;
list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT;
return BACNET_STATUS_ERROR;
}
/* get the current size of all entry of Recipient_List */
for (index = 0; index < NC_MAX_RECIPIENTS; index++) {
BACNET_DESTINATION *d1;
BACNET_RECIPIENT *r1;
d1 = &notification->Recipient_List[index];
r1 = &d1->Recipient;
if (!bacnet_recipient_device_wildcard(r1)) {
element_count++;
}
}
/* decode the elements */
new_element_count = 0;
application_data = list_element->application_data;
application_data_len = list_element->application_data_len;
while (application_data_len > 0) {
len = bacnet_destination_decode(
application_data, application_data_len, &recipient_list[index]);
if (len > 0) {
new_element_count++;
application_data_len -= len;
} else {
list_element->first_failed_element_number = new_element_count;
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_INVALID_DATA_ENCODING;
return BACNET_STATUS_ERROR;
}
}
if (new_element_count == 0) {
return BACNET_STATUS_OK;
}
/* determine the same and added recipient counts */
same_element_count = 0;
added_element_count = 0;
for (i = 0; i < new_element_count; i++) {
BACNET_DESTINATION *d1;
BACNET_RECIPIENT *r1;
d1 = &recipient_list[i];
r1 = &d1->Recipient;
same_element = false;
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
BACNET_DESTINATION *d2;
BACNET_RECIPIENT *r2;
d2 = &notification->Recipient_List[j];
r2 = &d2->Recipient;
if (bacnet_recipient_same(r1, r2)) {
same_element = true;
break;
}
}
if (same_element) {
same_element_count++;
} else {
added_element_count++;
if ((added_element_count + element_count) > NC_MAX_RECIPIENTS) {
list_element->first_failed_element_number = 1 + i;
list_element->error_class = ERROR_CLASS_RESOURCES;
list_element->error_code =
ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT;
return BACNET_STATUS_ERROR;
}
}
}
/* update existing and add new */
for (i = 0; i < new_element_count; i++) {
BACNET_DESTINATION *d1;
BACNET_RECIPIENT *r1;
d1 = &recipient_list[i];
r1 = &d1->Recipient;
same_element = false;
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
BACNET_DESTINATION *d2;
BACNET_RECIPIENT *r2;
d2 = &notification->Recipient_List[j];
r2 = &d2->Recipient;
if (bacnet_recipient_same(r1, r2)) {
/* update existing element */
same_element = true;
bacnet_destination_copy(d2, d1);
}
}
if (!same_element) {
/* add new element to next free slot */
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
BACNET_DESTINATION *d2;
BACNET_RECIPIENT *r2;
d2 = &notification->Recipient_List[j];
r2 = &d2->Recipient;
if (!bacnet_recipient_device_wildcard(r2)) {
bacnet_destination_copy(d2, d1);
break;
}
}
}
}
return BACNET_STATUS_OK;
}
/**
* @brief RemoveListElement from an object list property
* @ingroup ObjHelpers
* @param list_element [in] Pointer to the BACnet_List_Element_Data structure,
* which is packed with the information from the request.
* @return The length of the apdu encoded or #BACNET_STATUS_ERROR or
* #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT.
* @note After verifying the validity of the request, the responding
* BACnet-user shall attempt to modify the object identified in the
* 'Object Identifier' parameter. If the identified object exists and it
* has the property specified in the 'Property Identifier' parameter,
* and if present has the array element specified in the 'Property Array Index'
* parameter, an attempt shall be made to remove the
* elements in the 'List of Elements' from the property or array element
* of the property of the object.
*
* When comparing elements of the service with entries in the affected
* BACnetLIST, the complete element shall be compared
* unless the property description specifies otherwise. If one or more
* of the elements does not exist or cannot be removed because
* of insufficient authority, none of the elements shall be removed and
* a 'Result(-)' response primitive shall be issued.
*/
int Notification_Class_Remove_List_Element(
BACNET_LIST_ELEMENT_DATA *list_element)
{
NOTIFICATION_CLASS_INFO *notification = NULL;
uint32_t notify_index = 0;
unsigned index = 0;
BACNET_DESTINATION recipient_list[NC_MAX_RECIPIENTS] = { 0 };
uint8_t *application_data = NULL;
int application_data_len = 0, len = 0;
unsigned element_count = 0;
unsigned remove_element_count = 0;
bool same_element = false;
unsigned i = 0, j = 0;
if (!list_element) {
return BACNET_STATUS_ABORT;
}
if (list_element->object_property != PROP_RECIPIENT_LIST) {
list_element->error_class = ERROR_CLASS_SERVICES;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
return BACNET_STATUS_ERROR;
}
if (list_element->array_index != BACNET_ARRAY_ALL) {
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return BACNET_STATUS_ERROR;
}
notify_index =
Notification_Class_Instance_To_Index(list_element->object_instance);
if (notify_index < MAX_NOTIFICATION_CLASSES) {
notification = &NC_Info[notify_index];
} else {
list_element->error_class = ERROR_CLASS_OBJECT;
list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT;
return BACNET_STATUS_ERROR;
}
/* get the current size of all entry of Recipient_List */
for (index = 0; index < NC_MAX_RECIPIENTS; index++) {
BACNET_DESTINATION *d1;
BACNET_RECIPIENT *r1;
d1 = &notification->Recipient_List[index];
r1 = &d1->Recipient;
if (!bacnet_recipient_device_wildcard(r1)) {
element_count++;
}
}
/* decode the elements */
application_data = list_element->application_data;
application_data_len = list_element->application_data_len;
while (application_data_len > 0) {
len = bacnet_destination_decode(
application_data, application_data_len, &recipient_list[index]);
if (len > 0) {
remove_element_count++;
application_data_len -= len;
} else {
list_element->first_failed_element_number = remove_element_count;
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_INVALID_DATA_ENCODING;
return BACNET_STATUS_ERROR;
}
}
/* determine if one or more of the elements does not exist */
for (i = 0; i < remove_element_count; i++) {
BACNET_DESTINATION *d1;
BACNET_RECIPIENT *r1;
d1 = &recipient_list[i];
r1 = &d1->Recipient;
same_element = false;
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
BACNET_DESTINATION *d2;
BACNET_RECIPIENT *r2;
d2 = &notification->Recipient_List[j];
r2 = &d2->Recipient;
if (bacnet_recipient_same(r1, r2)) {
same_element = true;
}
}
if (!same_element) {
list_element->first_failed_element_number = 1 + i;
list_element->error_class = ERROR_CLASS_SERVICES;
list_element->error_code = ERROR_CODE_LIST_ELEMENT_NOT_FOUND;
return BACNET_STATUS_ERROR;
}
}
/* remove any matching elements */
for (i = 0; i < remove_element_count; i++) {
BACNET_DESTINATION *d1;
BACNET_RECIPIENT *r1;
d1 = &recipient_list[i];
r1 = &d1->Recipient;
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
BACNET_DESTINATION *d2;
BACNET_RECIPIENT *r2;
d2 = &notification->Recipient_List[j];
r2 = &d2->Recipient;
if (bacnet_recipient_same(r1, r2)) {
bacnet_destination_default_init(d2);
}
}
}
return BACNET_STATUS_OK;
}
#endif /* defined(INTRINSIC_REPORTING) */