diff --git a/bacnet-stack/include/bacapp.h b/bacnet-stack/include/bacapp.h index 5a59391f..5d3ea5c3 100644 --- a/bacnet-stack/include/bacapp.h +++ b/bacnet-stack/include/bacapp.h @@ -129,6 +129,11 @@ extern "C" { unsigned max_apdu_len, BACNET_APPLICATION_DATA_VALUE * value); + bool bacapp_decode_application_data_safe( + uint8_t * new_apdu, + uint32_t new_apdu_len, + BACNET_APPLICATION_DATA_VALUE * value); + int bacapp_encode_application_data( uint8_t * apdu, BACNET_APPLICATION_DATA_VALUE * value); diff --git a/bacnet-stack/include/bacdcode.h b/bacnet-stack/include/bacdcode.h index a331ea9b..27bd6fee 100644 --- a/bacnet-stack/include/bacdcode.h +++ b/bacnet-stack/include/bacdcode.h @@ -70,6 +70,11 @@ extern "C" { uint8_t * apdu, uint8_t * tag_number, uint32_t * value); + int decode_tag_number_and_value_safe( + uint8_t * apdu, + uint8_t apdu_len_remaining, + uint8_t * tag_number, + uint32_t * value); /* returns true if the tag is context specific */ bool decode_is_context_specific( uint8_t * apdu); @@ -171,6 +176,12 @@ extern "C" { uint16_t * object_type, uint32_t * instance); + int decode_object_id_safe( + uint8_t * apdu, + uint32_t len_value, + uint16_t * object_type, + uint32_t * instance); + int decode_context_object_id( uint8_t * apdu, uint8_t tag_number, @@ -316,7 +327,11 @@ extern "C" { int decode_bacnet_time( uint8_t * apdu, BACNET_TIME * btime); - int encode_context_time( + int decode_bacnet_time_safe( + uint8_t * apdu, + uint32_t len_value, + BACNET_TIME * btime); + int encode_context_time( uint8_t * apdu, uint8_t tag_number, BACNET_TIME * btime); @@ -351,7 +366,11 @@ extern "C" { int decode_date( uint8_t * apdu, BACNET_DATE * bdate); - int decode_application_date( + int decode_date_safe( + uint8_t * apdu, + uint32_t len_value, + BACNET_DATE * bdate); + int decode_application_date( uint8_t * apdu, BACNET_DATE * bdate); int decode_context_date( diff --git a/bacnet-stack/include/bacreal.h b/bacnet-stack/include/bacreal.h index 9323f61a..caafa57a 100644 --- a/bacnet-stack/include/bacreal.h +++ b/bacnet-stack/include/bacreal.h @@ -42,6 +42,11 @@ extern "C" { #endif /* __cplusplus */ + int decode_real_safe( + uint8_t * apdu, + uint32_t len_value, + float *real_value); + int decode_real( uint8_t * apdu, float *real_value); @@ -56,7 +61,12 @@ extern "C" { int decode_double( uint8_t * apdu, double *real_value); - int encode_bacnet_double( + int decode_double_safe( + uint8_t * apdu, + uint32_t len_value, + double *double_value); + + int encode_bacnet_double( double value, uint8_t * apdu); diff --git a/bacnet-stack/src/bacapp.c b/bacnet-stack/src/bacapp.c index 79418db2..34bf86d5 100644 --- a/bacnet-stack/src/bacapp.c +++ b/bacnet-stack/src/bacapp.c @@ -157,7 +157,6 @@ int bacapp_decode_data( { int len = 0; - if (apdu && value) { switch (tag_data_type) { #if defined (BACAPP_NULL) @@ -186,12 +185,12 @@ int bacapp_decode_data( #endif #if defined (BACAPP_REAL) case BACNET_APPLICATION_TAG_REAL: - len = decode_real(&apdu[0], &(value->type.Real)); + len = decode_real_safe(&apdu[0], len_value_type, &(value->type.Real)); break; #endif #if defined (BACAPP_DOUBLE) case BACNET_APPLICATION_TAG_DOUBLE: - len = decode_double(&apdu[0], &(value->type.Double)); + len = decode_double_safe(&apdu[0], len_value_type, &(value->type.Double)); break; #endif #if defined (BACAPP_OCTET_STRING) @@ -224,12 +223,12 @@ int bacapp_decode_data( #endif #if defined (BACAPP_DATE) case BACNET_APPLICATION_TAG_DATE: - len = decode_date(&apdu[0], &value->type.Date); + len = decode_date_safe(&apdu[0], len_value_type, &value->type.Date); break; #endif #if defined (BACAPP_TIME) case BACNET_APPLICATION_TAG_TIME: - len = decode_bacnet_time(&apdu[0], &value->type.Time); + len = decode_bacnet_time_safe(&apdu[0], len_value_type, &value->type.Time); break; #endif #if defined (BACAPP_OBJECT_ID) @@ -237,7 +236,7 @@ int bacapp_decode_data( { uint16_t object_type = 0; uint32_t instance = 0; - len = decode_object_id(&apdu[0], &object_type, &instance); + len = decode_object_id_safe(&apdu[0], len_value_type, &object_type, &instance); value->type.Object_Id.type = object_type; value->type.Object_Id.instance = instance; } @@ -248,6 +247,10 @@ int bacapp_decode_data( } } + if ( len == 0 && tag_data_type != BACNET_APPLICATION_TAG_NULL && tag_data_type != BACNET_APPLICATION_TAG_BOOLEAN ) + { + value->tag = MAX_BACNET_APPLICATION_TAG; + } return len; } @@ -281,6 +284,73 @@ int bacapp_decode_application_data( return len; } +/* +** Usage: Similar to strtok. Call function the first time with new_apdu and new_adu_len set to apdu buffer +** to be processed. Subsequent calls should pass in NULL. +** +** Returns true if a application message is correctly parsed. +** Returns false if no more application messages are available. +** +** This function is NOT thread safe. +** +** Notes: The _safe suffix is there because the function should be relatively safe against buffer overruns. +** +*/ + +bool bacapp_decode_application_data_safe( + uint8_t * new_apdu, + uint32_t new_apdu_len, + BACNET_APPLICATION_DATA_VALUE * value) +{ + /* The static variables that store the apdu buffer between function calls */ + static uint8_t * apdu = NULL; + static int32_t apdu_len_remaining = 0; + static uint32_t apdu_len = 0; + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + + bool ret = false; + + if ( new_apdu != NULL ) + { + apdu = new_apdu; + apdu_len_remaining = new_apdu_len; + apdu_len = 0; + } + + if (value && apdu_len_remaining > 0 && !decode_is_context_specific(&apdu[apdu_len]) ) { + value->context_specific = false; + tag_len = + decode_tag_number_and_value_safe(&apdu[apdu_len], apdu_len_remaining, &tag_number, + &len_value_type); + /* If tag_len is zero, then the tag information is truncated */ + if (tag_len) { + apdu_len += tag_len; + apdu_len_remaining -= tag_len; + /* The tag is boolean then len_value_type is interpreted as value, not length, so dont bother + ** checking with apdu_len_remaining */ + if ( tag_number == BACNET_APPLICATION_TAG_BOOLEAN || len_value_type <= apdu_len_remaining ) + { + value->tag = tag_number; + len = + bacapp_decode_data(&apdu[apdu_len], tag_number, len_value_type, + value); + apdu_len += len; + apdu_len_remaining -= len; + + ret = true; + } + } + value->next = NULL; + } + + + return ret; +} + + int bacapp_encode_context_data_value( uint8_t * apdu, uint8_t context_tag_number, @@ -984,6 +1054,9 @@ bool bacapp_parse_application_data( #include #include "ctest.h" + +#include + /* generic - can be used by other unit tests returns true if matching or same, false if different */ bool bacapp_same_value( @@ -1081,15 +1154,166 @@ bool bacapp_same_value( #endif #if defined (BACAPP_BIT_STRING) case BACNET_APPLICATION_TAG_BIT_STRING: + status = + bitstring_same(&value->type.Bit_String, + &test_value->type.Bit_String); + break; +#endif + default: status = false; break; } } -#endif return status; } +void testBACnetApplicationData_Safe( + Test * pTest) +{ + int i; + uint8_t apdu[MAX_APDU]; + int len = 0; + int apdu_len; + BACNET_APPLICATION_DATA_VALUE input_value[13]; + uint32_t len_segment[13]; + uint32_t single_length_segment; + BACNET_APPLICATION_DATA_VALUE value; + + + for ( i = 0;i < 13; i++) + { + input_value[i].tag = (BACNET_APPLICATION_TAG)i; + input_value[i].context_specific = 0; + input_value[i].context_tag = 0; + input_value[i].next = NULL; + switch(input_value[i].tag) + { + case BACNET_APPLICATION_TAG_NULL: + /* NULL: no data */ + break; + + case BACNET_APPLICATION_TAG_BOOLEAN: + input_value[i].type.Boolean = true; + break; + + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + input_value[i].type.Unsigned_Int = 0xDEADBEEF; + break; + + case BACNET_APPLICATION_TAG_SIGNED_INT: + input_value[i].type.Signed_Int = 0x00C0FFEE; + break; + case BACNET_APPLICATION_TAG_REAL: + input_value[i].type.Real = 3.141592654f; + break; + case BACNET_APPLICATION_TAG_DOUBLE: + input_value[i].type.Double = 2.32323232323; + break; + + case BACNET_APPLICATION_TAG_OCTET_STRING: + octetstring_init(&input_value[i].type.Octet_String, "This is a o-string", strlen("This is a o-string")); + break; + + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + characterstring_init_ansi(&input_value[i].type.Character_String, "Hello There!"); + break; + + case BACNET_APPLICATION_TAG_BIT_STRING: + bitstring_init(&input_value[i].type.Bit_String); + bitstring_set_bit(&input_value[i].type.Bit_String, 0, true); + bitstring_set_bit(&input_value[i].type.Bit_String, 1, false); + bitstring_set_bit(&input_value[i].type.Bit_String, 2, false); + bitstring_set_bit(&input_value[i].type.Bit_String, 3, true); + bitstring_set_bit(&input_value[i].type.Bit_String, 4, false); + bitstring_set_bit(&input_value[i].type.Bit_String, 5, true); + bitstring_set_bit(&input_value[i].type.Bit_String, 6, true); + break; + + case BACNET_APPLICATION_TAG_ENUMERATED: + input_value[i].type.Enumerated = 0x0BADF00D; + break; + + case BACNET_APPLICATION_TAG_DATE: + input_value[i].type.Date.day = 10; + input_value[i].type.Date.month = 9; + input_value[i].type.Date.wday = 3; + input_value[i].type.Date.year = 1998; + break; + + case BACNET_APPLICATION_TAG_TIME: + input_value[i].type.Time.hour = 12; + input_value[i].type.Time.hundredths = 56; + input_value[i].type.Time.min = 20; + input_value[i].type.Time.sec = 41; + break; + + case BACNET_APPLICATION_TAG_OBJECT_ID: + input_value[i].type.Object_Id.instance = 1234; + input_value[i].type.Object_Id.type = 12; + break; + + default: + break; + } + single_length_segment = bacapp_encode_data(&apdu[len], &input_value[i]);; + assert(single_length_segment > 0); + /* len_segment is accumulated length */ + if ( i == 0 ) + { + len_segment[i] = single_length_segment; + } + else + { + len_segment[i] = single_length_segment + len_segment[i-1]; + } + len = len_segment[i]; + } + /* + ** Start processing packets at processivly truncated lengths + */ + + for ( apdu_len = len; apdu_len >=0; apdu_len--) + { + bool status; + bool expected_status; + for ( i = 0;i < 14; i++) + { + if ( i == 13 ) + { + expected_status = false; + } + else{ + + if ( apdu_len < len_segment[i] ) + { + expected_status = false; + } + else + { + expected_status = true; + } + } + status = bacapp_decode_application_data_safe(i == 0?apdu:NULL, apdu_len, &value); + + ct_test(pTest, status == expected_status); + if ( status ) + { + ct_test(pTest, value.tag == i); + ct_test(pTest, + bacapp_same_value(&input_value[i], &value)); + ct_test(pTest, !value.context_specific); + ct_test(pTest, value.next == NULL); + } + else + { + break; + } + } + } +} + + void testBACnetApplicationDataLength( Test * pTest) { @@ -1453,6 +1677,7 @@ void testBACnetApplicationData( return; } + #ifdef TEST_BACNET_APPLICATION_DATA int main( void) @@ -1466,7 +1691,10 @@ int main( assert(rc); rc = ct_addTestFunction(pTest, testBACnetApplicationDataLength); assert(rc); - ct_setStream(pTest, stdout); + rc = ct_addTestFunction(pTest, testBACnetApplicationData_Safe); + assert(rc); + + ct_setStream(pTest, stdout); ct_run(pTest); (void) ct_report(pTest); ct_destroy(pTest); diff --git a/bacnet-stack/src/bacdcode.c b/bacnet-stack/src/bacdcode.c index 2ed3ff2e..d2e9746a 100644 --- a/bacnet-stack/src/bacdcode.c +++ b/bacnet-stack/src/bacdcode.c @@ -344,6 +344,33 @@ int decode_tag_number( return len; } +/* Same as function above, but will safely fail is packet has been truncated */ +int decode_tag_number_safe( + uint8_t * apdu, + uint16_t apdu_len_remaining, + uint8_t * tag_number) +{ + int len = 0; /* return value */ + + /* decode the tag number first */ + if ( apdu_len_remaining >= 1 ) { + if (decode_is_extended_tag_number(&apdu[0]) && apdu_len_remaining >= 2) { + /* extended tag */ + if (tag_number) { + *tag_number = apdu[1]; + } + len = 2; + } else { + if (tag_number) { + *tag_number = (uint8_t) (apdu[0] >> 4); + } + len = 1; + } + } + return len; +} + + bool decode_is_opening_tag( uint8_t * apdu) { @@ -407,6 +434,62 @@ int decode_tag_number_and_value( return len; } +/* Same as function above, but will safely fail is packet has been truncated */ +int decode_tag_number_and_value_safe( + uint8_t * apdu, + uint8_t apdu_len_remaining, + uint8_t * tag_number, + uint32_t * value) +{ + int len = 0; + + len = decode_tag_number_safe(&apdu[0], apdu_len_remaining, tag_number); + + if ( len > 0 ) { + apdu_len_remaining -= len; + if (decode_is_extended_value(&apdu[0])) { + /* tagged as uint32_t */ + if (apdu[len] == 255 && apdu_len_remaining >= 5) { + uint32_t value32; + len++; + len += decode_unsigned32(&apdu[len], &value32); + if (value) { + *value = value32; + } + } + /* tagged as uint16_t */ + else if (apdu[len] == 254 && apdu_len_remaining >= 3) { + uint16_t value16; + len++; + len += decode_unsigned16(&apdu[len], &value16); + if (value) { + *value = value16; + } + } + /* no tag - must be uint8_t */ + else if (apdu[len] < 254 && apdu_len_remaining >= 1) { + if (value) { + *value = apdu[len]; + } + len++; + } + else { + /* packet is truncated */ + len = 0; + } + } else if (decode_is_opening_tag(&apdu[0]) && value) { + *value = 0; + } else if (decode_is_closing_tag(&apdu[0]) && value) { + /* closing tag */ + *value = 0; + } else if (value) { + /* small value */ + *value = apdu[0] & 0x07; + } + } + return len; +} + /* from clause 20.2.1.3.2 Constructed Data */ /* returns true if the tag is context specific and matches */ bool decode_is_context_tag( @@ -728,6 +811,20 @@ int decode_object_id( return len; } +int decode_object_id_safe( + uint8_t * apdu, + uint32_t len_value, + uint16_t * object_type, + uint32_t * instance) +{ + if ( len_value != 4 ) { + return 0; + } + else { + return decode_object_id(apdu, object_type, instance); + } +} + int decode_context_object_id( uint8_t * apdu, uint8_t tag_number, @@ -1396,8 +1493,9 @@ int encode_application_double( { int len = 0; - /* assumes that the tag only consumes 1 octet */ - len = encode_bacnet_double(value, &apdu[1]); + /* assumes that the tag only consumes 2 octet */ + len = encode_bacnet_double(value, &apdu[2]); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_DOUBLE, false, (uint32_t) len); @@ -1491,6 +1589,25 @@ int decode_bacnet_time( return 4; } +int decode_bacnet_time_safe( + uint8_t * apdu, + uint32_t len_value, + BACNET_TIME * btime) +{ + if ( len_value != 4 ) + { + btime->hour = 0; + btime->hundredths = 0; + btime->min = 0; + btime->sec = 0; + return len_value; + } + else + { + return decode_bacnet_time(apdu, btime); + } +} + int decode_application_time( uint8_t * apdu, BACNET_TIME * btime) @@ -1613,6 +1730,24 @@ int decode_date( return 4; } +int decode_date_safe( + uint8_t * apdu, + uint32_t len_value, + BACNET_DATE * bdate) +{ + if ( len_value != 4 ) { + bdate->day = 0; + bdate->month = 0; + bdate->wday = 0; + bdate->year = 0; + return len_value; + } + else { + return decode_date(apdu, bdate); + } +} + + int decode_application_date( uint8_t * apdu, BACNET_DATE * bdate) @@ -1870,7 +2005,7 @@ void testBACDCodeReal( return; } -void testBACDCodeDouble( +static void testBACDCodeDouble( Test * pTest) { uint8_t double_array[8] = { 0 }; @@ -1891,12 +2026,12 @@ void testBACDCodeDouble( /* a real will take up 4 octects plus a one octet tag */ apdu_len = encode_application_double(&apdu[0], value); - ct_test(pTest, apdu_len == 9); + ct_test(pTest, apdu_len == 10); /* len tells us how many octets were used for encoding the value */ len = decode_tag_number_and_value(&apdu[0], &tag_number, &long_value); ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_DOUBLE); ct_test(pTest, decode_is_context_specific(&apdu[0]) == false); - ct_test(pTest, len == 1); + ct_test(pTest, len == 2); ct_test(pTest, long_value == 8); decode_double(&apdu[len], &decoded_value); ct_test(pTest, decoded_value == value); @@ -2472,7 +2607,7 @@ void testFloatContextDecodes( ct_test(pTest, in == out); } -void testObjectIDContextDecodes( +static void testObjectIDContextDecodes( Test * pTest) { uint8_t apdu[MAX_APDU]; @@ -2501,7 +2636,7 @@ void testObjectIDContextDecodes( } -void testCharacterStringContextDecodes( +static void testCharacterStringContextDecodes( Test * pTest) { uint8_t apdu[MAX_APDU]; @@ -2701,6 +2836,8 @@ int main( rc = ct_addTestFunction(pTest, testOctetStringContextDecodes); assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeDouble); + assert(rc); /* configure output */ ct_setStream(pTest, stdout); ct_run(pTest); diff --git a/bacnet-stack/src/bacreal.c b/bacnet-stack/src/bacreal.c index ccb0bbbd..efd87945 100644 --- a/bacnet-stack/src/bacreal.c +++ b/bacnet-stack/src/bacreal.c @@ -76,6 +76,22 @@ int decode_real( return 4; } +int decode_real_safe( + uint8_t * apdu, + uint32_t len_value, + float *real_value) +{ + if ( len_value != 4 ) + { + *real_value = 0.0f; + return len_value; + } + else + { + return decode_real(apdu, real_value); + } +} + int decode_context_real( uint8_t * apdu, uint8_t tag_number, @@ -159,6 +175,22 @@ int decode_double( return 8; } +int decode_double_safe( + uint8_t * apdu, + uint32_t len_value, + double *double_value) +{ + if ( len_value != 8 ) + { + *double_value = 0.0; + return len_value; + } + else + { + return decode_double(apdu, double_value); + } +} + /* from clause 20.2.7 Encoding of a Double Precision Real Number Value */ /* returns the number of apdu bytes consumed */ int encode_bacnet_double(