Files
bacnet_stack/src/bacnet/proplist.c
T

789 lines
26 KiB
C

/**
* @file
* @brief Property_List property encode decode helper
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2012
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <stdint.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/rpm.h"
#include "bacnet/rp.h"
#include "bacnet/proplist.h"
/**
* Function that returns the number of BACnet object properties in a list
*
* @param pList - array of type 'int32_t' that is a list of BACnet object
* properties, terminated by a '-1' value.
*/
uint32_t property_list_count(const int32_t *pList)
{
uint32_t property_count = 0;
if (pList) {
while (*pList != -1) {
property_count++;
pList++;
}
}
return property_count;
}
/**
* For a given object property, returns the true if in the property list
*
* @param pList - array of type 'int32_t' that is a list of BACnet object
* @param object_property - property enumeration or propritary value
*
* @return true if object_property is a member of the property list
*/
bool property_list_member(const int32_t *pList, int32_t object_property)
{
bool status = false;
if (pList) {
while ((*pList) != -1) {
if (object_property == (*pList)) {
status = true;
break;
}
pList++;
}
}
return status;
}
/**
* @brief Determine if the object property is a member of any of the lists
* @param pRequired - array of type 'int32_t' that is a list of BACnet
* properties
* @param pOptional - array of type 'int32_t' that is a list of BACnet
* properties
* @param pProprietary - array of type 'int32_t' that is a list of BACnet
* properties
* @param object_property - object-property to be checked
* @return true if the property is a member of any of these lists
*/
bool property_lists_member(
const int32_t *pRequired,
const int32_t *pOptional,
const int32_t *pProprietary,
int32_t object_property)
{
bool found = false;
found = property_list_member(pRequired, object_property);
if (!found) {
found = property_list_member(pOptional, object_property);
}
if (!found) {
found = property_list_member(pProprietary, object_property);
}
return found;
}
/**
* ReadProperty handler for this property. For the given ReadProperty
* data, the application_data is loaded or the error flags are set.
*
* @param rpdata - ReadProperty data, including requested data and
* data for the reply, or error response.
* @param pListRequired - list of required properties
* @param pListOptional - list of optional properties
* @param pListProprietary - list of proprietary properties
*
* @return number of APDU bytes in the response, or
* BACNET_STATUS_ERROR on error.
*/
int property_list_encode(
BACNET_READ_PROPERTY_DATA *rpdata,
const int32_t *pListRequired,
const int32_t *pListOptional,
const int32_t *pListProprietary)
{
int apdu_len = 0; /* return value */
uint8_t *apdu = NULL;
int max_apdu_len = 0;
uint32_t count = 0;
uint32_t required_count = 0;
uint32_t optional_count = 0;
uint32_t proprietary_count = 0;
int len = 0;
uint32_t i = 0; /* loop index */
required_count = property_list_count(pListRequired);
optional_count = property_list_count(pListOptional);
proprietary_count = property_list_count(pListProprietary);
/* total of all counts */
count = required_count + optional_count + proprietary_count;
if (required_count >= 3) {
/* less the 3 always required properties */
count -= 3;
if (property_list_member(pListRequired, PROP_PROPERTY_LIST)) {
/* property-list should not be in the required list
because this module handles it transparently.
Handle the case where it might be in the required list. */
count -= 1;
}
}
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
apdu = rpdata->application_data;
max_apdu_len = rpdata->application_data_len;
switch (rpdata->object_property) {
case PROP_PROPERTY_LIST:
if (rpdata->array_index == 0) {
/* Array element zero is the number of elements in the array */
apdu_len = encode_application_unsigned(&apdu[0], count);
} else if (rpdata->array_index == BACNET_ARRAY_ALL) {
/* if no index was specified, then try to encode the entire list
*/
/* into one packet. */
if (required_count > 3) {
for (i = 0; i < required_count; i++) {
if ((pListRequired[i] == PROP_OBJECT_TYPE) ||
(pListRequired[i] == PROP_OBJECT_IDENTIFIER) ||
(pListRequired[i] == PROP_OBJECT_NAME) ||
(pListRequired[i] == PROP_PROPERTY_LIST)) {
continue;
} else {
len = encode_application_enumerated(
&apdu[apdu_len], (uint32_t)pListRequired[i]);
}
/* add it if we have room */
if ((apdu_len + len) < max_apdu_len) {
apdu_len += len;
} else {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
apdu_len = BACNET_STATUS_ABORT;
break;
}
}
}
if (optional_count) {
for (i = 0; i < optional_count; i++) {
len = encode_application_enumerated(
&apdu[apdu_len], (uint32_t)pListOptional[i]);
/* add it if we have room */
if ((apdu_len + len) < max_apdu_len) {
apdu_len += len;
} else {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
apdu_len = BACNET_STATUS_ABORT;
break;
}
}
}
if (proprietary_count) {
for (i = 0; i < proprietary_count; i++) {
len = encode_application_enumerated(
&apdu[apdu_len], (uint32_t)pListProprietary[i]);
/* add it if we have room */
if ((apdu_len + len) < max_apdu_len) {
apdu_len += len;
} else {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
apdu_len = BACNET_STATUS_ABORT;
break;
}
}
}
} else {
if (rpdata->array_index <= count) {
count = 0;
if (required_count > 3) {
for (i = 0; i < required_count; i++) {
if ((pListRequired[i] == PROP_OBJECT_TYPE) ||
(pListRequired[i] == PROP_OBJECT_IDENTIFIER) ||
(pListRequired[i] == PROP_OBJECT_NAME) ||
(pListRequired[i] == PROP_PROPERTY_LIST)) {
continue;
} else {
count++;
}
if (count == rpdata->array_index) {
apdu_len = encode_application_enumerated(
&apdu[apdu_len],
(uint32_t)pListRequired[i]);
break;
}
}
}
if ((apdu_len == 0) && (optional_count > 0)) {
for (i = 0; i < optional_count; i++) {
count++;
if (count == rpdata->array_index) {
apdu_len = encode_application_enumerated(
&apdu[apdu_len],
(uint32_t)pListOptional[i]);
break;
}
}
}
if ((apdu_len == 0) && (proprietary_count > 0)) {
for (i = 0; i < proprietary_count; i++) {
count++;
if (count == rpdata->array_index) {
apdu_len = encode_application_enumerated(
&apdu[apdu_len],
(uint32_t)pListProprietary[i]);
break;
}
}
}
} else {
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
apdu_len = BACNET_STATUS_ERROR;
}
}
break;
default:
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
apdu_len = BACNET_STATUS_ERROR;
break;
}
return apdu_len;
}
/**
* ReadProperty handler for common properties. For the given ReadProperty
* data, the application_data is loaded or the error flags are set.
*
* @param rpdata - ReadProperty data, including requested data and
* data for the reply, or error response.
* @param device_instance_number - device instance number
*
* @return number of APDU bytes in the response, or
* BACNET_STATUS_ERROR on error.
*/
int property_list_common_encode(
BACNET_READ_PROPERTY_DATA *rpdata, uint32_t device_instance_number)
{
int apdu_len = BACNET_STATUS_ERROR;
uint8_t *apdu = NULL;
if (!rpdata) {
return 0;
}
if ((rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
apdu = rpdata->application_data;
switch (rpdata->object_property) {
case PROP_OBJECT_IDENTIFIER:
/* Device Object exception: requested instance
may not match our instance if a wildcard */
if (rpdata->object_type == OBJECT_DEVICE) {
rpdata->object_instance = device_instance_number;
}
apdu_len = encode_application_object_id(
&apdu[0], rpdata->object_type, rpdata->object_instance);
break;
case PROP_OBJECT_TYPE:
apdu_len =
encode_application_enumerated(&apdu[0], rpdata->object_type);
break;
default:
break;
}
return apdu_len;
}
/**
* @brief Determine if the property is a common property
* @param property - property value for comparison
* @return true if the property is a common object property
*/
bool property_list_common(BACNET_PROPERTY_ID property)
{
bool status = false;
switch (property) {
case PROP_OBJECT_IDENTIFIER:
case PROP_OBJECT_TYPE:
status = true;
break;
default:
break;
}
return status;
}
/* standard properties that are arrays
but not required to be supported in every object */
static const int32_t Properties_BACnetARRAY[] = {
/* unordered list of properties */
PROP_OBJECT_LIST,
PROP_STRUCTURED_OBJECT_LIST,
PROP_CONFIGURATION_FILES,
PROP_PROPERTY_LIST,
PROP_AUTHENTICATION_FACTORS,
PROP_ASSIGNED_ACCESS_RIGHTS,
PROP_ACTION,
PROP_ACTION_TEXT,
PROP_PRIORITY_ARRAY,
PROP_VALUE_SOURCE_ARRAY,
PROP_COMMAND_TIME_ARRAY,
PROP_ALARM_VALUES,
PROP_FAULT_VALUES,
PROP_EVENT_TIME_STAMPS,
PROP_EVENT_MESSAGE_TEXTS,
PROP_EVENT_MESSAGE_TEXTS_CONFIG,
PROP_SUPPORTED_FORMATS,
PROP_SUPPORTED_FORMAT_CLASSES,
PROP_SUBORDINATE_LIST,
PROP_SUBORDINATE_ANNOTATIONS,
PROP_SUBORDINATE_TAGS,
PROP_SUBORDINATE_NODE_TYPES,
PROP_SUBORDINATE_RELATIONSHIPS,
PROP_GROUP_MEMBERS,
PROP_GROUP_MEMBER_NAMES,
PROP_EXECUTION_DELAY,
PROP_CONTROL_GROUPS,
PROP_BIT_TEXT,
PROP_PORT_FILTER,
PROP_STATE_CHANGE_VALUES,
PROP_LINK_SPEEDS,
PROP_IP_DNS_SERVER,
PROP_IPV6_DNS_SERVER,
PROP_FLOOR_TEXT,
PROP_CAR_DOOR_TEXT,
PROP_ASSIGNED_LANDING_CALLS,
PROP_MAKING_CAR_CALL,
PROP_REGISTERED_CAR_CALL,
PROP_CAR_DOOR_STATUS,
PROP_CAR_DOOR_COMMAND,
PROP_LANDING_DOOR_STATUS,
PROP_STAGES,
PROP_STAGE_NAMES,
PROP_STATE_TEXT,
PROP_TARGET_REFERENCES,
PROP_MONITORED_OBJECTS,
PROP_SHED_LEVELS,
PROP_SHED_LEVEL_DESCRIPTIONS,
PROP_WEEKLY_SCHEDULE,
PROP_EXCEPTION_SCHEDULE,
PROP_TAGS,
PROP_ISSUER_CERTIFICATE_FILES,
PROP_NEGATIVE_ACCESS_RULES,
PROP_POSITIVE_ACCESS_RULES,
#if (INT_MAX > 0xFFFF)
PROP_SC_HUB_FUNCTION_ACCEPT_URIS,
#endif
-1
};
/**
* Function that returns the list of Required properties
* of known standard objects.
*
* @param object_type - enumerated BACNET_OBJECT_TYPE
* @return returns a pointer to a '-1' terminated array of
* type 'int32_t' that contain BACnet object properties for the given object
* type.
*/
const int32_t *property_list_bacnet_array(void)
{
return Properties_BACnetARRAY;
}
/**
* @brief Determine if the object property is a BACnetARRAY property
* @param object_type - object-type to be checked
* @param object_property - object-property to be checked
* @return true if the property is a BACnetARRAY property
*/
bool property_list_bacnet_array_member(
BACNET_OBJECT_TYPE object_type, BACNET_PROPERTY_ID object_property)
{
/* exceptions where a property is an BACnetARRAY or a BACnetLIST
only in specific object types */
switch (object_type) {
case OBJECT_GLOBAL_GROUP:
switch (object_property) {
case PROP_PRESENT_VALUE:
return true;
default:
break;
}
break;
case OBJECT_CHANNEL:
switch (object_property) {
case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES:
return true;
default:
break;
}
break;
case OBJECT_LOOP:
switch (object_property) {
case PROP_ACTION:
return false;
default:
break;
}
break;
default:
break;
}
if ((object_property >= PROP_PROPRIETARY_RANGE_MIN) &&
(object_property <= PROP_PROPRIETARY_RANGE_MAX)) {
/* all proprietary properties could be a BACnetARRAY */
return true;
}
return property_list_member(Properties_BACnetARRAY, object_property);
}
/* standard properties that are BACnetLIST */
static const int32_t Properties_BACnetLIST[] = {
/* unordered list of properties */
PROP_DATE_LIST,
PROP_VT_CLASSES_SUPPORTED,
PROP_ACTIVE_VT_SESSIONS,
PROP_TIME_SYNCHRONIZATION_RECIPIENTS,
PROP_DEVICE_ADDRESS_BINDING,
PROP_ACTIVE_COV_SUBSCRIPTIONS,
PROP_RESTART_NOTIFICATION_RECIPIENTS,
PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS,
PROP_ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS,
PROP_LIST_OF_GROUP_MEMBERS,
PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES,
PROP_ACCEPTED_MODES,
PROP_LIFE_SAFETY_ALARM_VALUES,
PROP_ALARM_VALUES,
PROP_FAULT_VALUES,
PROP_MEMBER_OF,
PROP_ZONE_MEMBERS,
PROP_RECIPIENT_LIST,
PROP_LOG_BUFFER,
PROP_MASKED_ALARM_VALUES,
PROP_FAILED_ATTEMPT_EVENTS,
PROP_ACCESS_ALARM_EVENTS,
PROP_ACCESS_TRANSACTION_EVENTS,
PROP_CREDENTIALS_IN_ZONE,
PROP_ENTRY_POINTS,
PROP_EXIT_POINTS,
PROP_MEMBERS,
PROP_CREDENTIALS,
PROP_REASON_FOR_DISABLE,
PROP_AUTHORIZATION_EXEMPTIONS,
PROP_COVU_RECIPIENTS,
PROP_SUBSCRIBED_RECIPIENTS,
PROP_BBMD_BROADCAST_DISTRIBUTION_TABLE,
PROP_BBMD_FOREIGN_DEVICE_TABLE,
PROP_MANUAL_SLAVE_ADDRESS_BINDING,
PROP_SLAVE_ADDRESS_BINDING,
PROP_VIRTUAL_MAC_ADDRESS_TABLE,
PROP_ROUTING_TABLE,
PROP_LANDING_CALLS,
PROP_FAULT_SIGNALS,
PROP_ADDITIONAL_REFERENCE_PORTS,
-1
};
/**
* Returns the list of BACnetLIST properties of known standard objects.
*
* @param object_type - enumerated BACNET_OBJECT_TYPE
* @return returns a pointer to a '-1' terminated array of
* type 'int32_t' that contain BACnet object properties for the given object
* type.
*/
const int32_t *property_list_bacnet_list(void)
{
return Properties_BACnetLIST;
}
/**
* @brief Determine if the object property is a BACnetLIST property
* @param object_type - object-type to be checked
* @param object_property - object-property to be checked
* @return true if the property is a BACnetLIST property
*/
bool property_list_bacnet_list_member(
BACNET_OBJECT_TYPE object_type, BACNET_PROPERTY_ID object_property)
{
/* exceptions where property is an BACnetLIST only in specific objects */
switch (object_type) {
case OBJECT_GROUP:
switch (object_property) {
case PROP_PRESENT_VALUE:
return true;
default:
break;
}
break;
case OBJECT_CHANNEL:
switch (object_property) {
case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES:
return false;
default:
break;
}
break;
default:
break;
}
if ((object_property >= PROP_PROPRIETARY_RANGE_MIN) &&
(object_property <= PROP_PROPRIETARY_RANGE_MAX)) {
/* all proprietary properties could be a BACnetLIST */
return true;
}
return property_list_member(Properties_BACnetLIST, object_property);
}
/**
* @brief Determine if the object property is a commandable member
*
* 19.2.1.1 Commandable Properties
* The prioritization scheme is applied to certain properties of objects.
* The standard commandable properties and objects are as follows.
*
* @param object_type - object-type to be checked
* @param object_property - object-property to be checked
* @return true if the property is a commandable member
*/
bool property_list_commandable_member(
BACNET_OBJECT_TYPE object_type, BACNET_PROPERTY_ID object_property)
{
bool status = false;
switch (object_type) {
case OBJECT_ACCESS_DOOR:
case OBJECT_ANALOG_OUTPUT:
case OBJECT_ANALOG_VALUE:
case OBJECT_BINARY_LIGHTING_OUTPUT:
case OBJECT_BINARY_OUTPUT:
case OBJECT_BINARY_VALUE:
case OBJECT_BITSTRING_VALUE:
case OBJECT_CHANNEL:
case OBJECT_CHARACTERSTRING_VALUE:
case OBJECT_DATE_VALUE:
case OBJECT_DATE_PATTERN_VALUE:
case OBJECT_DATETIME_VALUE:
case OBJECT_DATETIME_PATTERN_VALUE:
case OBJECT_INTEGER_VALUE:
case OBJECT_LARGE_ANALOG_VALUE:
case OBJECT_LIGHTING_OUTPUT:
case OBJECT_MULTI_STATE_OUTPUT:
case OBJECT_MULTI_STATE_VALUE:
case OBJECT_OCTETSTRING_VALUE:
case OBJECT_POSITIVE_INTEGER_VALUE:
case OBJECT_TIME_VALUE:
case OBJECT_TIME_PATTERN_VALUE:
if (object_property == PROP_PRESENT_VALUE) {
status = true;
}
break;
default:
break;
}
return status;
}
/**
* @brief Determine if the object property is a READ-ONLY property
* @param object_type - object-type to be checked
* @param object_property - object-property to be checked
* @return true if the property is a READ-ONLY property
* @note generally read-only per EPICS property gathering,
* sometimes stated explicitly in the standard and sometimes not.
* Used in the EPICS tools to determine if a property should
* avoid being written to determine writability of the property.
*/
bool property_list_read_only_member(
BACNET_OBJECT_TYPE object_type, BACNET_PROPERTY_ID object_property)
{
/* exceptions where the property is READ-ONLY only in specific objects */
switch (object_type) {
case OBJECT_AVERAGING:
switch (object_property) {
case PROP_MINIMUM_VALUE:
case PROP_MINIMUM_VALUE_TIMESTAMP:
case PROP_AVERAGE_VALUE:
case PROP_VARIANCE_VALUE:
case PROP_MAXIMUM_VALUE:
case PROP_MAXIMUM_VALUE_TIMESTAMP:
return true;
default:
break;
}
break;
case OBJECT_CALENDAR:
switch (object_property) {
case PROP_PRESENT_VALUE:
return true;
default:
break;
}
break;
case OBJECT_COMMAND:
switch (object_property) {
case PROP_IN_PROCESS:
case PROP_ALL_WRITES_SUCCESSFUL:
return true;
default:
break;
}
break;
case OBJECT_DEVICE:
switch (object_property) {
case PROP_SYSTEM_STATUS:
case PROP_VENDOR_NAME:
case PROP_VENDOR_IDENTIFIER:
case PROP_MODEL_NAME:
case PROP_FIRMWARE_REVISION:
case PROP_APPLICATION_SOFTWARE_VERSION:
case PROP_PROTOCOL_VERSION:
case PROP_PROTOCOL_REVISION:
case PROP_PROTOCOL_SERVICES_SUPPORTED:
case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
case PROP_SEGMENTATION_SUPPORTED:
case PROP_ACTIVE_VT_SESSIONS:
case PROP_DATABASE_REVISION:
case PROP_LAST_RESTORE_TIME:
case PROP_TIME_OF_DEVICE_RESTART:
case PROP_BACKUP_AND_RESTORE_STATE:
case PROP_ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS:
case PROP_ACTIVE_COV_SUBSCRIPTIONS:
return true;
default:
break;
}
break;
case OBJECT_PROGRAM:
switch (object_property) {
case PROP_REASON_FOR_HALT:
return true;
default:
break;
}
break;
case OBJECT_FILE:
switch (object_property) {
case PROP_MODIFICATION_DATE:
return true;
default:
break;
}
break;
case OBJECT_PULSE_CONVERTER:
switch (object_property) {
case PROP_COUNT_BEFORE_CHANGE:
return true;
default:
break;
}
break;
case OBJECT_TRENDLOG:
case OBJECT_EVENT_LOG:
case OBJECT_AUDIT_LOG:
case OBJECT_TREND_LOG_MULTIPLE:
switch (object_property) {
case PROP_TOTAL_RECORD_COUNT:
case PROP_RECORDS_SINCE_NOTIFICATION:
case PROP_LAST_NOTIFY_RECORD:
case PROP_LOG_BUFFER:
return true;
default:
break;
}
break;
case OBJECT_GLOBAL_GROUP:
switch (object_property) {
case PROP_MEMBER_STATUS_FLAGS:
return true;
default:
break;
}
break;
case OBJECT_CHANNEL:
switch (object_property) {
case PROP_WRITE_STATUS:
return true;
default:
break;
}
break;
case OBJECT_LIGHTING_OUTPUT:
switch (object_property) {
case PROP_IN_PROGRESS:
case PROP_EGRESS_ACTIVE:
return true;
default:
break;
}
break;
case OBJECT_BINARY_LIGHTING_OUTPUT:
switch (object_property) {
case PROP_EGRESS_ACTIVE:
return true;
default:
break;
}
break;
case OBJECT_NETWORK_PORT:
switch (object_property) {
case PROP_CHANGES_PENDING:
case PROP_SLAVE_ADDRESS_BINDING:
case PROP_WRITE_STATUS:
return true;
default:
break;
}
break;
case OBJECT_LOAD_CONTROL:
switch (object_property) {
case PROP_PRESENT_VALUE:
case PROP_EXPECTED_SHED_LEVEL:
case PROP_ACTUAL_SHED_LEVEL:
return true;
default:
break;
}
break;
default:
break;
}
if ((object_property >= PROP_PROPRIETARY_RANGE_MIN) &&
(object_property <= PROP_PROPRIETARY_RANGE_MAX)) {
/* all proprietary properties could be read-only */
return true;
}
/* Some properties, like Present_Value and Reliability,
may be temporarily writable under specific test
conditions (per Addendum 2020ci), but are defined
as read-only by default.*/
if (object_property == PROP_PRESENT_VALUE) {
if (!property_list_commandable_member(object_type, object_property)) {
return true;
}
}
if (object_property == PROP_RELIABILITY) {
return true;
}
return false;
}