From c8a6ea74b898bbf03b5098028fc856143cc590bb Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Wed, 14 Jun 2023 17:25:38 -0500 Subject: [PATCH] Fix WPM decoding. Add unit test for WPM. (#438) Co-authored-by: Steve Karg --- src/bacnet/wpm.c | 25 +++++- src/bacnet/wpm.h | 18 ++-- test/CMakeLists.txt | 1 + test/bacnet/wpm/CMakeLists.txt | 62 +++++++++++++ test/bacnet/wpm/src/main.c | 153 +++++++++++++++++++++++++++++++++ 5 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 test/bacnet/wpm/CMakeLists.txt create mode 100644 test/bacnet/wpm/src/main.c diff --git a/src/bacnet/wpm.c b/src/bacnet/wpm.c index c96cc4d6..2e157f46 100644 --- a/src/bacnet/wpm.c +++ b/src/bacnet/wpm.c @@ -473,7 +473,7 @@ int wpm_error_ack_decode_apdu( } if (decode_is_opening_tag_number(apdu_offset, 0)) { len = 1; - apdu_len -= len; + apdu_len += len; if (apdu) { apdu_offset = &apdu[apdu_len]; if (apdu_size > len) { @@ -509,7 +509,7 @@ int wpm_error_ack_decode_apdu( } if (decode_is_closing_tag_number(apdu_offset, 0)) { len = 1; - apdu_len -= len; + apdu_len += len; if (apdu) { apdu_offset = &apdu[apdu_len]; if (apdu_size > len) { @@ -527,7 +527,7 @@ int wpm_error_ack_decode_apdu( } if (decode_is_opening_tag_number(apdu_offset, 1)) { len = 1; - apdu_len -= len; + apdu_len += len; if (apdu) { apdu_offset = &apdu[apdu_len]; if (apdu_size > len) { @@ -571,3 +571,22 @@ int wpm_error_ack_decode_apdu( return apdu_len; } + +/** + * @brief Convert an array of BACnetWriteAccessData to linked list + * @param array pointer to element zero of the array + * @param size number of elements in the array +*/ +void wpm_write_access_data_link_array( + BACNET_WRITE_ACCESS_DATA *array, + size_t size) +{ + size_t i = 0; + + for (i = 0; i < size; i++) { + if (i > 0) { + array[i - 1].next = &array[i]; + } + array[i].next = NULL; + } +} diff --git a/src/bacnet/wpm.h b/src/bacnet/wpm.h index 546fd50e..760d708b 100644 --- a/src/bacnet/wpm.h +++ b/src/bacnet/wpm.h @@ -46,16 +46,17 @@ extern "C" { } BACNET_WRITE_ACCESS_DATA; /* decode the service request only */ + BACNET_STACK_EXPORT int wpm_decode_object_id( uint8_t * apdu, uint16_t apdu_len, - BACNET_WRITE_PROPERTY_DATA * data); + BACNET_WRITE_PROPERTY_DATA * wpdata); + BACNET_STACK_EXPORT int wpm_decode_object_property( uint8_t * apdu, uint16_t apdu_len, - BACNET_WRITE_PROPERTY_DATA * wpm_data); - + BACNET_WRITE_PROPERTY_DATA * wpdata); /* encode objects */ BACNET_STACK_EXPORT @@ -68,13 +69,13 @@ extern "C" { BACNET_OBJECT_TYPE object_type, uint32_t object_instance); BACNET_STACK_EXPORT - int wpm_encode_apdu_object_end( - uint8_t * apdu); - BACNET_STACK_EXPORT int wpm_encode_apdu_object_property( uint8_t * apdu, BACNET_WRITE_PROPERTY_DATA * wpdata); BACNET_STACK_EXPORT + int wpm_encode_apdu_object_end( + uint8_t * apdu); + BACNET_STACK_EXPORT int wpm_encode_apdu( uint8_t * apdu, size_t max_apdu, @@ -99,6 +100,11 @@ extern "C" { uint16_t apdu_size, BACNET_WRITE_PROPERTY_DATA * wp_data); + BACNET_STACK_EXPORT + void wpm_write_access_data_link_array( + BACNET_WRITE_ACCESS_DATA *base, + size_t size); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f7ec5ff0..4ee71aa9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -76,6 +76,7 @@ list(APPEND testdirs bacnet/whohas bacnet/whois bacnet/wp + bacnet/wpm bacnet/weeklyschedule ) diff --git a/test/bacnet/wpm/CMakeLists.txt b/test/bacnet/wpm/CMakeLists.txt new file mode 100644 index 00000000..af211ce9 --- /dev/null +++ b/test/bacnet/wpm/CMakeLists.txt @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + BACAPP_ALL=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/wpm.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdest.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacerror.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/wpm/src/main.c b/test/bacnet/wpm/src/main.c new file mode 100644 index 00000000..22a4c81d --- /dev/null +++ b/test/bacnet/wpm/src/main.c @@ -0,0 +1,153 @@ +/** + * @file + * @brief Unit test for service + * @author Steve Karg + * @date May 2023 + * + * SPDX-License-Identifier: MIT + */ +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Decode service header for WritePropertyMultiple + */ +static int wpm_decode_apdu(uint8_t *apdu, unsigned apdu_len, uint8_t *invoke_id) +{ + int len = 0; + + if (!apdu) { + return BACNET_STATUS_ERROR; + } + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) { + return BACNET_STATUS_ERROR; + } + /* apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU); */ + if (invoke_id) { + *invoke_id = apdu[2]; + } + if (apdu[3] != SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE) { + return BACNET_STATUS_ERROR; + } + len = 4; + + return len; +} + +static void testWritePropertyMultiple(void) +{ + BACNET_WRITE_ACCESS_DATA write_access_data[3] = { 0 }; + BACNET_WRITE_ACCESS_DATA test_write_access_data[3] = { 0 }; + BACNET_PROPERTY_VALUE property_value[3] = { 0 }; + BACNET_PROPERTY_VALUE test_property_value[3] = { 0 }; + BACNET_WRITE_PROPERTY_DATA wp_data = { 0 }; + uint8_t invoke_id = 1; + uint8_t test_invoke_id = 0; + int apdu_len = 0; + int len = 0; + uint8_t apdu[480] = { 0 }; + int offset = 0; + uint8_t tag_number = 0; + bool status = false; + + wpm_write_access_data_link_array(write_access_data, 3); + + write_access_data[0].object_type = OBJECT_ANALOG_VALUE; + write_access_data[0].object_instance = 1; + write_access_data[0].listOfProperties = &property_value[0]; + bacapp_property_value_list_init(&property_value[0], 1); + property_value[0].propertyIdentifier = PROP_PRESENT_VALUE; + property_value[0].propertyArrayIndex = 0; + property_value[0].value.tag = BACNET_APPLICATION_TAG_REAL; + property_value[0].value.type.Real = 3.14159; + property_value[0].value.next = NULL; + property_value[0].priority = 0; + + write_access_data[1].object_type = OBJECT_ANALOG_VALUE; + write_access_data[1].object_instance = 2; + write_access_data[1].listOfProperties = &property_value[1]; + bacapp_property_value_list_init(&property_value[1], 1); + property_value[1].propertyIdentifier = PROP_PRESENT_VALUE; + property_value[1].propertyArrayIndex = 0; + property_value[1].value.tag = BACNET_APPLICATION_TAG_REAL; + property_value[1].value.type.Real = 1.41421; + property_value[1].value.next = NULL; + property_value[1].priority = 0; + + write_access_data[2].object_type = OBJECT_BINARY_VALUE; + write_access_data[2].object_instance = 1; + write_access_data[2].listOfProperties = &property_value[2]; + bacapp_property_value_list_init(&property_value[2], 1); + property_value[2].propertyIdentifier = PROP_PRESENT_VALUE; + property_value[2].propertyArrayIndex = 0; + property_value[2].value.tag = BACNET_APPLICATION_TAG_ENUMERATED; + property_value[2].value.type.Enumerated = BINARY_ACTIVE; + property_value[2].value.next = NULL; + property_value[2].priority = 0; + + apdu_len = + wpm_encode_apdu(apdu, sizeof(apdu), invoke_id, write_access_data); + zassert_not_equal(apdu_len, 0, NULL); + + wpm_write_access_data_link_array(test_write_access_data, 3); + test_write_access_data[0].listOfProperties = &property_value[0]; + bacapp_property_value_list_init(&test_property_value[0], 1); + test_write_access_data[1].listOfProperties = &property_value[1]; + bacapp_property_value_list_init(&test_property_value[1], 1); + test_write_access_data[1].listOfProperties = &property_value[2]; + bacapp_property_value_list_init(&test_property_value[2], 1); + + len = wpm_decode_apdu(apdu, apdu_len, &test_invoke_id); + zassert_not_equal(len, BACNET_STATUS_ERROR, NULL); + offset += len; + /* decode service request */ + do { + /* decode Object Identifier */ + len = wpm_decode_object_id(&apdu[offset], apdu_len - offset, &wp_data); + zassert_not_equal(len, 0, NULL); + offset += len; + /* Opening tag 1 - List of Properties */ + status = decode_is_opening_tag_number(&apdu[offset++], 1); + zassert_not_equal(status, false, NULL); + do { + /* decode a 'Property Identifier': + (3) an optional 'Property Array Index' + (4) a 'Property Value' + (5) an optional 'Priority' */ + len = wpm_decode_object_property( + &apdu[offset], apdu_len - offset, &wp_data); + zassert_not_equal(len, 0, NULL); + offset += len; + printf("WPM: type=%lu instance=%lu property=%lu " + "priority=%lu index=%ld\n", + (unsigned long)wp_data.object_type, + (unsigned long)wp_data.object_instance, + (unsigned long)wp_data.object_property, + (unsigned long)wp_data.priority, (long)wp_data.array_index); + /* Closing tag 1 - List of Properties */ + if (decode_is_closing_tag_number(&apdu[offset], 1)) { + tag_number = 1; + offset++; + } else { + /* it was not tag 1, decode next Property Identifier */ + tag_number = 0; + } + /* end decoding List of Properties for "that" object */ + } while (tag_number != 1); + } while (offset < apdu_len); +} +/** + * @} + */ + +void test_main(void) +{ + ztest_test_suite(wp_tests, ztest_unit_test(testWritePropertyMultiple)); + + ztest_run_test_suite(wp_tests); +}