From b6fc656edc7598fa0787fe78cc612458ec886afb Mon Sep 17 00:00:00 2001 From: Niels Beier Date: Fri, 16 Oct 2026 13:22:31 +0200 Subject: [PATCH] fix(pubsub): defer group transport validation until server startup (#8583) * fix(pubsub): defer group transport validation until server startup * test(pubsub): add a writer group with a UDP address before server startup --- src/pubsub/ua_pubsub_readergroup.c | 5 +++ src/pubsub/ua_pubsub_writergroup.c | 10 +++++ tests/pubsub/check_pubsub_state_machine.c | 47 +++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index 2d897d82b..a8bddc757 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -1033,6 +1033,11 @@ UA_ReaderGroup_connectMQTT(UA_PubSubManager *psm, UA_ReaderGroup *rg, return res; } + /* A fresh ConnectionManager cannot perform the validation open before + * server startup. Defer it until the PubSubManager becomes operational. */ + if(psm->drv.state != UA_LIFECYCLESTATE_STARTED) + return UA_STATUSCODE_GOOD; + /* Set up the connection parameters. * TODO: Complete the MQTT parameters. */ UA_Boolean listen = true; diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 08e50c346..2fb8321e9 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -1474,6 +1474,11 @@ UA_WriterGroup_connectUDPUnicast(UA_PubSubManager *psm, UA_WriterGroup *wg, return UA_STATUSCODE_BADINVALIDARGUMENT; } + /* A fresh ConnectionManager cannot open even a validation-only + * connection. Defer the transport validation until server startup. */ + if(psm->drv.state != UA_LIFECYCLESTATE_STARTED) + return UA_STATUSCODE_GOOD; + /* Set up the connection parameters */ UA_Boolean listen = false; UA_KeyValuePair kvp[5]; @@ -1532,6 +1537,11 @@ UA_WriterGroup_connectMQTT(UA_PubSubManager *psm, UA_WriterGroup *wg, return res; } + /* See the UDP path above. A fresh ConnectionManager cannot perform the + * validation open, so defer it until server startup. */ + if(psm->drv.state != UA_LIFECYCLESTATE_STARTED) + return UA_STATUSCODE_GOOD; + /* Set up the connection parameters. * TODO: Complete the MQTT parameters. */ UA_Boolean listen = false; diff --git a/tests/pubsub/check_pubsub_state_machine.c b/tests/pubsub/check_pubsub_state_machine.c index 8c5599c01..90595e056 100644 --- a/tests/pubsub/check_pubsub_state_machine.c +++ b/tests/pubsub/check_pubsub_state_machine.c @@ -181,6 +181,52 @@ START_TEST(Connection_EnableBeforeStartup_PausesAndRecovers) { ck_assert_int_eq(connection->head.state, UA_PUBSUBSTATE_OPERATIONAL); } END_TEST +/* A WriterGroup with its own UDP address can be added before the server and + * its ConnectionManagers are started. The transport validation is deferred + * until startup. */ +START_TEST(WriterGroup_UdpAddressBeforeStartup_IsAccepted) { + addMinimalPubSubConnection(); + + UA_NetworkAddressUrlDataType addressUrl = + {UA_STRING_NULL, UA_STRING("opc.udp://127.0.0.1:4841/")}; + UA_DatagramWriterGroupTransport2DataType transport; + UA_DatagramWriterGroupTransport2DataType_init(&transport); + UA_ExtensionObject_setValue(&transport.address, &addressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + + UA_UadpWriterGroupMessageDataType messageSettings; + UA_UadpWriterGroupMessageDataType_init(&messageSettings); + + UA_WriterGroupConfig cfg; + memset(&cfg, 0, sizeof(UA_WriterGroupConfig)); + cfg.name = UA_STRING("Pre-start WG"); + cfg.publishingInterval = 100.0; + cfg.writerGroupId = 1; + cfg.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + UA_ExtensionObject_setValue(&cfg.messageSettings, &messageSettings, + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); + UA_ExtensionObject_setValue(&cfg.transportSettings, &transport, + &UA_TYPES[UA_TYPES_DATAGRAMWRITERGROUPTRANSPORT2DATATYPE]); + + UA_StatusCode res = + UA_Server_addWriterGroup(server, connectionIdent, &cfg, &writerGroupIdent); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + + /* The deferred validation does not prevent the group from running after + * the startup */ + res = UA_Server_run_startup(server); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + res = UA_Server_enablePubSubConnection(server, connectionIdent); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + res = UA_Server_enableWriterGroup(server, writerGroupIdent); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + + UA_PubSubState st = UA_PUBSUBSTATE_DISABLED; + res = UA_Server_getWriterGroupState(server, writerGroupIdent, &st); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + ck_assert_int_eq(st, UA_PUBSUBSTATE_OPERATIONAL); +} END_TEST + /* WriterGroup: enabling while the parent PubSubConnection is in * DISABLED state must land the WriterGroup in the PAUSED state. This * exercises the "if(connection->head.state != UA_PUBSUBSTATE_OPERATIONAL)" @@ -545,6 +591,7 @@ stateMachineSuite(void) { TCase *tc_wg_state = tcase_create("WriterGroup state-machine branches"); tcase_add_checked_fixture(tc_wg_state, setup, teardown); + tcase_add_test(tc_wg_state, WriterGroup_UdpAddressBeforeStartup_IsAccepted); tcase_add_test(tc_wg_state, WriterGroup_EnableWhileConnectionDisabled_GoesToPaused); tcase_add_test(tc_wg_state, WriterGroup_BeforeStateChangeCallbackIsInvoked); tcase_add_test(tc_wg_state, WriterGroup_EnableWhenDeleteFlagSet_Fails);