Files
bacnet_stack/src/bacnet/bacaction.c
T
Steve Karg 4326128e72 Secure ReadProperty decoding and BACnetActionCommand (#702)
* 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.
2024-07-25 17:12:08 -05:00

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;
}