diff --git a/src/bacnet/bacerror.c b/src/bacnet/bacerror.c index fe43fd6c..10b6d92c 100644 --- a/src/bacnet/bacerror.c +++ b/src/bacnet/bacerror.c @@ -73,7 +73,7 @@ int bacerror_decode_error_class_and_code(uint8_t *apdu, uint32_t len_value_type = 0; uint32_t decoded_value = 0; - if (apdu_len) { + if (apdu && apdu_len) { /* error class */ len += decode_tag_number_and_value( &apdu[len], &tag_number, &len_value_type); @@ -109,15 +109,17 @@ int bacerror_decode_service_request(uint8_t *apdu, { int len = 0; - if (apdu_len > 2) { + if (apdu && apdu_len > 2) { if (invoke_id) { *invoke_id = apdu[0]; } if (service) { *service = (BACNET_CONFIRMED_SERVICE)apdu[1]; } + len += 2; + /* decode the application class and code */ - len = bacerror_decode_error_class_and_code( + len += bacerror_decode_error_class_and_code( &apdu[2], apdu_len - 2, error_class, error_code); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2722fa52..96ed7efd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,6 +33,7 @@ add_custom_command(TARGET lcov # list(APPEND testdirs + unit/bacnet/bacerror unit/bacnet/bits ) diff --git a/test/unit/bacnet/bacerror/CMakeLists.txt b/test/unit/bacnet/bacerror/CMakeLists.txt new file mode 100644 index 00000000..f62e9c2f --- /dev/null +++ b/test/unit/bacnet/bacerror/CMakeLists.txt @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(unittest_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/unit/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/unit/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 + ) + +include_directories( + ./src + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/bacerror.c + # Support files and stubs (pathname alphabetical) + # Test and test library files + ./src/main.c + ./src/fakes/bacdcode.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/unit/bacnet/bacerror/src/fakes/bacdcode.c b/test/unit/bacnet/bacerror/src/fakes/bacdcode.c new file mode 100644 index 00000000..67fd89ae --- /dev/null +++ b/test/unit/bacnet/bacerror/src/fakes/bacdcode.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023 Legrand North America, LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "fakes/bacdcode.h" + +#if 0 +DEFINE_FAKE_VALUE_FUNC(int, encode_tag, uint8_t *, uint8_t, bool, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_opening_tag, uint8_t *, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_closing_tag, uint8_t *, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, decode_tag_number, uint8_t *, uint8_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_tag_number_decode, uint8_t *, uint32_t, uint8_t *); +#endif +DEFINE_FAKE_VALUE_FUNC( + int, decode_tag_number_and_value, uint8_t *, uint8_t *, uint32_t *); +#if 0 +DEFINE_FAKE_VALUE_FUNC(int, bacnet_tag_number_and_value_decode, uint8_t *, uint32_t, uint8_t *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(bool, decode_is_opening_tag_number, uint8_t *, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, decode_is_closing_tag_number, uint8_t *, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, decode_is_context_tag, uint8_t *, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, decode_is_context_tag_with_length, uint8_t *, uint8_t, int *); +DEFINE_FAKE_VALUE_FUNC(bool, decode_is_opening_tag, uint8_t *); +DEFINE_FAKE_VALUE_FUNC(bool, decode_is_closing_tag, uint8_t *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_null, uint8_t *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_null, uint8_t *, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_boolean, uint8_t *, bool); +DEFINE_FAKE_VALUE_FUNC(bool, decode_boolean, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_boolean, uint8_t *, uint8_t, bool); +DEFINE_FAKE_VALUE_FUNC(bool, decode_context_boolean, uint8_t *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_boolean2, uint8_t *, uint8_t, bool *); +DEFINE_FAKE_VALUE_FUNC(int, decode_bitstring, uint8_t *, uint32_t, BACNET_BIT_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_bitstring, uint8_t *, uint8_t, BACNET_BIT_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_bitstring, uint8_t *, BACNET_BIT_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_bitstring, uint8_t *, BACNET_BIT_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_bitstring, uint8_t *, uint8_t, BACNET_BIT_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_real, uint8_t *, float); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_real, uint8_t *, uint8_t, float); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_real, uint8_t *, uint8_t, float *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_double, uint8_t *, double); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_double, uint8_t *, uint8_t, double); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_double, uint8_t *, uint8_t, double *); +DEFINE_FAKE_VALUE_FUNC(int, decode_object_id, uint8_t *, BACNET_OBJECT_TYPE *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, decode_object_id_safe, uint8_t *, uint32_t, BACNET_OBJECT_TYPE *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_object_id_decode, uint8_t *, uint16_t, uint32_t, BACNET_OBJECT_TYPE *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_object_id_application_decode, uint8_t *, uint16_t, BACNET_OBJECT_TYPE *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_object_id_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_OBJECT_TYPE *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_object_id, uint8_t *, uint8_t, BACNET_OBJECT_TYPE *, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_object_id, uint8_t *, BACNET_OBJECT_TYPE, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_object_id, uint8_t *, uint8_t, BACNET_OBJECT_TYPE, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_object_id, uint8_t *, BACNET_OBJECT_TYPE, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_octet_string, uint8_t *, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_octet_string, uint8_t *, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_octet_string, uint8_t *, uint8_t, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, decode_octet_string, uint8_t *, uint32_t, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_octet_string, uint8_t *, uint8_t, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_octet_string_decode, uint8_t *, uint16_t, uint32_t, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_octet_string_application_decode, uint8_t *, uint16_t, BACNET_OCTET_STRING *); +DEFINE_FAKE_VALUE_FUNC(uint32_t, encode_bacnet_character_string_safe, uint8_t *apdu, uint32_t, uint8_t, char *, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_character_string, uint8_t *, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_character_string, uint8_t *, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_character_string, uint8_t *, uint8_t, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, decode_character_string, uint8_t *, uint32_t, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_character_string, uint8_t *, uint8_t, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_character_string_decode, uint8_t *, uint16_t, uint32_t, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_character_string_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_CHARACTER_STRING *); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_unsigned, uint8_t *, BACNET_UNSIGNED_INTEGER); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_unsigned, uint8_t *, uint8_t, BACNET_UNSIGNED_INTEGER); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_unsigned, uint8_t *, BACNET_UNSIGNED_INTEGER); +DEFINE_FAKE_VALUE_FUNC(int, decode_unsigned, uint8_t *, uint32_t, BACNET_UNSIGNED_INTEGER *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_unsigned, uint8_t *, uint8_t, BACNET_UNSIGNED_INTEGER *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_unsigned_decode, uint8_t *, uint16_t, uint32_t, BACNET_UNSIGNED_INTEGER *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_unsigned_application_decode, uint8_t *, uint16_t, BACNET_UNSIGNED_INTEGER *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_unsigned_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_UNSIGNED_INTEGER *); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_signed, uint8_t *, int32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_signed, uint8_t *, int32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_signed, uint8_t *, uint8_t, int32_t); +DEFINE_FAKE_VALUE_FUNC(int, decode_signed, uint8_t *, uint32_t, int32_t *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_signed, uint8_t *, uint8_t, int32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_signed_decode, uint8_t *, uint16_t, uint32_t, int32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_signed_context_decode, uint8_t *, uint16_t, uint8_t, int32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_signed_application_decode, uint8_t *, uint16_t, int32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_enumerated_decode, uint8_t *, uint16_t, uint32_t, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_enumerated_context_decode, uint8_t *, uint16_t, uint8_t, uint32_t *); +#endif +DEFINE_FAKE_VALUE_FUNC(int, decode_enumerated, uint8_t *, uint32_t, uint32_t *); +#if 0 +DEFINE_FAKE_VALUE_FUNC(int, decode_context_enumerated, uint8_t *, uint8_t, uint32_t *); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_enumerated, uint8_t *, uint32_t); +#endif +DEFINE_FAKE_VALUE_FUNC(int, encode_application_enumerated, uint8_t *, uint32_t); +#if 0 +DEFINE_FAKE_VALUE_FUNC(int, encode_context_enumerated, uint8_t *, uint8_t, uint32_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_time, uint8_t *, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_time, uint8_t *, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, decode_bacnet_time, uint8_t *, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, decode_bacnet_time_safe, uint8_t *, uint32_t, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_time, uint8_t *, uint8_t, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, decode_application_time, uint8_t *, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_bacnet_time, uint8_t *, uint8_t, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_time_decode, uint8_t *, uint16_t, uint32_t, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_time_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, bacnet_time_application_decode, uint8_t *, uint16_t, BACNET_TIME *); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_date, uint8_t *, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(int, encode_application_date, uint8_t *, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_date, uint8_t *, uint8_t, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(int, decode_date, uint8_t *, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(int, decode_date_safe, uint8_t *, uint32_t, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(int, decode_application_date, uint8_t *, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_date, uint8_t *, uint8_t, BACNET_DATE *); +DEFINE_FAKE_VALUE_FUNC(uint8_t, encode_max_segs_max_apdu, int, int); +DEFINE_FAKE_VALUE_FUNC(int, decode_max_segs, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, decode_max_apdu, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_simple_ack, uint8_t *, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, encode_bacnet_address, uint8_t *, BACNET_ADDRESS *); +DEFINE_FAKE_VALUE_FUNC(int, decode_bacnet_address, uint8_t *, BACNET_ADDRESS *); +DEFINE_FAKE_VALUE_FUNC(int, encode_context_bacnet_address, uint8_t *, uint8_t, BACNET_ADDRESS *); +DEFINE_FAKE_VALUE_FUNC(int, decode_context_bacnet_address, uint8_t *, uint8_t, BACNET_ADDRESS *); +#endif diff --git a/test/unit/bacnet/bacerror/src/fakes/bacdcode.h b/test/unit/bacnet/bacerror/src/fakes/bacdcode.h new file mode 100644 index 00000000..f6667ac6 --- /dev/null +++ b/test/unit/bacnet/bacerror/src/fakes/bacdcode.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2023 Legrand North America, LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BACNET_STACK_TEST_UNIT_BACNET_BACERROR_FAKES_BACDCODE_H_ +#define BACNET_STACK_TEST_UNIT_BACNET_BACERROR_FAKES_BACDCODE_H_ + +#include +#include + +#include + +#define BACNET_STACK_TEST_BACNET_BACDCODE_FFF_FAKES_LIST(FAKE) \ + FAKE(decode_tag_number_and_value) \ + FAKE(decode_enumerated) \ + FAKE(encode_application_enumerated) + +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 +DECLARE_FAKE_VALUE_FUNC(int, encode_tag, uint8_t *, uint8_t, bool, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_opening_tag, uint8_t *, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_closing_tag, uint8_t *, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, decode_tag_number, uint8_t *, uint8_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_tag_number_decode, uint8_t *, uint32_t, uint8_t *); +#endif +DECLARE_FAKE_VALUE_FUNC( + int, decode_tag_number_and_value, uint8_t *, uint8_t *, uint32_t *); +#if 0 +DECLARE_FAKE_VALUE_FUNC(int, bacnet_tag_number_and_value_decode, uint8_t *, uint32_t, uint8_t *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(bool, decode_is_opening_tag_number, uint8_t *, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, decode_is_closing_tag_number, uint8_t *, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, decode_is_context_tag, uint8_t *, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, decode_is_context_tag_with_length, uint8_t *, uint8_t, int *); +DECLARE_FAKE_VALUE_FUNC(bool, decode_is_opening_tag, uint8_t *); +DECLARE_FAKE_VALUE_FUNC(bool, decode_is_closing_tag, uint8_t *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_null, uint8_t *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_null, uint8_t *, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_boolean, uint8_t *, bool); +DECLARE_FAKE_VALUE_FUNC(bool, decode_boolean, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_boolean, uint8_t *, uint8_t, bool); +DECLARE_FAKE_VALUE_FUNC(bool, decode_context_boolean, uint8_t *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_boolean2, uint8_t *, uint8_t, bool *); +DECLARE_FAKE_VALUE_FUNC(int, decode_bitstring, uint8_t *, uint32_t, BACNET_BIT_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_bitstring, uint8_t *, uint8_t, BACNET_BIT_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_bitstring, uint8_t *, BACNET_BIT_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_bitstring, uint8_t *, BACNET_BIT_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_bitstring, uint8_t *, uint8_t, BACNET_BIT_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_real, uint8_t *, float); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_real, uint8_t *, uint8_t, float); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_real, uint8_t *, uint8_t, float *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_double, uint8_t *, double); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_double, uint8_t *, uint8_t, double); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_double, uint8_t *, uint8_t, double *); +DECLARE_FAKE_VALUE_FUNC(int, decode_object_id, uint8_t *, BACNET_OBJECT_TYPE *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, decode_object_id_safe, uint8_t *, uint32_t, BACNET_OBJECT_TYPE *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_object_id_decode, uint8_t *, uint16_t, uint32_t, BACNET_OBJECT_TYPE *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_object_id_application_decode, uint8_t *, uint16_t, BACNET_OBJECT_TYPE *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_object_id_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_OBJECT_TYPE *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_object_id, uint8_t *, uint8_t, BACNET_OBJECT_TYPE *, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_object_id, uint8_t *, BACNET_OBJECT_TYPE, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_object_id, uint8_t *, uint8_t, BACNET_OBJECT_TYPE, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_object_id, uint8_t *, BACNET_OBJECT_TYPE, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_octet_string, uint8_t *, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_octet_string, uint8_t *, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_octet_string, uint8_t *, uint8_t, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, decode_octet_string, uint8_t *, uint32_t, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_octet_string, uint8_t *, uint8_t, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_octet_string_decode, uint8_t *, uint16_t, uint32_t, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_octet_string_application_decode, uint8_t *, uint16_t, BACNET_OCTET_STRING *); +DECLARE_FAKE_VALUE_FUNC(uint32_t, encode_bacnet_character_string_safe, uint8_t *apdu, uint32_t, uint8_t, char *, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_character_string, uint8_t *, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_character_string, uint8_t *, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_character_string, uint8_t *, uint8_t, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, decode_character_string, uint8_t *, uint32_t, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_character_string, uint8_t *, uint8_t, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_character_string_decode, uint8_t *, uint16_t, uint32_t, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_character_string_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_CHARACTER_STRING *); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_unsigned, uint8_t *, BACNET_UNSIGNED_INTEGER); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_unsigned, uint8_t *, uint8_t, BACNET_UNSIGNED_INTEGER); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_unsigned, uint8_t *, BACNET_UNSIGNED_INTEGER); +DECLARE_FAKE_VALUE_FUNC(int, decode_unsigned, uint8_t *, uint32_t, BACNET_UNSIGNED_INTEGER *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_unsigned, uint8_t *, uint8_t, BACNET_UNSIGNED_INTEGER *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_unsigned_decode, uint8_t *, uint16_t, uint32_t, BACNET_UNSIGNED_INTEGER *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_unsigned_application_decode, uint8_t *, uint16_t, BACNET_UNSIGNED_INTEGER *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_unsigned_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_UNSIGNED_INTEGER *); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_signed, uint8_t *, int32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_signed, uint8_t *, int32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_signed, uint8_t *, uint8_t, int32_t); +DECLARE_FAKE_VALUE_FUNC(int, decode_signed, uint8_t *, uint32_t, int32_t *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_signed, uint8_t *, uint8_t, int32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_signed_decode, uint8_t *, uint16_t, uint32_t, int32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_signed_context_decode, uint8_t *, uint16_t, uint8_t, int32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_signed_application_decode, uint8_t *, uint16_t, int32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_enumerated_decode, uint8_t *, uint16_t, uint32_t, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_enumerated_context_decode, uint8_t *, uint16_t, uint8_t, uint32_t *); +#endif +DECLARE_FAKE_VALUE_FUNC( + int, decode_enumerated, uint8_t *, uint32_t, uint32_t *); +#if 0 +DECLARE_FAKE_VALUE_FUNC(int, decode_context_enumerated, uint8_t *, uint8_t, uint32_t *); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_enumerated, uint8_t *, uint32_t); +#endif +DECLARE_FAKE_VALUE_FUNC( + int, encode_application_enumerated, uint8_t *, uint32_t); +#if 0 +DECLARE_FAKE_VALUE_FUNC(int, encode_context_enumerated, uint8_t *, uint8_t, uint32_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_time, uint8_t *, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_time, uint8_t *, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, decode_bacnet_time, uint8_t *, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, decode_bacnet_time_safe, uint8_t *, uint32_t, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_time, uint8_t *, uint8_t, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, decode_application_time, uint8_t *, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_bacnet_time, uint8_t *, uint8_t, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_time_decode, uint8_t *, uint16_t, uint32_t, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_time_context_decode, uint8_t *, uint16_t, uint8_t, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, bacnet_time_application_decode, uint8_t *, uint16_t, BACNET_TIME *); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_date, uint8_t *, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(int, encode_application_date, uint8_t *, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_date, uint8_t *, uint8_t, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(int, decode_date, uint8_t *, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(int, decode_date_safe, uint8_t *, uint32_t, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(int, decode_application_date, uint8_t *, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_date, uint8_t *, uint8_t, BACNET_DATE *); +DECLARE_FAKE_VALUE_FUNC(uint8_t, encode_max_segs_max_apdu, int, int); +DECLARE_FAKE_VALUE_FUNC(int, decode_max_segs, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, decode_max_apdu, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_simple_ack, uint8_t *, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, encode_bacnet_address, uint8_t *, BACNET_ADDRESS *); +DECLARE_FAKE_VALUE_FUNC(int, decode_bacnet_address, uint8_t *, BACNET_ADDRESS *); +DECLARE_FAKE_VALUE_FUNC(int, encode_context_bacnet_address, uint8_t *, uint8_t, BACNET_ADDRESS *); +DECLARE_FAKE_VALUE_FUNC(int, decode_context_bacnet_address, uint8_t *, uint8_t, BACNET_ADDRESS *); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* BACNET_STACK_TEST_UNIT_BACNET_BACERROR_FAKES_BACDCODE_H_ */ diff --git a/test/unit/bacnet/bacerror/src/main.c b/test/unit/bacnet/bacerror/src/main.c new file mode 100644 index 00000000..e6adf756 --- /dev/null +++ b/test/unit/bacnet/bacerror/src/main.c @@ -0,0 +1,1127 @@ +/* + * Copyright (c) 2023 Legrand North America, LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bacnet/bacerror.h" + +#include +#include +#include +#include + +#include "fakes/bacdcode.h" + +#if 0 +#include +#else + +/* The helpful macro below is from a Zephyr v3.3.0+ PR currently under review. + * Once it is in a Zephyr release, then it can be provided via #include + * rather than declared locally. + */ +#ifndef RETURN_HANDLED_CONTEXT +#define RETURN_HANDLED_CONTEXT( \ + FUNCNAME, CONTEXTTYPE, RESULTFIELD, CONTEXTPTRNAME, HANDLERBODY) \ + if (FUNCNAME##_fake.return_val_seq_len) { \ + CONTEXTTYPE *const contexts = CONTAINER_OF( \ + FUNCNAME##_fake.return_val_seq, CONTEXTTYPE, RESULTFIELD); \ + size_t const seq_idx = (FUNCNAME##_fake.return_val_seq_idx < \ + FUNCNAME##_fake.return_val_seq_len) \ + ? FUNCNAME##_fake.return_val_seq_idx++ \ + : FUNCNAME##_fake.return_val_seq_idx - 1; \ + CONTEXTTYPE *const CONTEXTPTRNAME = &contexts[seq_idx]; \ + HANDLERBODY; \ + } \ + return FUNCNAME##_fake.return_val +#endif /* RETURN_HANDLED_CONTEXT */ + +#endif + +#define RESET_HISTORY_AND_FAKES() \ + BACNET_STACK_TEST_BACNET_BACDCODE_FFF_FAKES_LIST(RESET_FAKE) \ + FFF_RESET_HISTORY() + +DEFINE_FFF_GLOBALS; + +/* + * Custom Fakes: + */ +struct encode_application_enumerated_custom_fake_context { + uint8_t *const apdu_expected; + uint32_t value_expected; + + /* Written to client by custom fake */ + const uint8_t *const encoded_enumerated; + int const encoded_enumerated_len; + + int result; +}; + +static int encode_application_enumerated_custom_fake( + uint8_t *apdu, uint32_t enumerated) +{ + RETURN_HANDLED_CONTEXT(encode_application_enumerated, + struct encode_application_enumerated_custom_fake_context, + result, /* return field name in _fake_context struct */ + context, /* Name of context ptr variable used below */ + { + if (context != NULL) { + if (context->result == 0) { + if (apdu != NULL) { + memcpy(apdu, context->encoded_enumerated, + context->encoded_enumerated_len); + } + } + + return context->result; + } + + return encode_application_enumerated_fake.return_val; + }); +} + +struct decode_tag_number_and_value_custom_fake_context { + uint8_t *const apdu_expected; + + /* Written to client by custom fake */ + uint8_t const tag_number; + uint32_t const value; + + int result; +}; + +static int decode_tag_number_and_value_custom_fake( + uint8_t *apdu, uint8_t *tag_number, uint32_t *value) +{ + RETURN_HANDLED_CONTEXT(decode_tag_number_and_value, + struct decode_tag_number_and_value_custom_fake_context, + result, /* return field name in _fake_context struct */ + context, /* Name of context ptr variable used below */ + { + if (context != NULL) { + if (context->result > 0) { + if (tag_number != NULL) + *tag_number = context->tag_number; + if (value != NULL) + *value = context->value; + } + + return context->result; + } + + return decode_tag_number_and_value_fake.return_val; + }); +} + +struct decode_enumerated_custom_fake_context { + uint8_t *const apdu_expected; + + /* Written to client by custom fake */ + uint32_t const value; + + int result; +}; + +static int decode_enumerated_custom_fake( + uint8_t *apdu, uint32_t len_value, uint32_t *value) +{ + RETURN_HANDLED_CONTEXT(decode_enumerated, + struct decode_enumerated_custom_fake_context, + result, /* return field name in _fake_context struct */ + context, /* Name of context ptr variable used below */ + { + if (context != NULL) { + if (context->result > 0) { + if (value != NULL) + *value = context->value; + } + + return context->result; + } + + return decode_enumerated_fake.return_val; + }); +} + +/* + * Tests: + */ + +static void test_bacerror_encode_apdu(void) +{ + uint8_t test_apdu[32] = { 0 }; + + struct test_case { + const char *description_oneliner; + + uint8_t apdu_prefill; + + uint8_t *apdu; + uint8_t invoke_id; + BACNET_CONFIRMED_SERVICE service; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + void **expected_call_history; + + /* Last FFF sequence entry is reused for excess calls. + * Have an extra entry that returns a distinct failure (-E2BIG) + * + * Expect one less call than _len, or 0 if sequence ptr is NULL + * + * Configure to return -E2BIG if excess calls. + */ + int encode_application_enumerated_custom_fake_contexts_len; + struct encode_application_enumerated_custom_fake_context + *encode_application_enumerated_custom_fake_contexts; + + int result_expected; + } const test_cases[] = { + { + .description_oneliner = "Call with NULL apdu", + + .apdu = NULL, + + .expected_call_history = + (void *[]) { + NULL, /* mark end of array */ + }, + + .result_expected = 0, /* zero total length of APDU */ + }, + { + .description_oneliner = + "Call with valid apdu, return negative apdu_len", + + .apdu_prefill = 0x55U, /* arbitrary */ + + .apdu = &test_apdu[0], + .invoke_id = 0xFEU, + .service = MAX_BACNET_CONFIRMED_SERVICE, + .error_class = ERROR_CLASS_PROPRIETARY_FIRST, + .error_code = ERROR_CODE_PROPRIETARY_LAST, + + .expected_call_history = + (void *[]) { + encode_application_enumerated, + encode_application_enumerated, NULL, /* mark end of array */ + }, + + .encode_application_enumerated_custom_fake_contexts_len = 3, + .encode_application_enumerated_custom_fake_contexts = + (struct encode_application_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[3], + .value_expected = ERROR_CLASS_PROPRIETARY_FIRST, + .result = -2, + }, + { + .apdu_expected = &test_apdu[1], + .value_expected = ERROR_CODE_PROPRIETARY_LAST, + .result = -1, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .result_expected = 0, /* zero total length of APDU */ + }, + { + .description_oneliner = + "Call with valid apdu, return positive apdu_len", + + .apdu_prefill = 0xABU, /* arbitrary */ + + .apdu = &test_apdu[0], + .invoke_id = 0xFEU, + .service = SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, + .error_class = ERROR_CLASS_PROPRIETARY_LAST, + .error_code = ERROR_CODE_PROPRIETARY_FIRST, + + .expected_call_history = + (void *[]) { + encode_application_enumerated, + encode_application_enumerated, NULL, /* mark end of array */ + }, + + .encode_application_enumerated_custom_fake_contexts_len = 3, + .encode_application_enumerated_custom_fake_contexts = + (struct encode_application_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[3], + .value_expected = ERROR_CLASS_PROPRIETARY_LAST, + .result = 4, + }, + { + .apdu_expected = &test_apdu[7], + .value_expected = ERROR_CODE_PROPRIETARY_FIRST, + .result = 5, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .result_expected = 3 + 4 + 5, /* total length of APDU */ + }, + }; + + for (int i = 0; i < ARRAY_SIZE(test_cases); ++i) { + const struct test_case *const tc = &test_cases[i]; + + printk("Checking test_cases[%i]: %s\n", i, + (tc->description_oneliner != NULL) ? tc->description_oneliner : ""); + + /* + * Set up pre-conditions + */ + RESET_HISTORY_AND_FAKES(); + + /* NOTE: Point to the return type field in the first returns struct. + * This custom_fake: + * - uses *_fake.return_val_seq and CONTAINER_OF() + * to determine the beginning of the array of structures. + * - uses *_fake.return_val_seq_id to index into + * the array of structures. + * This overloading is to allow the return_val_seq to + * also contain call-specific output parameters to be + * applied by the custom_fake. + */ + encode_application_enumerated_fake.return_val = + -E2BIG; /* for excessive calls */ + SET_RETURN_SEQ(encode_application_enumerated, + &tc->encode_application_enumerated_custom_fake_contexts[0].result, + tc->encode_application_enumerated_custom_fake_contexts_len); + encode_application_enumerated_fake.custom_fake = + encode_application_enumerated_custom_fake; + + memset(test_apdu, tc->apdu_prefill, sizeof(test_apdu)); + + /* + * Call code_under_test + */ + int result = bacerror_encode_apdu(tc->apdu, tc->invoke_id, tc->service, + tc->error_class, tc->error_code); + + /* + * Verify expected behavior of code_under_test: + * - call history, args per call + * - results + * - outputs + */ + if (tc->expected_call_history != NULL) { + for (int j = 0; j < fff.call_history_idx; ++j) { + zassert_equal( + fff.call_history[j], tc->expected_call_history[j], NULL); + } + zassert_is_null( + tc->expected_call_history[fff.call_history_idx], NULL); + } else { + zassert_equal(fff.call_history_idx, 0, NULL); + } + + const int encode_application_enumerated_fake_call_count_expected = + (tc->encode_application_enumerated_custom_fake_contexts == NULL) + ? 0 + : tc->encode_application_enumerated_custom_fake_contexts_len - 1; + + zassert_equal(encode_application_enumerated_fake.call_count, + encode_application_enumerated_fake_call_count_expected, NULL); + for (int j = 0; + j < encode_application_enumerated_fake_call_count_expected; ++j) { + zassert_equal(encode_application_enumerated_fake.arg0_history[j], + tc->encode_application_enumerated_custom_fake_contexts[j] + .apdu_expected, + NULL); + zassert_equal(encode_application_enumerated_fake.arg1_history[j], + tc->encode_application_enumerated_custom_fake_contexts[j] + .value_expected, + NULL); + } + + if (tc->apdu != NULL) { + zassert_equal(test_apdu[0], PDU_TYPE_ERROR, NULL); + zassert_equal(test_apdu[1], tc->invoke_id, NULL); + zassert_equal(test_apdu[2], tc->service, NULL); + + /* Verify remaining APDU bytes were not modified by code under test + */ + for (int j = 3; j < sizeof(test_apdu); ++j) { + zassert_equal(test_apdu[j], tc->apdu_prefill, NULL); + } + } + + zassert_equal(result, tc->result_expected, NULL); + } +} + +static void test_bacerror_decode_error_class_and_code(void) +{ +#if !BACNET_SVC_SERVER + uint8_t test_apdu[32] = { 0 }; + + uint8_t test_invoke_id = 0; /* bacerror_decode_service_request */ + BACNET_CONFIRMED_SERVICE test_service = + 0; /* bacerror_decode_service_request */ + BACNET_ERROR_CLASS test_error_class = 0; + BACNET_ERROR_CODE test_error_code = 0; + + struct test_case { + const char *description_oneliner; + + BACNET_ERROR_CLASS error_class_prefill; + BACNET_ERROR_CODE error_code_prefill; + + bool call_bacerror_decode_service_request; + + uint8_t *apdu; + unsigned apdu_len; + uint8_t *invoke_id; /* bacerror_decode_service_request */ + BACNET_CONFIRMED_SERVICE *service; /* bacerror_decode_service_request */ + BACNET_ERROR_CLASS *error_class; + BACNET_ERROR_CODE *error_code; + + void **expected_call_history; + + /* Last FFF sequence entry is reused for excess calls. + * Have an extra entry that returns a distinct failure (-E2BIG) + * + * Expect one less call than _len, or 0 if sequence ptr is NULL + * + * Configure to return -E2BIG if excess calls. + */ + int decode_tag_number_and_value_custom_fake_contexts_len; + struct decode_tag_number_and_value_custom_fake_context + *decode_tag_number_and_value_custom_fake_contexts; + + int decode_enumerated_custom_fake_contexts_len; + struct decode_enumerated_custom_fake_context + *decode_enumerated_custom_fake_contexts; + + uint8_t invoke_id_expected; /* bacerror_decode_service_request */ + BACNET_CONFIRMED_SERVICE + service_expected; /* bacerror_decode_service_request */ + BACNET_ERROR_CLASS error_class_expected; + BACNET_ERROR_CODE error_code_expected; + + int result_expected; /* zero is error, >0 is len consumed */ + } const test_cases[] = { + { + .description_oneliner = "Handle apdu ref of NULL", + + .apdu = NULL, + .apdu_len = 1, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + NULL, /* mark end of array */ + }, + + .result_expected = 0, /* zero total length of APDU consumed */ + }, + { + .description_oneliner = "Handle apdu_len of zero (0)", + + .apdu = + (uint8_t[]) { + 0, + }, + .apdu_len = 0, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + NULL, /* mark end of array */ + }, + + .result_expected = 0, /* zero total length of APDU consumed */ + }, + { + .description_oneliner = "Handle invalid first tag", + + .apdu = + (uint8_t[]) { + 0, + }, + .apdu_len = 1, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + decode_tag_number_and_value, NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 2, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[0], + .tag_number = + BACNET_APPLICATION_TAG_ENUMERATED - 1, /* err */ + .result = 1, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .result_expected = 0, /* zero total length of APDU consumed */ + }, + { + .description_oneliner = "Handle invalid second tag", + + .apdu = + (uint8_t[]) { + 0, + }, + .apdu_len = 1, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + decode_tag_number_and_value, decode_enumerated, + decode_tag_number_and_value, NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 3, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[0], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 42, /* arbitrary */ + .result = 1, + }, + { + .apdu_expected = &test_apdu[1 + 1], + + .tag_number = + BACNET_APPLICATION_TAG_ENUMERATED + 1, /* err */ + .value = 24, /* arbitrary */ + .result = 1, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 2, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[1], + + .value = 3, /* arbitrary */ + .result = 1, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .result_expected = 0, /* length of APDU consumed */ + }, + { + .description_oneliner = "Handle valid inputs", + + .apdu = + (uint8_t[]) { + 0, + }, + .apdu_len = 1, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + decode_tag_number_and_value, decode_enumerated, + decode_tag_number_and_value, decode_enumerated, + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 3, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[0], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 42, /* arbitrary */ + .result = 1, + }, + { + .apdu_expected = &test_apdu[1 + 2], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 24, /* arbitrary */ + .result = 3, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 3, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[1], + + .value = 3, /* error_class, arbitrary */ + .result = 2, + }, + { + .apdu_expected = &test_apdu[1 + 2 + 3], + + .value = 7, /* error_code, arbitrary */ + .result = 4, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 1 + 2 + 3 + 4, /* length of APDU consumed */ + }, + { + .description_oneliner = "decode_service_request, apdu ref NULL", + + .call_bacerror_decode_service_request = true, + + .apdu = NULL, + .apdu_len = 3, + .invoke_id = &test_invoke_id, + .service = &test_service, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 1, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 1, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .invoke_id_expected = 0xFEU, /* apdu[0] */ + .service_expected = 0xFDU, /* apdu[1] */ + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 0, /* length of APDU consumed */ + }, + { + .description_oneliner = + "decode_service_request invalid inputs, apdu_len = 0", + + .call_bacerror_decode_service_request = true, + + .apdu = + (uint8_t[]) { + 0xBEU, /* apdu[0]: invoke_id */ + 0xBDU, /* apdu[1]: service */ + 0, + }, + .apdu_len = 0, + .invoke_id = &test_invoke_id, + .service = &test_service, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 1, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 1, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .invoke_id_expected = 0xFEU, /* apdu[0] */ + .service_expected = 0xFDU, /* apdu[1] */ + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 0, /* length of APDU consumed */ + }, + { + .description_oneliner = + "decode_service_request invalid inputs, apdu_len = 2", + + .call_bacerror_decode_service_request = true, + + .apdu = + (uint8_t[]) { + 0xBEU, /* apdu[0]: invoke_id */ + 0xBDU, /* apdu[1]: service */ + 0, + }, + .apdu_len = 2, + .invoke_id = &test_invoke_id, + .service = &test_service, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 1, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 1, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .invoke_id_expected = 0xFEU, /* apdu[0] */ + .service_expected = 0xFDU, /* apdu[1] */ + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 0, /* length of APDU consumed */ + }, + { + .description_oneliner = "decode_service_request valid inputs", + + .call_bacerror_decode_service_request = true, + + .apdu = + (uint8_t[]) { + 0xFEU, /* apdu[0]: invoke_id */ + 0xFDU, /* apdu[1]: service */ + 0, + }, + .apdu_len = 3, + .invoke_id = &test_invoke_id, + .service = &test_service, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + decode_tag_number_and_value, decode_enumerated, + decode_tag_number_and_value, decode_enumerated, + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 3, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[2 + 0], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 142, /* arbitrary */ + .result = 1, + }, + { + .apdu_expected = &test_apdu[2 + 1 + 2], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 124, /* arbitrary */ + .result = 3, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 3, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[2 + 1], + + .value = 3, /* error_class, arbitrary */ + .result = 2, + }, + { + .apdu_expected = &test_apdu[2 + 1 + 2 + 3], + + .value = 7, /* error_code, arbitrary */ + .result = 4, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .invoke_id_expected = 0xFEU, /* apdu[0] */ + .service_expected = 0xFDU, /* apdu[1] */ + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 2 + 1 + 2 + 3 + 4, /* length of APDU consumed */ + }, + { + .description_oneliner = + "decode_service_request valid inputs, invoke_id is NULL", + + .call_bacerror_decode_service_request = true, + + .apdu = + (uint8_t[]) { + 0xFEU, /* apdu[0]: invoke_id */ + 0xFDU, /* apdu[1]: service */ + 0, + }, + .apdu_len = 3, + .invoke_id = NULL, + .service = &test_service, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + decode_tag_number_and_value, decode_enumerated, + decode_tag_number_and_value, decode_enumerated, + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 3, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[2 + 0], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 142, /* arbitrary */ + .result = 1, + }, + { + .apdu_expected = &test_apdu[2 + 1 + 2], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 124, /* arbitrary */ + .result = 3, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 3, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[2 + 1], + + .value = 3, /* error_class, arbitrary */ + .result = 2, + }, + { + .apdu_expected = &test_apdu[2 + 1 + 2 + 3], + + .value = 7, /* error_code, arbitrary */ + .result = 4, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .invoke_id_expected = 0xFEU, /* apdu[0] */ + .service_expected = 0xFDU, /* apdu[1] */ + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 2 + 1 + 2 + 3 + 4, /* length of APDU consumed */ + }, + { + .description_oneliner = + "decode_service_request valid inputs, service is NULL", + + .call_bacerror_decode_service_request = true, + + .apdu = + (uint8_t[]) { + 0xFCU, /* apdu[0]: invoke_id */ + 0xFBU, /* apdu[1]: service */ + 0, + }, + .apdu_len = 3, + .invoke_id = &test_invoke_id, + .service = NULL, + .error_class = &test_error_class, + .error_class_prefill = 1U, + .error_code = &test_error_code, + .error_code_prefill = 0xFFFFU, + + .expected_call_history = + (void *[]) { + decode_tag_number_and_value, decode_enumerated, + decode_tag_number_and_value, decode_enumerated, + NULL, /* mark end of array */ + }, + + .decode_tag_number_and_value_custom_fake_contexts_len = 3, + .decode_tag_number_and_value_custom_fake_contexts = + (struct decode_tag_number_and_value_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[2 + 0], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 142, /* arbitrary */ + .result = 1, + }, + { + .apdu_expected = &test_apdu[2 + 1 + 2], + + .tag_number = BACNET_APPLICATION_TAG_ENUMERATED, + .value = 124, /* arbitrary */ + .result = 3, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .decode_enumerated_custom_fake_contexts_len = 3, + .decode_enumerated_custom_fake_contexts = + (struct decode_enumerated_custom_fake_context[]) { + { + .apdu_expected = &test_apdu[2 + 1], + + .value = 3, /* error_class, arbitrary */ + .result = 2, + }, + { + .apdu_expected = &test_apdu[2 + 1 + 2 + 3], + + .value = 7, /* error_code, arbitrary */ + .result = 4, + }, + { + .result = -E2BIG, /* for excessive calls */ + }, + }, + + .invoke_id_expected = 0xFCU, /* apdu[0] */ + .service_expected = 0xFBU, /* apdu[1] */ + .error_class_expected = 3, + .error_code_expected = 7, + + .result_expected = 2 + 1 + 2 + 3 + 4, /* length of APDU consumed */ + }, + }; + + for (int i = 0; i < ARRAY_SIZE(test_cases); ++i) { + const struct test_case *const tc = &test_cases[i]; + + printk("Checking test_cases[%i]: %s\n", i, + (tc->description_oneliner != NULL) ? tc->description_oneliner : ""); + + /* + * Set up pre-conditions + */ + RESET_HISTORY_AND_FAKES(); + + /* NOTE: Point to the return type field in the first returns struct. + * This custom_fake: + * - uses *_fake.return_val_seq and CONTAINER_OF() + * to determine the beginning of the array of structures. + * - uses *_fake.return_val_seq_id to index into + * the array of structures. + * This overloading is to allow the return_val_seq to + * also contain call-specific output parameters to be + * applied by the custom_fake. + */ + decode_tag_number_and_value_fake.return_val = + -E2BIG; /* for excessive calls */ + SET_RETURN_SEQ(decode_tag_number_and_value, + &tc->decode_tag_number_and_value_custom_fake_contexts[0].result, + tc->decode_tag_number_and_value_custom_fake_contexts_len); + decode_tag_number_and_value_fake.custom_fake = + decode_tag_number_and_value_custom_fake; + + decode_enumerated_fake.return_val = -E2BIG; /* for excessive calls */ + SET_RETURN_SEQ(decode_enumerated, + &tc->decode_enumerated_custom_fake_contexts[0].result, + tc->decode_enumerated_custom_fake_contexts_len); + decode_enumerated_fake.custom_fake = decode_enumerated_custom_fake; + + memset(test_apdu, 0, sizeof(test_apdu)); + if (tc->apdu != NULL) { + memcpy(test_apdu, tc->apdu, MIN(tc->apdu_len, sizeof(test_apdu))); + } + test_invoke_id = + ~tc->invoke_id_expected; /* bacerror_decode_service_request */ + test_service = + ~tc->service_expected; /* bacerror_decode_service_request */ + test_error_class = tc->error_class_prefill; + test_error_code = tc->error_code_prefill; + + /* + * Call code_under_test + */ + int result = -1; + if (tc->call_bacerror_decode_service_request) { + result = bacerror_decode_service_request( + ((tc->apdu != NULL) ? test_apdu : NULL), tc->apdu_len, + ((tc->invoke_id) ? &test_invoke_id : NULL), + ((tc->service) ? &test_service : NULL), tc->error_class, + tc->error_code); + } else { + result = bacerror_decode_error_class_and_code( + ((tc->apdu != NULL) ? test_apdu : NULL), tc->apdu_len, + tc->error_class, tc->error_code); + } + + /* + * Verify expected behavior of code_under_test: + * - call history, args per call + * - results + * - outputs + */ + if (tc->expected_call_history != NULL) { + for (int j = 0; j < fff.call_history_idx; ++j) { + zassert_equal( + fff.call_history[j], tc->expected_call_history[j], NULL); + } + zassert_is_null( + tc->expected_call_history[fff.call_history_idx], NULL); + } else { + zassert_equal(fff.call_history_idx, 0, NULL); + } + + const int decode_tag_number_and_value_fake_call_count_expected = + (tc->decode_tag_number_and_value_custom_fake_contexts == NULL) + ? 0 + : tc->decode_tag_number_and_value_custom_fake_contexts_len - 1; + + zassert_equal(decode_tag_number_and_value_fake.call_count, + decode_tag_number_and_value_fake_call_count_expected, NULL); + for (int j = 0; + j < decode_tag_number_and_value_fake_call_count_expected; ++j) { + zassert_equal(decode_tag_number_and_value_fake.arg0_history[j], + tc->decode_tag_number_and_value_custom_fake_contexts[j] + .apdu_expected, + NULL); + zassert_not_null( + decode_tag_number_and_value_fake.arg1_history[j], NULL); + zassert_not_null( + decode_tag_number_and_value_fake.arg2_history[j], NULL); + } + + const int decode_enumerated_fake_call_count_expected = + (tc->decode_enumerated_custom_fake_contexts == NULL) + ? 0 + : tc->decode_enumerated_custom_fake_contexts_len - 1; + + zassert_equal(decode_enumerated_fake.call_count, + decode_enumerated_fake_call_count_expected, NULL); + for (int j = 0; j < decode_enumerated_fake_call_count_expected; ++j) { + zassert_equal(decode_enumerated_fake.arg0_history[j], + tc->decode_enumerated_custom_fake_contexts[j].apdu_expected, + NULL); + zassert_equal(decode_enumerated_fake.arg1_history[j], + tc->decode_tag_number_and_value_custom_fake_contexts[j].value, + NULL); + zassert_not_null(decode_enumerated_fake.arg2_history[j], NULL); + } + + if ((tc->apdu != NULL) && tc->result_expected > 0) { + if (tc->call_bacerror_decode_service_request) { + if (tc->invoke_id != NULL) { + zassert_equal(test_invoke_id, tc->invoke_id_expected, NULL); + } + + if (tc->service != NULL) { + zassert_equal(test_service, tc->service_expected, NULL); + } + } + + if (tc->error_class != NULL) { + zassert_equal(test_error_class, tc->error_class_expected, NULL); + } + if (tc->error_code != NULL) { + zassert_equal(test_error_code, tc->error_code_expected, NULL); + } + } else { +#if 0 /* NOTE: outputs are only valid if result > 0 */ + zassert_equal(test_error_class, tc->error_class_prefill, NULL); + zassert_equal(test_error_code, tc->error_code_prefill, NULL); +#endif + } + + zassert_equal(result, tc->result_expected, NULL); + } +#else + ztest_test_skip(); +#endif +} + +void test_main(void) +{ + ztest_test_suite(bacnet_bacerror, + ztest_unit_test(test_bacerror_encode_apdu), + ztest_unit_test(test_bacerror_decode_error_class_and_code)); + ztest_run_test_suite(bacnet_bacerror); +} diff --git a/test/ztest/include/zephyr/sys/util.h b/test/ztest/include/zephyr/sys/util.h new file mode 100644 index 00000000..1a705e14 --- /dev/null +++ b/test/ztest/include/zephyr/sys/util.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2011-2014, Wind River Systems, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Content copied out of from Zephyr v3.2.0. + * Copyright and licensing retained. + */ + +#ifndef BACNETSTACK_TEST_ZTEST_INCLUDE_ZEPHYR_SYS_UTIL_H_ +#define BACNETSTACK_TEST_ZTEST_INCLUDE_ZEPHYR_SYS_UTIL_H_ + +/** @brief 0 if @p cond is true-ish; causes a compile error otherwise. */ +#define ZERO_OR_COMPILE_ERROR(cond) ((int) sizeof(char[1 - 2 * !(cond)]) - 1) + +#if defined(__cplusplus) + +/* The built-in function used below for type checking in C is not + * supported by GNU C++. + */ +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) + +#else /* __cplusplus */ + +/** + * @brief Zero if @p array has an array type, a compile error otherwise + * + * This macro is available only from C, not C++. + */ +#define IS_ARRAY(array) \ + ZERO_OR_COMPILE_ERROR( \ + !__builtin_types_compatible_p(__typeof__(array), \ + __typeof__(&(array)[0]))) + +/** + * @brief Number of elements in the given @p array + * + * In C++, due to language limitations, this will accept as @p array + * any type that implements operator[]. The results may not be + * particularly meaningful in this case. + * + * In C, passing a pointer as @p array causes a compile error. + */ +#define ARRAY_SIZE(array) \ + ((size_t) (IS_ARRAY(array) + (sizeof(array) / sizeof((array)[0])))) + +#endif /* __cplusplus */ + +/** + * @brief Get a pointer to a structure containing the element + * + * Example: + * + * struct foo { + * int bar; + * }; + * + * struct foo my_foo; + * int *ptr = &my_foo.bar; + * + * struct foo *container = CONTAINER_OF(ptr, struct foo, bar); + * + * Above, @p container points at @p my_foo. + * + * @param ptr pointer to a structure element + * @param type name of the type that @p ptr is an element of + * @param field the name of the field within the struct @p ptr points to + * @return a pointer to the structure that contains @p ptr + */ +#define CONTAINER_OF(ptr, type, field) \ + ((type *)(((char *)(ptr)) - offsetof(type, field))) + + +#ifndef MAX +/** + * @brief Obtain the maximum of two values. + * + * @note Arguments are evaluated twice. Use Z_MAX for a GCC-only, single + * evaluation version + * + * @param a First value. + * @param b Second value. + * + * @returns Maximum value of @p a and @p b. + */ +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +/** + * @brief Obtain the minimum of two values. + * + * @note Arguments are evaluated twice. Use Z_MIN for a GCC-only, single + * evaluation version + * + * @param a First value. + * @param b Second value. + * + * @returns Minimum value of @p a and @p b. + */ +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#endif /* BACNETSTACK_TEST_ZTEST_INCLUDE_ZEPHYR_SYS_UTIL_H_ */ diff --git a/zephyr/tests/unit/bacnet/bacerror/CMakeLists.txt b/zephyr/tests/unit/bacnet/bacerror/CMakeLists.txt new file mode 100644 index 00000000..c177b82d --- /dev/null +++ b/zephyr/tests/unit/bacnet/bacerror/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (c) 2022 Legrand North America, LLC. +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +if(NOT ZEPHYR_CURRENT_MODULE_DIR) + string(REGEX REPLACE "/zephyr/tests/[a-zA-Z_/-]*$" "" + ZEPHYR_CURRENT_MODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +if(BOARD STREQUAL unit_testing) + find_package(Zephyr COMPONENTS unittest REQUIRED HINTS $ENV{ZEPHYR_BASE}) + set(target testbinary) +else() + find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + set(target app) +endif() + +project(bacnet_bacerror) +target_sources(${target} PRIVATE + ${ZEPHYR_CURRENT_MODULE_DIR}/src/bacnet/bacerror.c + ${ZEPHYR_CURRENT_MODULE_DIR}/test/unit/bacnet/bacerror/src/main.c + ${ZEPHYR_CURRENT_MODULE_DIR}/test/unit/bacnet/bacerror/src/fakes/bacdcode.c + ${CMAKE_CURRENT_SOURCE_DIR}/main.c +) + + +# NOTE for Zephyr >= v3.2.0: +# - Zephyr unittest builds for target 'testbinary' instead of 'app'. +# - Zephyr unittest does not generate ZEPHYR__MODULE_DIR. +# So we have to use relative paths to get to the source. + +target_include_directories(${target} PRIVATE + ${ZEPHYR_CURRENT_MODULE_DIR}/test/unit/bacnet/bacerror/src + ${ZEPHYR_CURRENT_MODULE_DIR}/src +) diff --git a/zephyr/tests/unit/bacnet/bacerror/main.c b/zephyr/tests/unit/bacnet/bacerror/main.c new file mode 100644 index 00000000..a8cf976f --- /dev/null +++ b/zephyr/tests/unit/bacnet/bacerror/main.c @@ -0,0 +1 @@ +/* This file is intentionally empty */ diff --git a/zephyr/tests/unit/bacnet/bacerror/prj.conf b/zephyr/tests/unit/bacnet/bacerror/prj.conf new file mode 100644 index 00000000..d41d3a24 --- /dev/null +++ b/zephyr/tests/unit/bacnet/bacerror/prj.conf @@ -0,0 +1 @@ +# This file is intentionally empty diff --git a/zephyr/tests/unit/bacnet/bacerror/testcase.yaml b/zephyr/tests/unit/bacnet/bacerror/testcase.yaml new file mode 100644 index 00000000..c70f7ff7 --- /dev/null +++ b/zephyr/tests/unit/bacnet/bacerror/testcase.yaml @@ -0,0 +1,3 @@ +tests: + bacnet.bacerror.unit: + type: unit