4326128e72
* Refactored and secured BACnetActionCommand codec into bacaction.c module for command object and added to bacapp module encode/decode with define for enabling and pseudo application tag for internal use. * Simplified bacapp_data_len() and moved into bacdcode module as bacnet_enclosed_data_len() function. * Secured ReadProperty-REQUEST and -ACK decoding. * Removed deprecated Keylist_Key() functions from usage. * Removed pseudo application datatypes from bacapp_data_decode() which only uses primitive application tag encoded values. * Defined INT_MAX when it is not already defined by compiler or libc. * Deprecated bacapp_decode_application_data_len() and bacapp_decode_context_data_len() as they are no longer used in any code in the library. * Added BACnetScale to bacapp module. Improved complex property value decoding. Refactored bacapp_decode_known_property() function. * Refactored and improved the bacapp_snprintf() function for printing EPICS. * Fixed Lighting Output WriteProperty to handle known property decoding.
569 lines
17 KiB
C
569 lines
17 KiB
C
/**
|
|
* @file
|
|
* @brief BACnetActionCommand codec used by Command objects
|
|
* @author Steve Karg <skarg@users.sourceforge.net>
|
|
* @date 2024
|
|
* @copyright SPDX-License-Identifier: MIT
|
|
*/
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
/* BACnet Stack defines - first */
|
|
#include "bacnet/bacdef.h"
|
|
/* BACnet Stack API */
|
|
#include "bacnet/bacaction.h"
|
|
#include "bacnet/bacdcode.h"
|
|
|
|
/**
|
|
* @brief Encode property value according to the application tag
|
|
* @param apdu - Pointer to the buffer to encode to, or NULL for length
|
|
* @param value - Pointer to the property value to encode from
|
|
* @return number of bytes encoded
|
|
*/
|
|
int bacnet_action_property_value_encode(
|
|
uint8_t *apdu, BACNET_ACTION_PROPERTY_VALUE *value)
|
|
{
|
|
int apdu_len = 0; /* total length of the apdu, return value */
|
|
|
|
if (!value) {
|
|
return 0;
|
|
}
|
|
switch (value->tag) {
|
|
#if defined(BACACTION_NULL)
|
|
case BACNET_APPLICATION_TAG_NULL:
|
|
if (apdu) {
|
|
apdu[0] = value->tag;
|
|
}
|
|
apdu_len++;
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_BOOLEAN)
|
|
case BACNET_APPLICATION_TAG_BOOLEAN:
|
|
apdu_len = encode_application_boolean(apdu, value->type.Boolean);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_UNSIGNED)
|
|
case BACNET_APPLICATION_TAG_UNSIGNED_INT:
|
|
apdu_len =
|
|
encode_application_unsigned(apdu, value->type.Unsigned_Int);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_SIGNED)
|
|
case BACNET_APPLICATION_TAG_SIGNED_INT:
|
|
apdu_len = encode_application_signed(apdu, value->type.Signed_Int);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_REAL)
|
|
case BACNET_APPLICATION_TAG_REAL:
|
|
apdu_len = encode_application_real(apdu, value->type.Real);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_DOUBLE)
|
|
case BACNET_APPLICATION_TAG_DOUBLE:
|
|
apdu_len = encode_application_double(apdu, value->type.Double);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_ENUMERATED)
|
|
case BACNET_APPLICATION_TAG_ENUMERATED:
|
|
apdu_len =
|
|
encode_application_enumerated(apdu, value->type.Enumerated);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Decode property value from the application buffer
|
|
* @param apdu - Pointer to the buffer to decode from
|
|
* @param apdu_size Size of the buffer to decode from
|
|
* @param value - Pointer to the property value to decode to
|
|
* @return number of bytes encoded
|
|
*/
|
|
int bacnet_action_property_value_decode(
|
|
uint8_t *apdu, uint32_t apdu_size, BACNET_ACTION_PROPERTY_VALUE *value)
|
|
{
|
|
int len = 0;
|
|
int apdu_len = 0;
|
|
BACNET_TAG tag = { 0 };
|
|
|
|
if (!value) {
|
|
return 0;
|
|
}
|
|
if (!apdu) {
|
|
return 0;
|
|
}
|
|
len = bacnet_tag_decode(apdu, apdu_size, &tag);
|
|
if ((len > 0) && tag.application) {
|
|
if (value) {
|
|
value->tag = tag.number;
|
|
}
|
|
switch (tag.number) {
|
|
#if defined(BACACTION_NULL)
|
|
case BACNET_APPLICATION_TAG_NULL:
|
|
apdu_len = len;
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_BOOLEAN)
|
|
case BACNET_APPLICATION_TAG_BOOLEAN:
|
|
apdu_len = bacnet_boolean_application_decode(
|
|
apdu, apdu_size, &value->type.Boolean);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_UNSIGNED)
|
|
case BACNET_APPLICATION_TAG_UNSIGNED_INT:
|
|
apdu_len = bacnet_unsigned_application_decode(
|
|
apdu, apdu_size, &value->type.Unsigned_Int);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_SIGNED)
|
|
case BACNET_APPLICATION_TAG_SIGNED_INT:
|
|
apdu_len = bacnet_signed_application_decode(
|
|
apdu, apdu_size, &value->type.Signed_Int);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_REAL)
|
|
case BACNET_APPLICATION_TAG_REAL:
|
|
apdu_len = bacnet_real_application_decode(
|
|
apdu, apdu_size, &value->type.Real);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_DOUBLE)
|
|
case BACNET_APPLICATION_TAG_DOUBLE:
|
|
apdu_len = bacnet_double_application_decode(
|
|
apdu, apdu_size, &value->type.Double);
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_ENUMERATED)
|
|
case BACNET_APPLICATION_TAG_ENUMERATED:
|
|
apdu_len = bacnet_enumerated_application_decode(
|
|
apdu, apdu_size, &value->type.Enumerated);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Compare two BACnetActionPropertyValue complex datatypes
|
|
* @param value1 [in] The first structure to compare
|
|
* @param value2 [in] The second structure to compare
|
|
* @return true if the two structures are the same
|
|
*/
|
|
bool bacnet_action_property_value_same(
|
|
BACNET_ACTION_PROPERTY_VALUE *value1, BACNET_ACTION_PROPERTY_VALUE *value2)
|
|
{
|
|
bool status = false; /*return value */
|
|
|
|
if ((value1 == NULL) || (value2 == NULL)) {
|
|
return false;
|
|
}
|
|
/* does the tag match? */
|
|
if (value1->tag == value2->tag) {
|
|
status = true;
|
|
}
|
|
if (status) {
|
|
/* second test for same-ness */
|
|
status = false;
|
|
/* does the value match? */
|
|
switch (value1->tag) {
|
|
#if defined(BACACTION_NULL)
|
|
case BACNET_APPLICATION_TAG_NULL:
|
|
status = true;
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_BOOLEAN)
|
|
case BACNET_APPLICATION_TAG_BOOLEAN:
|
|
if (value1->type.Boolean == value2->type.Boolean) {
|
|
status = true;
|
|
}
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_UNSIGNED)
|
|
case BACNET_APPLICATION_TAG_UNSIGNED_INT:
|
|
if (value1->type.Unsigned_Int == value2->type.Unsigned_Int) {
|
|
status = true;
|
|
}
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_SIGNED)
|
|
case BACNET_APPLICATION_TAG_SIGNED_INT:
|
|
if (value1->type.Signed_Int == value2->type.Signed_Int) {
|
|
status = true;
|
|
}
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_REAL)
|
|
case BACNET_APPLICATION_TAG_REAL:
|
|
if (!islessgreater(value1->type.Real, value2->type.Real)) {
|
|
status = true;
|
|
}
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_DOUBLE)
|
|
case BACNET_APPLICATION_TAG_DOUBLE:
|
|
if (!islessgreater(value1->type.Double, value2->type.Double)) {
|
|
status = true;
|
|
}
|
|
break;
|
|
#endif
|
|
#if defined(BACACTION_ENUMERATED)
|
|
case BACNET_APPLICATION_TAG_ENUMERATED:
|
|
if (value1->type.Enumerated == value2->type.Enumerated) {
|
|
status = true;
|
|
}
|
|
break;
|
|
#endif
|
|
case BACNET_APPLICATION_TAG_EMPTYLIST:
|
|
status = true;
|
|
break;
|
|
default:
|
|
status = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Encode the BACnetActionCommand complex datatype
|
|
*
|
|
* BACnetActionCommand ::= SEQUENCE {
|
|
* deviceIdentifier [0] BACnetObjectIdentifier OPTIONAL,
|
|
* objectIdentifier [1] BACnetObjectIdentifier,
|
|
* propertyIdentifier [2] BACnetPropertyIdentifier,
|
|
* propertyArrayIndex [3] Unsigned OPTIONAL,
|
|
* --used only with array datatype
|
|
* propertyValue [4] ABSTRACT-SYNTAX.&Type,
|
|
* priority [5] Unsigned (1..16) OPTIONAL,
|
|
* --used only when property is commandable
|
|
* postDelay [6] Unsigned OPTIONAL,
|
|
* quitOnFailure [7] BOOLEAN,
|
|
* writeSuccessful [8] BOOLEAN
|
|
* }
|
|
*
|
|
* @param apdu [out] The APDU buffer to encode into, or NULL for length
|
|
* @param entry [in] The BACNET_ACTION_LIST structure to encode
|
|
* @return The length of the encoded data, or BACNET_STATUS_REJECT on error
|
|
*/
|
|
int bacnet_action_command_encode(uint8_t *apdu, BACNET_ACTION_LIST *entry)
|
|
{
|
|
int len = 0;
|
|
int apdu_len = 0;
|
|
|
|
if (!entry) {
|
|
return BACNET_STATUS_REJECT;
|
|
}
|
|
/* deviceIdentifier [0] BACnetObjectIdentifier OPTIONAL */
|
|
if (entry->Device_Id.instance <= BACNET_MAX_INSTANCE) {
|
|
len = encode_context_object_id(
|
|
apdu, 0, entry->Device_Id.type, entry->Device_Id.instance);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_REJECT;
|
|
}
|
|
/* objectIdentifier [1] BACnetObjectIdentifier */
|
|
len = encode_context_object_id(
|
|
apdu, 1, entry->Object_Id.type, entry->Object_Id.instance);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
/* propertyIdentifier [2] BACnetPropertyIdentifier */
|
|
len = encode_context_enumerated(apdu, 2, entry->Property_Identifier);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
/* propertyArrayIndex [3] Unsigned OPTIONAL */
|
|
if (entry->Property_Array_Index != BACNET_ARRAY_ALL) {
|
|
len = encode_context_unsigned(apdu, 3, entry->Property_Array_Index);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
}
|
|
/* propertyValue [4] ABSTRACT-SYNTAX.&Type */
|
|
len = encode_opening_tag(apdu, 4);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
len = bacnet_action_property_value_encode(apdu, &entry->Value);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
len = encode_closing_tag(apdu, 4);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
/* priority [5] Unsigned (1..16) OPTIONAL */
|
|
if (entry->Priority != BACNET_NO_PRIORITY) {
|
|
len = encode_context_unsigned(apdu, 5, entry->Priority);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
}
|
|
/* postDelay [6] Unsigned OPTIONAL */
|
|
if (entry->Post_Delay != UINT32_MAX) {
|
|
len = encode_context_unsigned(apdu, 6, entry->Post_Delay);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
}
|
|
/* quitOnFailure [7] BOOLEAN */
|
|
len = encode_context_boolean(apdu, 7, entry->Quit_On_Failure);
|
|
apdu_len += len;
|
|
if (apdu) {
|
|
apdu += len;
|
|
}
|
|
/* writeSuccessful [8] BOOLEAN */
|
|
len = encode_context_boolean(apdu, 8, entry->Write_Successful);
|
|
apdu_len += len;
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Decode the BACnetActionCommand complex datatype
|
|
* @param apdu [in] The APDU buffer to decode
|
|
* @param apdu_size [in] The size of the APDU buffer
|
|
* @param tag [in] The BACNET_APPLICATION_TAG of the value
|
|
* @param entry [out] The BACNET_ACTION_LIST structure to fill
|
|
* @return The length of the decoded data, or BACNET_STATUS_ERROR on error
|
|
*/
|
|
int bacnet_action_command_decode(
|
|
uint8_t *apdu, size_t apdu_size, BACNET_ACTION_LIST *entry)
|
|
{
|
|
int len = 0;
|
|
int apdu_len = 0;
|
|
int data_len = 0;
|
|
uint32_t instance = 0;
|
|
BACNET_OBJECT_TYPE type = OBJECT_NONE; /* for decoding */
|
|
uint32_t property = 0; /* for decoding */
|
|
BACNET_UNSIGNED_INTEGER unsigned_value = 0;
|
|
bool boolean_value = false;
|
|
|
|
if (!apdu) {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* deviceIdentifier [0] BACnetObjectIdentifier OPTIONAL */
|
|
len = bacnet_object_id_context_decode(
|
|
&apdu[apdu_len], apdu_size - apdu_len, 0, &type, &instance);
|
|
if (len > 0) {
|
|
if (instance > BACNET_MAX_INSTANCE) {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Device_Id.type = type;
|
|
entry->Device_Id.instance = instance;
|
|
}
|
|
} else if (len == 0) {
|
|
/* wrong tag - optional - skip apdu_len increment */
|
|
if (entry) {
|
|
entry->Device_Id.type = OBJECT_DEVICE;
|
|
entry->Device_Id.instance = BACNET_MAX_INSTANCE;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* objectIdentifier [1] BACnetObjectIdentifier */
|
|
len = bacnet_object_id_context_decode(
|
|
&apdu[apdu_len], apdu_size - apdu_len, 1, &type, &instance);
|
|
if (len > 0) {
|
|
if (instance > BACNET_MAX_INSTANCE) {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Object_Id.type = type;
|
|
entry->Object_Id.instance = instance;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* propertyIdentifier [2] BACnetPropertyIdentifier */
|
|
len = bacnet_enumerated_context_decode(
|
|
&apdu[apdu_len], apdu_size - apdu_len, 2, &property);
|
|
if (len > 0) {
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Property_Identifier = (BACNET_PROPERTY_ID)property;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* propertyArrayIndex [3] Unsigned OPTIONAL */
|
|
len = bacnet_unsigned_context_decode(
|
|
&apdu[apdu_len], apdu_size - apdu_len, 3, &unsigned_value);
|
|
if (len > 0) {
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Property_Array_Index = unsigned_value;
|
|
}
|
|
} else {
|
|
/* wrong tag or malformed - optional - skip apdu_len increment */
|
|
if (entry) {
|
|
entry->Property_Array_Index = BACNET_ARRAY_ALL;
|
|
}
|
|
}
|
|
/* propertyValue [4] ABSTRACT-SYNTAX.&Type */
|
|
if (!bacnet_is_opening_tag_number(
|
|
&apdu[apdu_len], apdu_size - apdu_len, 4, &len)) {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* determine the length of the data blob */
|
|
data_len =
|
|
bacnet_enclosed_data_length(&apdu[apdu_len], apdu_size - apdu_len);
|
|
if (data_len == BACNET_STATUS_ERROR) {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* count the opening tag number length */
|
|
apdu_len += len;
|
|
/* decode data from the APDU */
|
|
if (data_len > MAX_APDU) {
|
|
/* not enough size in application_data to store the data chunk */
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
if (entry) {
|
|
len = bacnet_action_property_value_decode(
|
|
&apdu[apdu_len], data_len, &entry->Value);
|
|
if (len < 0) {
|
|
/* signal internal error */
|
|
entry->Value.tag = BACNET_APPLICATION_TAG_ERROR;
|
|
}
|
|
}
|
|
/* add on the data length */
|
|
apdu_len += data_len;
|
|
/* closing */
|
|
if (!bacnet_is_closing_tag_number(
|
|
&apdu[apdu_len], apdu_size - apdu_len, 4, &len)) {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* count the closing tag number length */
|
|
apdu_len += len;
|
|
/* priority [5] Unsigned (1..16) OPTIONAL */
|
|
len = bacnet_unsigned_context_decode(
|
|
&apdu[apdu_len], apdu_len - apdu_size, 5, &unsigned_value);
|
|
if (len > 0) {
|
|
apdu_len += len;
|
|
if ((unsigned_value >= BACNET_MIN_PRIORITY) &&
|
|
(unsigned_value <= BACNET_MAX_PRIORITY)) {
|
|
if (entry) {
|
|
entry->Priority = (uint8_t)unsigned_value;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
} else {
|
|
/* wrong tag or malformed - optional - skip apdu_len increment */
|
|
if (entry) {
|
|
entry->Priority = BACNET_NO_PRIORITY;
|
|
}
|
|
}
|
|
/* postDelay [6] Unsigned OPTIONAL */
|
|
len = bacnet_unsigned_context_decode(
|
|
&apdu[apdu_len], apdu_len - apdu_size, 6, &unsigned_value);
|
|
if (len > 0) {
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Post_Delay = (uint8_t)unsigned_value;
|
|
}
|
|
} else {
|
|
/* wrong tag or malformed - optional - skip apdu_len increment */
|
|
if (entry) {
|
|
entry->Post_Delay = UINT32_MAX;
|
|
}
|
|
}
|
|
/* quitOnFailure [7] BOOLEAN */
|
|
len = bacnet_boolean_context_decode(
|
|
&apdu[apdu_len], apdu_len - apdu_size, 7, &boolean_value);
|
|
if (len > 0) {
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Quit_On_Failure = boolean_value;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
/* writeSuccessful [8] BOOLEAN */
|
|
len = bacnet_boolean_context_decode(
|
|
&apdu[apdu_len], apdu_len - apdu_size, 8, &boolean_value);
|
|
if (len > 0) {
|
|
apdu_len += len;
|
|
if (entry) {
|
|
entry->Write_Successful = boolean_value;
|
|
}
|
|
} else {
|
|
return BACNET_STATUS_ERROR;
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Compare two BACnetActionCommand complex datatypes
|
|
* @param entry1 [in] The first BACNET_ACTION_LIST structure to compare
|
|
* @param entry2 [in] The second BACNET_ACTION_LIST structure to compare
|
|
* @return True if the two structures are the same, else False
|
|
*/
|
|
bool bacnet_action_command_same(
|
|
BACNET_ACTION_LIST *entry1, BACNET_ACTION_LIST *entry2)
|
|
{
|
|
if (!entry1 || !entry2) {
|
|
return false;
|
|
}
|
|
if (entry1->Device_Id.type != entry2->Device_Id.type) {
|
|
return false;
|
|
}
|
|
if (entry1->Device_Id.instance != entry2->Device_Id.instance) {
|
|
return false;
|
|
}
|
|
if (entry1->Object_Id.type != entry2->Object_Id.type) {
|
|
return false;
|
|
}
|
|
if (entry1->Object_Id.instance != entry2->Object_Id.instance) {
|
|
return false;
|
|
}
|
|
if (entry1->Property_Identifier != entry2->Property_Identifier) {
|
|
return false;
|
|
}
|
|
if (entry1->Property_Array_Index != entry2->Property_Array_Index) {
|
|
return false;
|
|
}
|
|
if (!bacnet_action_property_value_same(&entry1->Value, &entry2->Value)) {
|
|
return false;
|
|
}
|
|
if (entry1->Priority != entry2->Priority) {
|
|
return false;
|
|
}
|
|
if (entry1->Post_Delay != entry2->Post_Delay) {
|
|
return false;
|
|
}
|
|
if (entry1->Quit_On_Failure != entry2->Quit_On_Failure) {
|
|
return false;
|
|
}
|
|
if (entry1->Write_Successful != entry2->Write_Successful) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|