Support routed virtual device backup/restore reinitialization (#1320)

* Support routed virtual device backup restore reinitialization

Allow routed virtual devices to accept ReinitializeDevice so Backup and Restore
states can be handled per device. Gateway behavior remains unchanged, and
DeviceCommunicationControl remains disabled for virtual devices.

Virtual-device COLDSTART, WARMSTART, and ACTIVATE_CHANGES requests now return
OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED instead of being acknowledged.

* Cover routed device backup restore test variants

Add routed-device tests for ReinitializeDevice service approval, DCC blocking,
virtual-device unsupported reinitialize states, per-device Backup/Restore state,
and backup timeout countdown behavior.

Build device tests in four compile-time variants so BAC_ROUTING and
BACNET_BACKUP_RESTORE are covered in both enabled and disabled combinations.

* Keep routed device variant tests in CTest

Register each optional device test build as its own CTest build/run pair so BAC_ROUTING and BACNET_BACKUP_RESTORE variants keep the existing fixture-based build-before-run behavior without changing the top-level test CMake file.

Constraint: Top-level test/CMakeLists.txt remains unchanged to preserve the existing test registration convention
Confidence: high
Scope-risk: narrow
Tested: cmake -S test -B /tmp/bacnet-stack-test-cmake-pr1320
Tested: ctest --test-dir /tmp/bacnet-stack-test-cmake-pr1320 -R '^test_device(_.*)?$' --output-on-failure -j4
Not-tested: Full repository CTest suite

* Ensure Backup/Restore has real configuration-file storage

Centralize reinitialize state storage so routed devices and the default device use the same access path. Enforce BACNET_BACKUP_FILE_COUNT as a positive Backup/Restore invariant, while preserving BACNET_BACKUP_RESTORE guards for no-backup builds.

Constraint: BACNET_BACKUP_RESTORE performs backup and restore through Configuration_Files[0].
Rejected: Preserve BACNET_BACKUP_FILE_COUNT=0 support | it leaves only state/property handling without a usable backup file.
Confidence: high
Scope-risk: moderate
Directive: Guard no-backup builds with BACNET_BACKUP_RESTORE, not BACNET_BACKUP_FILE_COUNT.
Tested: ctest --test-dir test/build -R '^(build_device|test_device|build_device_backup_restore|test_device_backup_restore|build_device_bac_routing|test_device_bac_routing|build_device_bac_routing_backup_restore|test_device_bac_routing_backup_restore)$' --output-on-failure
Tested: cmake --build build --target server gateway gateway2 bacbasic
Not-tested: Runtime BACnet client backup/restore exchange

---------

Signed-off-by: kimhyeongjun <hjun1.kim@samsung.com>
Co-authored-by: OmX <omx@oh-my-codex.dev>
This commit is contained in:
Hyeongjun Kim
2026-04-30 23:05:14 +09:00
committed by GitHub
parent 1f75040b5c
commit b9dc173c57
6 changed files with 651 additions and 101 deletions
+63 -1
View File
@@ -30,7 +30,7 @@ include_directories(
${TST_DIR}/ztest/include
)
add_executable(${PROJECT_NAME}
set(TEST_DEVICE_SOURCES
# File(s) under test
${SRC_DIR}/bacnet/basic/object/device.c
# Support files and stubs (pathname alphabetical)
@@ -140,3 +140,65 @@ add_executable(${PROJECT_NAME}
${ZTST_DIR}/ztest_mock.c
${ZTST_DIR}/ztest.c
)
function(configure_device_test_target
target_name routing_enabled backup_restore_enabled)
if(routing_enabled)
target_sources(${target_name} PRIVATE
${SRC_DIR}/bacnet/basic/object/gateway/gw_device.c
)
target_compile_definitions(${target_name} PRIVATE
BAC_ROUTING
)
endif()
if(backup_restore_enabled)
target_compile_definitions(${target_name} PRIVATE
BACNET_BACKUP_RESTORE
)
endif()
endfunction()
# Register a CTest build/run pair for one device test binary.
function(
add_device_ctest
target_name
test_name)
add_test(NAME build_${test_name} COMMAND ${CMAKE_COMMAND} --build "${CMAKE_BINARY_DIR}" --config $<CONFIG> --target
${target_name})
add_test(NAME test_${test_name} COMMAND $<TARGET_FILE:${target_name}>)
set_tests_properties(test_${test_name} PROPERTIES FIXTURES_REQUIRED fixture_${test_name})
set_tests_properties(build_${test_name} PROPERTIES FIXTURES_SETUP fixture_${test_name})
endfunction()
add_executable(${PROJECT_NAME}
${TEST_DEVICE_SOURCES}
)
configure_device_test_target(${PROJECT_NAME} OFF OFF)
# Backup/Restore-only test binary.
add_executable(${PROJECT_NAME}_backup_restore ${TEST_DEVICE_SOURCES})
configure_device_test_target(
${PROJECT_NAME}_backup_restore
OFF
ON)
# BAC_ROUTING-only test binary.
add_executable(${PROJECT_NAME}_bac_routing ${TEST_DEVICE_SOURCES})
configure_device_test_target(
${PROJECT_NAME}_bac_routing
ON
OFF)
# BAC_ROUTING with Backup/Restore test binary.
add_executable(${PROJECT_NAME}_bac_routing_backup_restore ${TEST_DEVICE_SOURCES})
configure_device_test_target(
${PROJECT_NAME}_bac_routing_backup_restore
ON
ON)
# CTest pair for the Backup/Restore-only binary.
add_device_ctest(${PROJECT_NAME}_backup_restore ${basename}_backup_restore)
# CTest pair for the BAC_ROUTING-only binary.
add_device_ctest(${PROJECT_NAME}_bac_routing ${basename}_bac_routing)
# CTest pair for the BAC_ROUTING with Backup/Restore binary.
add_device_ctest(${PROJECT_NAME}_bac_routing_backup_restore ${basename}_bac_routing_backup_restore)
+243
View File
@@ -91,6 +91,49 @@ static bool Write_Property_Proprietary(BACNET_WRITE_PROPERTY_DATA *data)
return status;
}
#if defined(BAC_ROUTING)
/**
* @brief Verify routed virtual devices still do not expose DCC.
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(device_tests, test_Routed_Device_DCC_Remains_Blocked)
#else
static void test_Routed_Device_DCC_Remains_Blocked(void)
#endif
{
BACNET_CHARACTER_STRING object_name = { 0 };
uint8_t apdu[MAX_APDU] = { 0 };
uint16_t device_index = 0;
int len = 0;
bool status = false;
Device_Init(NULL);
Routing_Device_Init(1000);
status = characterstring_init_ansi(&object_name, "Virtual Device");
zassert_true(status, NULL);
device_index = Add_Routed_Device(1001, &object_name, "Virtual Device");
zassert_equal(device_index, 1, NULL);
status = Set_Routed_Device_Object_Index(0);
zassert_true(status, NULL);
len = Routed_Device_Service_Approval(
SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, 0, NULL, 0);
zassert_equal(len, 0, NULL);
status = Set_Routed_Device_Object_Index(device_index);
zassert_true(status, NULL);
len = Routed_Device_Service_Approval(
SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, 0, NULL, 0);
zassert_not_equal(len, 0, NULL);
len = Routed_Device_Service_Approval(
SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, 0, apdu, 1);
zassert_true(len > 0, NULL);
status = Set_Routed_Device_Object_Index(0);
zassert_true(status, NULL);
}
#endif
/**
* @brief ReadProperty handler for this objects proprietary properties.
* For the given ReadProperty data, the application_data is loaded
@@ -421,6 +464,190 @@ static void testDevice(void)
return;
}
#if defined(BAC_ROUTING)
static void test_Routed_Device_Reinitialize_Unsupported_State(
BACNET_REINITIALIZED_STATE state)
{
bool status = false;
BACNET_REINITIALIZE_DEVICE_DATA rd_data = { 0 };
Device_Reinitialize_State_Set(BACNET_REINIT_IDLE);
Device_Reinitialize_Password_Set(NULL);
rd_data.error_class = ERROR_CLASS_DEVICE;
rd_data.error_code = ERROR_CODE_SUCCESS;
rd_data.state = state;
characterstring_init_ansi(&rd_data.password, NULL);
status = Device_Reinitialize(&rd_data);
zassert_false(status, NULL);
zassert_equal(
rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s",
bactext_error_class_name(rd_data.error_class));
zassert_equal(
rd_data.error_code, ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
"error-code=%s", bactext_error_code_name(rd_data.error_code));
zassert_equal(Device_Reinitialized_State(), BACNET_REINIT_IDLE, NULL);
}
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(device_tests, test_Routed_Device_Reinitialize)
#else
static void test_Routed_Device_Reinitialize(void)
#endif
{
bool status = false;
BACNET_CHARACTER_STRING object_name = { 0 };
BACNET_REINITIALIZE_DEVICE_DATA rd_data = { 0 };
Device_Init(NULL);
Routing_Device_Init(100);
characterstring_init_ansi(&object_name, "VirtualDevice");
zassert_equal(Add_Routed_Device(101, &object_name, "Virtual"), 1, NULL);
status = Set_Routed_Device_Object_Index(0);
zassert_true(status, NULL);
zassert_equal(
Routed_Device_Service_Approval(
SERVICE_SUPPORTED_REINITIALIZE_DEVICE, 0, NULL, 0),
0, NULL);
zassert_equal(
Routed_Device_Service_Approval(
SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, 0, NULL, 0),
0, NULL);
Device_Reinitialize_State_Set(BACNET_REINIT_IDLE);
Device_Reinitialize_Password_Set(NULL);
rd_data.error_class = ERROR_CLASS_DEVICE;
rd_data.error_code = ERROR_CODE_SUCCESS;
rd_data.state = BACNET_REINIT_COLDSTART;
characterstring_init_ansi(&rd_data.password, NULL);
status = Device_Reinitialize(&rd_data);
zassert_true(status, NULL);
zassert_equal(Device_Reinitialized_State(), BACNET_REINIT_COLDSTART, NULL);
status = Set_Routed_Device_Object_Index(1);
zassert_true(status, NULL);
zassert_equal(
Routed_Device_Service_Approval(
SERVICE_SUPPORTED_REINITIALIZE_DEVICE, 0, NULL, 0),
0, NULL);
zassert_not_equal(
Routed_Device_Service_Approval(
SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, 0, NULL, 0),
0, NULL);
test_Routed_Device_Reinitialize_Unsupported_State(BACNET_REINIT_COLDSTART);
test_Routed_Device_Reinitialize_Unsupported_State(BACNET_REINIT_WARMSTART);
test_Routed_Device_Reinitialize_Unsupported_State(
BACNET_REINIT_ACTIVATE_CHANGES);
}
#endif
#if defined(BAC_ROUTING) && defined(BACNET_BACKUP_RESTORE)
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(device_tests, test_Routed_Device_Backup_Restore_Independence)
#else
static void test_Routed_Device_Backup_Restore_Independence(void)
#endif
{
bool status = false;
BACNET_CHARACTER_STRING object_name = { 0 };
BACNET_REINITIALIZE_DEVICE_DATA rd_data = { 0 };
Device_Init(NULL);
Routing_Device_Init(200);
characterstring_init_ansi(&object_name, "VirtualDevice1");
zassert_equal(Add_Routed_Device(201, &object_name, "Virtual1"), 1, NULL);
characterstring_init_ansi(&object_name, "VirtualDevice2");
zassert_equal(Add_Routed_Device(202, &object_name, "Virtual2"), 2, NULL);
rd_data.error_class = ERROR_CLASS_DEVICE;
rd_data.error_code = ERROR_CODE_SUCCESS;
rd_data.state = BACNET_REINIT_STARTRESTORE;
characterstring_init_ansi(&rd_data.password, NULL);
Set_Routed_Device_Object_Index(1);
Device_Reinitialize_Password_Set(NULL);
status = Device_Reinitialize(&rd_data);
zassert_true(status, NULL);
zassert_equal(
Device_Backup_And_Restore_State(), BACKUP_STATE_PERFORMING_A_RESTORE,
NULL);
rd_data.error_class = ERROR_CLASS_DEVICE;
rd_data.error_code = ERROR_CODE_SUCCESS;
status = Device_Reinitialize(&rd_data);
zassert_false(status, NULL);
zassert_equal(rd_data.error_class, ERROR_CLASS_DEVICE, NULL);
zassert_equal(
rd_data.error_code, ERROR_CODE_CONFIGURATION_IN_PROGRESS, NULL);
Set_Routed_Device_Object_Index(0);
zassert_equal(Device_Backup_And_Restore_State(), BACKUP_STATE_IDLE, NULL);
Set_Routed_Device_Object_Index(2);
Device_Reinitialize_Password_Set(NULL);
zassert_equal(Device_Backup_And_Restore_State(), BACKUP_STATE_IDLE, NULL);
status = Device_Reinitialize(&rd_data);
zassert_true(status, NULL);
zassert_equal(
Device_Backup_And_Restore_State(), BACKUP_STATE_PERFORMING_A_RESTORE,
NULL);
Set_Routed_Device_Object_Index(1);
rd_data.error_class = ERROR_CLASS_DEVICE;
rd_data.error_code = ERROR_CODE_SUCCESS;
rd_data.state = BACNET_REINIT_ABORTRESTORE;
status = Device_Reinitialize(&rd_data);
zassert_true(status, NULL);
zassert_equal(Device_Backup_And_Restore_State(), BACKUP_STATE_IDLE, NULL);
Set_Routed_Device_Object_Index(2);
zassert_equal(
Device_Backup_And_Restore_State(), BACKUP_STATE_PERFORMING_A_RESTORE,
NULL);
}
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(device_tests, test_Routed_Device_Backup_Countdown_Per_Device)
#else
static void test_Routed_Device_Backup_Countdown_Per_Device(void)
#endif
{
bool status = false;
BACNET_CHARACTER_STRING object_name = { 0 };
Device_Init(NULL);
Routing_Device_Init(300);
characterstring_init_ansi(&object_name, "VirtualDevice1");
zassert_equal(Add_Routed_Device(301, &object_name, "Virtual1"), 1, NULL);
characterstring_init_ansi(&object_name, "VirtualDevice2");
zassert_equal(Add_Routed_Device(302, &object_name, "Virtual2"), 2, NULL);
status = Set_Routed_Device_Object_Index(1);
zassert_true(status, NULL);
Device_Backup_Failure_Timeout_Set(1);
Device_Backup_And_Restore_State_Set(BACKUP_STATE_PERFORMING_A_BACKUP);
Device_Backup_Failure_Timeout_Restart();
status = Set_Routed_Device_Object_Index(2);
zassert_true(status, NULL);
zassert_equal(Device_Backup_And_Restore_State(), BACKUP_STATE_IDLE, NULL);
status = Set_Routed_Device_Object_Index(1);
zassert_true(status, NULL);
Device_Timer(500);
zassert_equal(
Device_Backup_And_Restore_State(), BACKUP_STATE_PERFORMING_A_BACKUP,
NULL);
Device_Timer(500);
zassert_equal(
Device_Backup_And_Restore_State(), BACKUP_STATE_BACKUP_FAILURE, NULL);
status = Set_Routed_Device_Object_Index(2);
zassert_true(status, NULL);
zassert_equal(Device_Backup_And_Restore_State(), BACKUP_STATE_IDLE, NULL);
}
#endif
/**
* @}
*/
@@ -430,9 +657,25 @@ ZTEST_SUITE(device_tests, NULL, NULL, NULL, NULL, NULL);
#else
void test_main(void)
{
#if defined(BAC_ROUTING) && defined(BACNET_BACKUP_RESTORE)
ztest_test_suite(
device_tests, ztest_unit_test(testDevice),
ztest_unit_test(test_Device_Data_Sharing),
ztest_unit_test(test_Routed_Device_DCC_Remains_Blocked),
ztest_unit_test(test_Routed_Device_Reinitialize),
ztest_unit_test(test_Routed_Device_Backup_Restore_Independence),
ztest_unit_test(test_Routed_Device_Backup_Countdown_Per_Device));
#elif defined(BAC_ROUTING)
ztest_test_suite(
device_tests, ztest_unit_test(testDevice),
ztest_unit_test(test_Device_Data_Sharing),
ztest_unit_test(test_Routed_Device_DCC_Remains_Blocked),
ztest_unit_test(test_Routed_Device_Reinitialize));
#else
ztest_test_suite(
device_tests, ztest_unit_test(testDevice),
ztest_unit_test(test_Device_Data_Sharing));
#endif
ztest_run_test_suite(device_tests);
}