From 2b328266c8f22c8dc3483f3f3ccfb3ec2d3a8aa3 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 9 Dec 2025 11:02:58 -0600 Subject: [PATCH] Fixed Channel object coercion encoding of non-primitive data. (#1181) * Refactored the object-id to and from 32-bit value so channel-value could re-use the same API. * Expanded the BACnetChannelValue coercion function to include all the coercions, no coercions, and invalid datatypes described in Table 12-63 of the Channel object. Expanded unit testing code coverage for BACnetChannelValue. --- CHANGELOG.md | 3 + src/bacnet/bacdcode.c | 48 +++- src/bacnet/bacdcode.h | 7 + src/bacnet/channel_value.c | 197 ++++++++++---- test/bacnet/channel_value/src/main.c | 379 +++++++++++++++++++++++++++ 5 files changed, 579 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2175d312..45249b75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,9 @@ The git repositories are hosted at the following sites: ### Fixed +* Fixed the BACnetChannelValue coercion function to include all the coercions, + no coercions, and invalid datatypes described in Table 12-63 of the Channel + object. Expanded unit testing code coverage for BACnetChannelValue. (#1181) * Fixed the Channel object to handle all data types that do not need coercion when written. Fixed present-value when no value is able to be encoded. (#1176) diff --git a/src/bacnet/bacdcode.c b/src/bacnet/bacdcode.c index a1927879..9c3e6b51 100644 --- a/src/bacnet/bacdcode.c +++ b/src/bacnet/bacdcode.c @@ -1846,6 +1846,40 @@ int encode_context_bitstring( return apdu_len; } +/** + * @brief Encode the BACnet Object Identifier Value + * as defined in clause 20.2.14 Encoding of an Object Identifier Value + * @param object_type - object type to be encoded + * @param instance - object instance to be encoded + * @return the 32-bit object identifier value + */ +uint32_t +bacnet_object_id_to_value(BACNET_OBJECT_TYPE object_type, uint32_t instance) +{ + return (((uint32_t)object_type & BACNET_MAX_OBJECT) + << BACNET_INSTANCE_BITS) | + (instance & BACNET_MAX_INSTANCE); +} + +/** + * @brief Decode the BACnet Object Identifier Value + * as defined in clause 20.2.14 Encoding of an Object Identifier Value + * @param value - the 32-bit object identifier value to be decoded + * @param object_type - pointer to store decoded object type + * @param instance - object instance to be decoded + */ +void bacnet_object_id_from_value( + uint32_t value, BACNET_OBJECT_TYPE *object_type, uint32_t *instance) +{ + if (object_type) { + *object_type = (BACNET_OBJECT_TYPE)(( + (value >> BACNET_INSTANCE_BITS) & BACNET_MAX_OBJECT)); + } + if (instance) { + *instance = (value & BACNET_MAX_INSTANCE); + } +} + /** * @brief Decode the BACnet Object Identifier Value * as defined in clause 20.2.14 Encoding of an Object Identifier Value @@ -1870,13 +1904,7 @@ int decode_object_id_safe( if (len_value_type == len) { if (apdu) { /* value is meaningless if apdu was NULL */ - if (object_type) { - *object_type = (BACNET_OBJECT_TYPE)(( - (value >> BACNET_INSTANCE_BITS) & BACNET_MAX_OBJECT)); - } - if (instance) { - *instance = (value & BACNET_MAX_INSTANCE); - } + bacnet_object_id_from_value(value, object_type, instance); } } @@ -2123,12 +2151,10 @@ int decode_context_object_id( int encode_bacnet_object_id( uint8_t *apdu, BACNET_OBJECT_TYPE object_type, uint32_t instance) { - uint32_t value = 0; + uint32_t value; int len; - value = - (((uint32_t)object_type & BACNET_MAX_OBJECT) << BACNET_INSTANCE_BITS) | - (instance & BACNET_MAX_INSTANCE); + value = bacnet_object_id_to_value(object_type, instance); len = encode_unsigned32(apdu, value); return len; diff --git a/src/bacnet/bacdcode.h b/src/bacnet/bacdcode.h index d9f5a888..7de36ab2 100644 --- a/src/bacnet/bacdcode.h +++ b/src/bacnet/bacdcode.h @@ -312,6 +312,13 @@ BACNET_STACK_EXPORT int bacnet_double_application_decode( const uint8_t *apdu, uint32_t apdu_len_max, double *value); +BACNET_STACK_EXPORT +uint32_t +bacnet_object_id_to_value(BACNET_OBJECT_TYPE object_type, uint32_t instance); +BACNET_STACK_EXPORT +void bacnet_object_id_from_value( + uint32_t value, BACNET_OBJECT_TYPE *object_type, uint32_t *instance); + BACNET_STACK_EXPORT int encode_bacnet_object_id( uint8_t *apdu, BACNET_OBJECT_TYPE object_type, uint32_t instance); diff --git a/src/bacnet/channel_value.c b/src/bacnet/channel_value.c index 336cf02c..af818a2f 100644 --- a/src/bacnet/channel_value.c +++ b/src/bacnet/channel_value.c @@ -740,73 +740,97 @@ static int channel_value_coerce_data_encode( uint32_t unsigned_value = 0; int32_t signed_value = 0; bool boolean_value = false; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t instance = 0; if (!value) { return BACNET_STATUS_ERROR; } + if (value->tag == tag) { + /* no coercion */ + return bacnet_channel_value_type_encode(apdu, value); + } switch (value->tag) { case BACNET_APPLICATION_TAG_NULL: if ((tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) || - (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND)) { + (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND) || + (tag == BACNET_APPLICATION_TAG_XY_COLOR)) { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } else { /* no coercion */ - if (apdu) { - *apdu = value->tag; - } - apdu_len++; + apdu_len = encode_application_null(apdu); } break; #if defined(CHANNEL_BOOLEAN) case BACNET_APPLICATION_TAG_BOOLEAN: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - apdu_len = - encode_application_boolean(apdu, value->type.Boolean); + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + /* The Boolean value FALSE is mapped to 0 + and TRUE is mapped to 1.*/ if (value->type.Boolean) { unsigned_value = 1; } apdu_len = encode_application_unsigned(apdu, unsigned_value); } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + /* The Boolean value FALSE is mapped to 0 + and TRUE is mapped to 1.*/ if (value->type.Boolean) { signed_value = 1; } apdu_len = encode_application_signed(apdu, signed_value); } else if (tag == BACNET_APPLICATION_TAG_REAL) { + /* The Boolean value FALSE is mapped to 0 + and TRUE is mapped to 1.*/ if (value->type.Boolean) { - float_value = 1; + float_value = 1.0f; } apdu_len = encode_application_real(apdu, float_value); } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + /* The Boolean value FALSE is mapped to 0 + and TRUE is mapped to 1.*/ if (value->type.Boolean) { - double_value = 1; + double_value = 1.0; } apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + /* The Boolean value FALSE is mapped to 0 + and TRUE is mapped to 1.*/ if (value->type.Boolean) { unsigned_value = 1; } apdu_len = encode_application_enumerated(apdu, unsigned_value); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif #if defined(CHANNEL_UNSIGNED) case BACNET_APPLICATION_TAG_UNSIGNED_INT: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); + } else if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + /* The numeric value 0 maps to FALSE + and anything else is TRUE.*/ if (value->type.Unsigned_Int) { boolean_value = true; } apdu_len = encode_application_boolean(apdu, boolean_value); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - unsigned_value = value->type.Unsigned_Int; - apdu_len = encode_application_unsigned(apdu, unsigned_value); } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { if (value->type.Unsigned_Int <= 2147483647) { signed_value = value->type.Unsigned_Int; apdu_len = encode_application_signed(apdu, signed_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_REAL) { @@ -814,6 +838,10 @@ static int channel_value_coerce_data_encode( float_value = (float)value->type.Unsigned_Int; apdu_len = encode_application_real(apdu, float_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { @@ -822,14 +850,26 @@ static int channel_value_coerce_data_encode( } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { unsigned_value = value->type.Unsigned_Int; apdu_len = encode_application_enumerated(apdu, unsigned_value); + } else if (tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + bacnet_object_id_from_value( + value->type.Unsigned_Int, &object_type, &instance); + apdu_len = + encode_application_object_id(apdu, object_type, instance); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif #if defined(CHANNEL_SIGNED) case BACNET_APPLICATION_TAG_SIGNED_INT: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); + } else if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + /* The numeric value 0 maps to FALSE + and anything else is TRUE.*/ if (value->type.Signed_Int) { boolean_value = true; } @@ -841,16 +881,21 @@ static int channel_value_coerce_data_encode( apdu_len = encode_application_unsigned(apdu, unsigned_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - signed_value = value->type.Signed_Int; - apdu_len = encode_application_signed(apdu, signed_value); } else if (tag == BACNET_APPLICATION_TAG_REAL) { if (value->type.Signed_Int <= 9999999) { float_value = (float)value->type.Signed_Int; apdu_len = encode_application_real(apdu, float_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { @@ -860,13 +905,20 @@ static int channel_value_coerce_data_encode( unsigned_value = value->type.Signed_Int; apdu_len = encode_application_enumerated(apdu, unsigned_value); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif #if defined(CHANNEL_REAL) case BACNET_APPLICATION_TAG_REAL: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); + } else if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + /* The numeric value 0 maps to FALSE + and anything else is TRUE.*/ if (islessgreater(value->type.Real, 0.0F)) { boolean_value = true; } @@ -878,6 +930,10 @@ static int channel_value_coerce_data_encode( apdu_len = encode_application_unsigned(apdu, unsigned_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { @@ -886,11 +942,12 @@ static int channel_value_coerce_data_encode( signed_value = (int32_t)value->type.Real; apdu_len = encode_application_signed(apdu, signed_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - float_value = value->type.Real; - apdu_len = encode_application_real(apdu, float_value); } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = value->type.Real; apdu_len = encode_application_double(apdu, double_value); @@ -901,16 +958,27 @@ static int channel_value_coerce_data_encode( apdu_len = encode_application_enumerated(apdu, unsigned_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif #if defined(CHANNEL_DOUBLE) case BACNET_APPLICATION_TAG_DOUBLE: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); + } else if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + /* The numeric value 0 maps to FALSE + and anything else is TRUE.*/ if (islessgreater(value->type.Double, 0.0)) { boolean_value = true; } @@ -922,6 +990,10 @@ static int channel_value_coerce_data_encode( apdu_len = encode_application_unsigned(apdu, unsigned_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { @@ -930,6 +1002,10 @@ static int channel_value_coerce_data_encode( signed_value = (int32_t)value->type.Double; apdu_len = encode_application_signed(apdu, signed_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_REAL) { @@ -938,11 +1014,12 @@ static int channel_value_coerce_data_encode( float_value = (float)value->type.Double; apdu_len = encode_application_real(apdu, float_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - double_value = value->type.Double; - apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { if ((value->type.Double >= 0.0) && (value->type.Double <= 2147483000.0)) { @@ -950,16 +1027,27 @@ static int channel_value_coerce_data_encode( apdu_len = encode_application_enumerated(apdu, unsigned_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif #if defined(CHANNEL_ENUMERATED) case BACNET_APPLICATION_TAG_ENUMERATED: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); + } else if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + /* The numeric value 0 maps to FALSE + and anything else is TRUE.*/ if (value->type.Enumerated) { boolean_value = true; } @@ -972,6 +1060,10 @@ static int channel_value_coerce_data_encode( signed_value = value->type.Enumerated; apdu_len = encode_application_signed(apdu, signed_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_REAL) { @@ -979,49 +1071,66 @@ static int channel_value_coerce_data_encode( float_value = (float)value->type.Enumerated; apdu_len = encode_application_real(apdu, float_value); } else { + /* Those cases where coercion of values exceeds + a range specified by an indicated coercion rule + shall be considered as coercion failures and + the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = (double)value->type.Enumerated; apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - unsigned_value = value->type.Enumerated; - apdu_len = encode_application_enumerated(apdu, unsigned_value); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif -#if defined(CHANNEL_LIGHTING_COMMAND) - case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: - if (tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) { - apdu_len = lighting_command_encode( - apdu, &value->type.Lighting_Command); +#if defined(CHANNEL_DATE) + case BACNET_APPLICATION_TAG_DATE: + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif -#if defined(CHANNEL_COLOR_COMMAND) - case BACNET_APPLICATION_TAG_COLOR_COMMAND: - if (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND) { - apdu_len = - color_command_encode(apdu, &value->type.Color_Command); +#if defined(CHANNEL_TIME) + case BACNET_APPLICATION_TAG_TIME: + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif -#if defined(CHANNEL_XY_COLOR) - case BACNET_APPLICATION_TAG_XY_COLOR: - if (tag == BACNET_APPLICATION_TAG_XY_COLOR) { - apdu_len = xy_color_encode(apdu, &value->type.XY_Color); +#if defined(CHANNEL_OBJECT_ID) + case BACNET_APPLICATION_TAG_OBJECT_ID: + if (tag == BACNET_APPLICATION_TAG_NULL) { + apdu_len = encode_application_null(apdu); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + unsigned_value = bacnet_object_id_to_value( + value->type.Object_Id.type, value->type.Object_Id.instance); + apdu_len = encode_application_unsigned(apdu, unsigned_value); } else { + /* Those cases where Invalid Datatype (ID) is indicated + in Table 12-63 shall be considered as coercion failures + and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; } break; #endif default: + /* Not defined in Table 12-63 shall be considered as coercion + failures and the write shall not occur. */ apdu_len = BACNET_STATUS_ERROR; break; } diff --git a/test/bacnet/channel_value/src/main.c b/test/bacnet/channel_value/src/main.c index b9ef0b3a..eba597b5 100644 --- a/test/bacnet/channel_value/src/main.c +++ b/test/bacnet/channel_value/src/main.c @@ -12,6 +12,7 @@ #include #include #include +#include "bacnet/bacdcode.h" #include "bacnet/bactext.h" #include "bacnet/channel_value.h" @@ -51,6 +52,27 @@ static void test_BACnetChannelValue(void) { .tag = BACNET_APPLICATION_TAG_ENUMERATED, .type.Enumerated = 0x0BADF00D, .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_OCTET_STRING, + .type.Octet_String = { .length = 4, + .value = { 0xDE, 0xAD, 0xBE, 0xEF } }, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_CHARACTER_STRING, + .type.Character_String = { .encoding = CHARACTER_UTF8, + .length = 11, + .value = "Hello World" }, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_BIT_STRING, + .type.Bit_String = { .bits_used = 10, .value = { 0xFF, 0x03 } }, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_DATE, + .type.Date = { .year = 2024, .month = 10, .day = 31, .wday = 4 }, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_TIME, + .type.Time = { .hour = 13, .min = 45, .sec = 30, .hundredths = 50 }, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_OBJECT_ID, + .type.Object_Id = { .type = OBJECT_ANALOG_INPUT, .instance = 12345 }, + .next = NULL }, { .tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND, .type.Lighting_Command.operation = BACNET_LIGHTS_NONE, .next = NULL }, @@ -68,6 +90,7 @@ static void test_BACnetChannelValue(void) } ascii_values[] = { { "NULL", BACNET_APPLICATION_TAG_NULL }, { "FALSE", BACNET_APPLICATION_TAG_BOOLEAN }, + { "TRUE", BACNET_APPLICATION_TAG_BOOLEAN }, { "1234567890", BACNET_APPLICATION_TAG_UNSIGNED_INT }, { "-1234567890", BACNET_APPLICATION_TAG_SIGNED_INT }, { "3.141592654", BACNET_APPLICATION_TAG_REAL }, @@ -85,10 +108,301 @@ static void test_BACnetChannelValue(void) }; size_t i; BACNET_CHANNEL_VALUE test_value = { 0 }; + BACNET_APPLICATION_TAG test_tag, expected_tag; + BACNET_TAG tag = { 0 }; + BACNET_CHANNEL_VALUE coercion_value = { 0 }; + struct channel_value_coercion_values { + BACNET_CHANNEL_VALUE value; + BACNET_APPLICATION_TAG tag; + BACNET_APPLICATION_TAG expected_tag; + } coercion_values[] = { + /* add valid coercion test cases here */ + { { .tag = BACNET_APPLICATION_TAG_NULL }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_NULL }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_NULL }, + /* Boolean */ + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = false }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = false }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = true }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = false }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = true }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = false }, + BACNET_APPLICATION_TAG_REAL, + BACNET_APPLICATION_TAG_REAL }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = true }, + BACNET_APPLICATION_TAG_REAL, + BACNET_APPLICATION_TAG_REAL }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = false }, + BACNET_APPLICATION_TAG_DOUBLE, + BACNET_APPLICATION_TAG_DOUBLE }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = true }, + BACNET_APPLICATION_TAG_DOUBLE, + BACNET_APPLICATION_TAG_DOUBLE }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = false }, + BACNET_APPLICATION_TAG_ENUMERATED, + BACNET_APPLICATION_TAG_ENUMERATED }, + { { .tag = BACNET_APPLICATION_TAG_BOOLEAN, .type.Boolean = true }, + BACNET_APPLICATION_TAG_ENUMERATED, + BACNET_APPLICATION_TAG_ENUMERATED }, + /* Unsigned Integer */ + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 1 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 2147483647UL }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 2147483647UL + 1UL }, + BACNET_APPLICATION_TAG_SIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 9999999UL }, + BACNET_APPLICATION_TAG_REAL, + BACNET_APPLICATION_TAG_REAL }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 9999999UL + 1UL }, + BACNET_APPLICATION_TAG_REAL, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 1UL }, + BACNET_APPLICATION_TAG_DOUBLE, + BACNET_APPLICATION_TAG_DOUBLE }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 1UL }, + BACNET_APPLICATION_TAG_ENUMERATED, + BACNET_APPLICATION_TAG_ENUMERATED }, + { { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 1UL }, + BACNET_APPLICATION_TAG_OBJECT_ID, + BACNET_APPLICATION_TAG_OBJECT_ID }, + /* Signed Integer */ + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 0 }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 0 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 1 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 0 }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, + .type.Signed_Int = 2147483647 }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = -1L }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, + .type.Signed_Int = 9999999L }, + BACNET_APPLICATION_TAG_REAL, + BACNET_APPLICATION_TAG_REAL }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, + .type.Signed_Int = 9999999L + 1L }, + BACNET_APPLICATION_TAG_REAL, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 1L }, + BACNET_APPLICATION_TAG_DOUBLE, + BACNET_APPLICATION_TAG_DOUBLE }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 1L }, + BACNET_APPLICATION_TAG_ENUMERATED, + BACNET_APPLICATION_TAG_ENUMERATED }, + { { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, .type.Signed_Int = 1L }, + BACNET_APPLICATION_TAG_OBJECT_ID, + MAX_BACNET_APPLICATION_TAG }, + /* REAL */ + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 0.0f }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 0.0f }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 1.0f }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 0.0f }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = -1.0f }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 9999999.0f }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = -1.0f }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_REAL, + .type.Real = 214783000.0F + 9999.0f }, + BACNET_APPLICATION_TAG_SIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 1.0f }, + BACNET_APPLICATION_TAG_DOUBLE, + BACNET_APPLICATION_TAG_DOUBLE }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 1.0f }, + BACNET_APPLICATION_TAG_ENUMERATED, + BACNET_APPLICATION_TAG_ENUMERATED }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = -1.0f }, + BACNET_APPLICATION_TAG_ENUMERATED, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 1.0f }, + BACNET_APPLICATION_TAG_OBJECT_ID, + MAX_BACNET_APPLICATION_TAG }, + /* Double */ + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 0.0 }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 0.0 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 1.0 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 0.0 }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = -1.0 }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 9999999.0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = -1.0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, + .type.Double = 214783000.0 + 9999.0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 1.0 }, + BACNET_APPLICATION_TAG_REAL, + BACNET_APPLICATION_TAG_REAL }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 3.4E+40 }, + BACNET_APPLICATION_TAG_REAL, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 1.0 }, + BACNET_APPLICATION_TAG_ENUMERATED, + BACNET_APPLICATION_TAG_ENUMERATED }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = -1.0 }, + BACNET_APPLICATION_TAG_ENUMERATED, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_DOUBLE, .type.Double = 1.0 }, + BACNET_APPLICATION_TAG_OBJECT_ID, + MAX_BACNET_APPLICATION_TAG }, + /* Enumerated */ + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, .type.Unsigned_Int = 1 }, + BACNET_APPLICATION_TAG_BOOLEAN, + BACNET_APPLICATION_TAG_BOOLEAN }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, .type.Unsigned_Int = 0 }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 2147483647UL }, + BACNET_APPLICATION_TAG_SIGNED_INT, + BACNET_APPLICATION_TAG_SIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 2147483647UL + 1UL }, + BACNET_APPLICATION_TAG_SIGNED_INT, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 9999999UL }, + BACNET_APPLICATION_TAG_REAL, + BACNET_APPLICATION_TAG_REAL }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 9999999UL + 1UL }, + BACNET_APPLICATION_TAG_REAL, + MAX_BACNET_APPLICATION_TAG }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 1UL }, + BACNET_APPLICATION_TAG_DOUBLE, + BACNET_APPLICATION_TAG_DOUBLE }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 1UL }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Unsigned_Int = 1UL }, + BACNET_APPLICATION_TAG_OBJECT_ID, + MAX_BACNET_APPLICATION_TAG }, + /* DATE */ + { { .tag = BACNET_APPLICATION_TAG_DATE }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_DATE }, + BACNET_APPLICATION_TAG_BOOLEAN, + MAX_BACNET_APPLICATION_TAG }, + /* TIME */ + { { .tag = BACNET_APPLICATION_TAG_TIME }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_TIME }, + BACNET_APPLICATION_TAG_BOOLEAN, + MAX_BACNET_APPLICATION_TAG }, + /* Object Identifier */ + { { .tag = BACNET_APPLICATION_TAG_OBJECT_ID, + .type.Object_Id = { .type = OBJECT_DEVICE, .instance = 12345 } }, + BACNET_APPLICATION_TAG_NULL, + BACNET_APPLICATION_TAG_NULL }, + { { .tag = BACNET_APPLICATION_TAG_OBJECT_ID, + .type.Object_Id = { .type = OBJECT_LOOP, .instance = 12345 } }, + BACNET_APPLICATION_TAG_UNSIGNED_INT, + BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { { .tag = BACNET_APPLICATION_TAG_OBJECT_ID, + .type.Object_Id = { .type = OBJECT_LOOP, .instance = 12345 } }, + BACNET_APPLICATION_TAG_BOOLEAN, + MAX_BACNET_APPLICATION_TAG }, + /* negative testing */ + { { .tag = BACNET_APPLICATION_TAG_EMPTYLIST }, + BACNET_APPLICATION_TAG_EMPTYLIST, + MAX_BACNET_APPLICATION_TAG }, + }; bacnet_channel_value_link_array(case_value, ARRAY_SIZE(case_value)); value = &case_value[0]; while (value) { + /* no coercion path */ null_len = bacnet_channel_value_encode(NULL, sizeof(apdu), value); if (value->tag != BACNET_APPLICATION_TAG_NULL) { zassert_not_equal(null_len, 0, NULL); @@ -110,6 +424,10 @@ static void test_BACnetChannelValue(void) zassert_true( status, "decode: different: %s", bactext_application_tag_name(value->tag)); + status = bacnet_channel_value_copy(NULL, value); + zassert_false(status, NULL); + status = bacnet_channel_value_copy(&test_value, NULL); + zassert_false(status, NULL); status = bacnet_channel_value_copy(&test_value, value); zassert_true( status, "copy: failed: %s", @@ -118,8 +436,69 @@ static void test_BACnetChannelValue(void) zassert_true( status, "copy: different: %s", bactext_application_tag_name(value->tag)); + /* coercion path */ + null_len = bacnet_channel_value_coerce_data_encode( + NULL, sizeof(apdu), value, value->tag); + if (value->tag != BACNET_APPLICATION_TAG_NULL) { + zassert_not_equal(null_len, 0, NULL); + } + apdu_len = bacnet_channel_value_coerce_data_encode( + apdu, sizeof(apdu), value, value->tag); + zassert_equal( + apdu_len, null_len, "value->tag: %s len=%d null_len=%d", + bactext_application_tag_name(value->tag), apdu_len, null_len); + test_len = bacnet_channel_value_decode(NULL, apdu_len, &test_value); + zassert_equal(test_len, BACNET_STATUS_ERROR, NULL); + test_len = bacnet_channel_value_decode(apdu, apdu_len, NULL); + zassert_equal(test_len, BACNET_STATUS_ERROR, NULL); + test_len = bacnet_channel_value_decode(apdu, apdu_len, &test_value); + zassert_not_equal( + test_len, BACNET_STATUS_ERROR, "value->tag: %s test_len=%d", + bactext_application_tag_name(value->tag), test_len); + zassert_equal(test_len, apdu_len, NULL); + zassert_equal( + value->tag, test_value.tag, "value->tag: %s test_tag=%s", + bactext_application_tag_name(value->tag), + bactext_application_tag_name(test_value.tag)); + status = bacnet_channel_value_same(value, &test_value); + zassert_true( + status, "decode: different: %s", + bactext_application_tag_name(value->tag)); + /* next test case */ value = value->next; } + apdu_len = bacnet_channel_value_coerce_data_encode( + apdu, sizeof(apdu), NULL, BACNET_APPLICATION_TAG_NULL); + zassert_equal(apdu_len, BACNET_STATUS_ERROR, NULL); + for (i = 0; i < ARRAY_SIZE(coercion_values); i++) { + bacnet_channel_value_copy(&coercion_value, &coercion_values[i].value); + test_tag = coercion_values[i].tag; + expected_tag = coercion_values[i].expected_tag; + /* coercion path */ + apdu_len = bacnet_channel_value_coerce_data_encode( + apdu, sizeof(apdu), &coercion_value, test_tag); + if (apdu_len != BACNET_STATUS_ERROR) { + test_len = bacnet_tag_decode(apdu, apdu_len, &tag); + zassert_not_equal( + test_len, 0, "tag decode failed len=%d", test_len); + zassert_true(tag.application, "tag is not application"); + zassert_equal( + tag.number, expected_tag, + "value->tag: %s coerce-to: %s expected=%s apdu=%s len=%d", + bactext_application_tag_name(coercion_value.tag), + bactext_application_tag_name(test_tag), + bactext_application_tag_name(expected_tag), + bactext_application_tag_name(tag.number), apdu_len); + } else { + zassert_equal( + expected_tag, MAX_BACNET_APPLICATION_TAG, + "value->tag: %s coerce-to: %s len=%d", + bactext_application_tag_name(coercion_value.tag), + bactext_application_tag_name(test_tag), apdu_len); + } + } + status = bacnet_channel_value_from_ascii(NULL, NULL); + zassert_false(status, NULL); for (i = 0; i < ARRAY_SIZE(ascii_values); i++) { status = bacnet_channel_value_from_ascii( &test_value, ascii_values[i].string);