Refactor/cov subscriptions encoding decoding test (#1296)

* Add BACnetRecipientProcess type encoding and decoding functions with associated tests

* Refactor COV subscription encoding and decoding functions to reduce code size and reuse existing unit tested functions.

* Refactor COV subscription handling to simplify error checking
This commit is contained in:
Steve Karg
2026-04-20 10:53:55 -05:00
committed by GitHub
parent 52561f2336
commit b9149dd639
15 changed files with 1151 additions and 171 deletions
+4 -2
View File
@@ -10,14 +10,16 @@
void bip_get_my_address(BACNET_ADDRESS *my_address)
{
(void)my_address;
if (my_address) {
my_address->mac_len = 0;
}
}
int bip_send_pdu(
BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
uint8_t *pdu,
unsigned pdu_len)
uint16_t pdu_len)
{
(void)dest;
(void)npdu_data;
@@ -46,3 +46,57 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
(void)rpdata;
return 0;
}
bool Device_Valid_Object_Id(
BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
{
(void)object_type;
(void)object_instance;
return true;
}
bool Device_Value_List_Supported(BACNET_OBJECT_TYPE object_type)
{
(void)object_type;
return true;
}
bool Device_COV(BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
{
(void)object_type;
(void)object_instance;
return false;
}
void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
{
(void)object_type;
(void)object_instance;
}
bool Device_Encode_Value_List(
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_VALUE *value_list)
{
(void)object_type;
(void)object_instance;
(void)value_list;
return true;
}
uint16_t Routed_Device_Object_Index(void)
{
return 0;
}
bool Set_Routed_Device_Object_Index(uint16_t index)
{
(void)index;
return true;
}
uint16_t Get_Num_Managed_Devices(void)
{
return 1;
}
@@ -0,0 +1,83 @@
# 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-Z0-9_/-]*$"
"/src"
SRC_DIR
${CMAKE_CURRENT_SOURCE_DIR})
string(REGEX REPLACE
"/test/bacnet/[a-zA-Z0-9_/-]*$"
"/test"
TST_DIR
${CMAKE_CURRENT_SOURCE_DIR})
set(ZTST_DIR "${TST_DIR}/ztest/src")
add_compile_definitions(
BACNET_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/basic/service/h_cov.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
${SRC_DIR}/bacnet/bacaction.c
${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/bacint.c
${SRC_DIR}/bacnet/baclog.c
${SRC_DIR}/bacnet/bacreal.c
${SRC_DIR}/bacnet/bacstr.c
${SRC_DIR}/bacnet/bactext.c
${SRC_DIR}/bacnet/bacerror.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/debug.c
${SRC_DIR}/bacnet/basic/tsm/tsm.c
${SRC_DIR}/bacnet/bactimevalue.c
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/cov.c
${SRC_DIR}/bacnet/dailyschedule.c
${SRC_DIR}/bacnet/datetime.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/shed_level.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/abort.c
${SRC_DIR}/bacnet/dcc.c
${SRC_DIR}/bacnet/npdu.c
${SRC_DIR}/bacnet/reject.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/apdu_mock.c
${TST_DIR}/bacnet/basic/object/test/bip_mock.c
${TST_DIR}/bacnet/basic/object/test/device_mock.c
${ZTST_DIR}/ztest_mock.c
${ZTST_DIR}/ztest.c
)
+348
View File
@@ -0,0 +1,348 @@
/**
* @file
* @brief test BACnet COV service handler
* @date 2025
* @copyright SPDX-License-Identifier: MIT
*/
#include <zephyr/ztest.h>
#include <bacnet/bacdef.h>
#include <bacnet/cov.h>
#include <bacnet/basic/services.h>
/**
* @addtogroup bacnet_tests
* @{
*/
/* Mock functions for Device_ */
#include <bacnet/basic/object/device.h>
/* Mocks have been moved to bacnet/basic/object/test/ */
/**
* @brief Test test_h_cov_init_encode
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_init_encode)
#else
static void test_h_cov_init_encode(void)
#endif
{
uint8_t apdu[480] = { 0 };
int apdu_size = sizeof(apdu);
int len;
uint8_t service_request[128] = { 0 };
BACNET_ADDRESS src = { 0 };
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 };
handler_cov_init();
len = handler_cov_encode_subscriptions(apdu, apdu_size);
/* Should be zero because we initialized the handler */
zassert_equal(len, 0, NULL);
/* Create a valid subscription */
cov_data.subscriberProcessIdentifier = 1;
cov_data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
cov_data.monitoredObjectIdentifier.instance = 0;
cov_data.cancellationRequest = false;
cov_data.issueConfirmedNotifications = false;
cov_data.lifetime = 0;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
zassert_not_equal(len, 0, NULL);
zassert_not_equal(len, BACNET_STATUS_ABORT, NULL);
handler_cov_subscribe(service_request, len, &src, &service_data);
/* Normal encode */
len = handler_cov_encode_subscriptions(apdu, apdu_size);
zassert_not_equal(len, 0, NULL);
zassert_not_equal(len, BACNET_STATUS_ABORT, NULL);
/* edge case: exact size buffer encode */
len = handler_cov_encode_subscriptions(apdu, len);
zassert_equal(len, len, NULL);
/* edge case: buffer too small */
len = handler_cov_encode_subscriptions(apdu, len - 1);
zassert_equal(len, BACNET_STATUS_ABORT, NULL);
}
/**
* @brief Test test_h_cov_timer
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_timer)
#else
static void test_h_cov_timer(void)
#endif
{
handler_cov_timer_seconds(1);
zassert_true(true, NULL);
}
/**
* @brief Test test_h_cov_fsm
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_fsm)
#else
static void test_h_cov_fsm(void)
#endif
{
bool idle;
uint8_t service_request[128] = { 0 };
BACNET_ADDRESS src = { 0 };
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 };
int len;
handler_cov_init();
idle = handler_cov_fsm();
zassert_true(idle, NULL); /* No subscriptions, quickly back to idle */
/* Subscribe Unconfirmed */
cov_data.subscriberProcessIdentifier = 1;
cov_data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
cov_data.monitoredObjectIdentifier.instance = 0;
cov_data.cancellationRequest = false;
cov_data.issueConfirmedNotifications = false;
cov_data.lifetime = 0;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
/* IDLE -> MARK */
idle = handler_cov_fsm();
zassert_false(idle, NULL);
/* MARK -> CLEAR */
idle = handler_cov_fsm();
zassert_false(idle, NULL);
/* CLEAR -> FREE */
idle = handler_cov_fsm();
zassert_false(idle, NULL);
/* FREE -> SEND */
idle = handler_cov_fsm();
zassert_false(idle, NULL);
/* SEND -> IDLE */
idle = handler_cov_fsm();
zassert_true(idle, NULL);
/* Subscribe Confirmed */
cov_data.subscriberProcessIdentifier = 2;
cov_data.issueConfirmedNotifications = true;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
/* Run FSM again */
handler_cov_fsm(); /* IDLE -> MARK */
handler_cov_fsm(); /* MARK -> CLEAR */
handler_cov_fsm(); /* CLEAR -> FREE */
handler_cov_fsm(); /* FREE -> SEND */
handler_cov_fsm(); /* SEND -> IDLE */
/* Trigger Confirmed free branch by expiring */
cov_data.lifetime = 10;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
handler_cov_fsm(); /* IDLE -> MARK */
handler_cov_fsm(); /* MARK -> CLEAR */
handler_cov_fsm(); /* CLEAR -> FREE */
handler_cov_fsm(); /* FREE -> SEND */
handler_cov_fsm(); /* SEND -> IDLE */
/* Expire */
handler_cov_timer_seconds(15);
handler_cov_task();
zassert_true(true, NULL);
}
/**
* @brief Test test_h_cov_subscribe_error_cases
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_subscribe_error_cases)
#else
static void test_h_cov_subscribe_error_cases(void)
#endif
{
uint8_t service_request[128] = { 0 };
BACNET_ADDRESS src = { 0 };
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 };
int len;
handler_cov_init();
/* test segment abort */
service_data.segmented_message = true;
handler_cov_subscribe(service_request, 10, &src, &service_data);
zassert_true(true, NULL);
/* test missing required parameter */
service_data.segmented_message = false;
handler_cov_subscribe(service_request, 0, &src, &service_data);
zassert_true(true, NULL);
/* test bad request decode */
handler_cov_subscribe(service_request, 1, &src, &service_data);
zassert_true(true, NULL);
/* test valid decode but object valid (by mock) */
cov_data.subscriberProcessIdentifier = 1;
cov_data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
cov_data.monitoredObjectIdentifier.instance = 12345;
cov_data.cancellationRequest = false;
cov_data.issueConfirmedNotifications = false;
cov_data.lifetime = 0;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
zassert_true(true, NULL);
/* test cancellation of non-existing */
cov_data.cancellationRequest = true;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
zassert_true(true, NULL);
}
/**
* @brief Test test_h_cov_subscribe_out_of_space
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_subscribe_out_of_space)
#else
static void test_h_cov_subscribe_out_of_space(void)
#endif
{
uint8_t service_request[128] = { 0 };
BACNET_ADDRESS src = { 0 };
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 };
int len;
int i;
handler_cov_init();
cov_data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
cov_data.monitoredObjectIdentifier.instance = 0;
cov_data.cancellationRequest = false;
cov_data.issueConfirmedNotifications = false;
cov_data.lifetime = 0;
for (i = 0; i < 130; i++) {
cov_data.subscriberProcessIdentifier = i;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
}
zassert_true(true, NULL);
}
/**
* @brief Test test_h_cov_timer_expiration
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_timer_expiration)
#else
static void test_h_cov_timer_expiration(void)
#endif
{
uint8_t service_request[128] = { 0 };
BACNET_ADDRESS src = { 0 };
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 };
int len;
handler_cov_init();
/* Subscribe with lifetime */
cov_data.subscriberProcessIdentifier = 1;
cov_data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
cov_data.monitoredObjectIdentifier.instance = 0;
cov_data.cancellationRequest = false;
cov_data.issueConfirmedNotifications = true;
cov_data.lifetime = 10;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
/* Check timer decreasing */
handler_cov_timer_seconds(5);
handler_cov_timer_seconds(10); /* Should expire here */
zassert_true(true, NULL);
}
/**
* @brief Test test_h_cov_address_management
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(h_cov_tests, test_h_cov_address_management)
#else
static void test_h_cov_address_management(void)
#endif
{
uint8_t service_request[128] = { 0 };
BACNET_ADDRESS src = { 0 };
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 };
int len;
int i;
handler_cov_init();
cov_data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
cov_data.monitoredObjectIdentifier.instance = 0;
cov_data.cancellationRequest = false;
cov_data.issueConfirmedNotifications = false;
cov_data.lifetime = 0;
/* Add subscriptions from 18 different sources */
for (i = 0; i < 18; i++) {
src.mac_len = 1;
src.mac[0] = i + 1;
cov_data.subscriberProcessIdentifier = i;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
}
/* Cancel one, to free an address */
src.mac_len = 1;
src.mac[0] = 1;
cov_data.subscriberProcessIdentifier = 0;
cov_data.cancellationRequest = true;
len = cov_subscribe_service_request_encode(
service_request, sizeof(service_request), &cov_data);
handler_cov_subscribe(service_request, len, &src, &service_data);
zassert_true(true, NULL);
}
/**
* @}
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST_SUITE(h_cov_tests, NULL, NULL, NULL, NULL, NULL);
#else
void test_main(void)
{
ztest_test_suite(
h_cov_tests, ztest_unit_test(test_h_cov_init_encode),
ztest_unit_test(test_h_cov_timer), ztest_unit_test(test_h_cov_fsm),
ztest_unit_test(test_h_cov_subscribe_error_cases),
ztest_unit_test(test_h_cov_subscribe_out_of_space),
ztest_unit_test(test_h_cov_timer_expiration),
ztest_unit_test(test_h_cov_address_management));
ztest_run_test_suite(h_cov_tests);
}
#endif