/** * @file * @brief Property_List property encode decode helper * @author Steve Karg * @date 2012 * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 */ #include /* 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; }