Coverage Report

Created: 2026-09-27 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541/tests/testing-plugins/testing_networklayers.c
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "testing_networklayers.h"
6
7
#include <open62541/plugin/log_stdout.h>
8
9
109k
#define TEST_CM_MAX_CONNS 16
10
11
typedef struct {
12
    uintptr_t connId;
13
    void *application;
14
    void *context;
15
    UA_ConnectionManager_connectionCallback callback;
16
    size_t rxCount;
17
    size_t txCount;
18
} TestCMConnection;
19
20
/* TestCM embeds UA_ConnectionManager as its first member so that a
21
 * UA_ConnectionManager* can be cast directly to TestCM* and back. */
22
typedef struct TestCM {
23
    UA_ConnectionManager cm;
24
    void *context; /* opaque user context, get/set via public API */
25
    UA_ByteString lastSent; /* last buffer stolen by sendWithConnection */
26
    UA_Boolean logEvents;
27
    UA_StatusCode (*sendWithConnectionOverload)(UA_ConnectionManager *cm,
28
                                               uintptr_t connectionId,
29
                                               const UA_KeyValueMap *params,
30
                                               UA_ByteString *buf);
31
    TestCMConnection conns[TEST_CM_MAX_CONNS];
32
    size_t connCount;
33
    uintptr_t nextConnId;
34
} TestCM;
35
36
static const char *
37
0
testCM_connectionStateName(UA_ConnectionState state) {
38
0
    switch(state) {
39
0
    case UA_CONNECTIONSTATE_CLOSED:
40
0
        return "closed";
41
0
    case UA_CONNECTIONSTATE_OPENING:
42
0
        return "opening";
43
0
    case UA_CONNECTIONSTATE_ESTABLISHED:
44
0
        return "established";
45
0
    case UA_CONNECTIONSTATE_CLOSING:
46
0
        return "closing";
47
0
    case UA_CONNECTIONSTATE_BLOCKING:
48
0
        return "blocking";
49
0
    case UA_CONNECTIONSTATE_REOPENING:
50
0
        return "reopening";
51
0
    default:
52
0
        return "unknown";
53
0
    }
54
0
}
55
56
static void
57
testCM_logEvent(TestCM *tcm, const char *eventName, uintptr_t connectionId,
58
3.96k
                UA_ConnectionState state, const UA_ByteString *msg) {
59
3.96k
    if(!tcm->logEvents)
60
3.96k
        return;
61
62
0
    UA_String b64Payload = UA_STRING_NULL;
63
0
    UA_ByteString_toBase64(msg, &b64Payload);
64
65
0
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
66
0
                "[Connection %u] protocol=%S event=%s state=%s payload=%S",
67
0
                (unsigned)connectionId, tcm->cm.protocol, eventName,
68
0
                testCM_connectionStateName(state), b64Payload);
69
70
0
    UA_String_clear(&b64Payload);
71
0
}
72
73
void
74
0
TestConnectionManager_setContext(UA_ConnectionManager *cm, void *context) {
75
0
    ((TestCM *)(void *)cm)->context = context;
76
0
}
77
78
void *
79
0
TestConnectionManager_getContext(UA_ConnectionManager *cm) {
80
0
    return ((TestCM *)(void *)cm)->context;
81
0
}
82
83
const UA_ByteString *
84
0
TestConnectionManager_getLastSent(UA_ConnectionManager *cm) {
85
0
    return &((TestCM *)(void *)cm)->lastSent;
86
0
}
87
88
UA_StatusCode
89
TestConnectionManager_createConnection(UA_ConnectionManager *cm,
90
                                       void *application,
91
                                       void *context,
92
                                       UA_ConnectionManager_connectionCallback connectionCallback,
93
0
                                       uintptr_t *outConnectionId) {
94
0
    TestCM *tcm = (TestCM *)(void *)cm;
95
0
    if(!tcm || !connectionCallback || !outConnectionId)
96
0
        return UA_STATUSCODE_BADINVALIDARGUMENT;
97
98
0
    size_t slot = TEST_CM_MAX_CONNS;
99
0
    for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
100
0
        if(!tcm->conns[i].callback) {
101
0
            slot = i;
102
0
            break;
103
0
        }
104
0
    }
105
0
    if(slot == TEST_CM_MAX_CONNS)
106
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
107
108
0
    uintptr_t connId = tcm->nextConnId++;
109
0
    tcm->conns[slot].connId = connId;
110
0
    tcm->conns[slot].application = application;
111
0
    tcm->conns[slot].context = context;
112
0
    tcm->conns[slot].callback = connectionCallback;
113
0
    tcm->connCount++;
114
0
    *outConnectionId = connId;
115
0
    return UA_STATUSCODE_GOOD;
116
0
}
117
118
UA_StatusCode
119
TestConnectionManager_removeConnection(UA_ConnectionManager *cm,
120
1.69k
                                       uintptr_t connectionId) {
121
1.69k
    TestCM *tcm = (TestCM *)(void *)cm;
122
1.69k
    if(!tcm)
123
0
        return UA_STATUSCODE_BADINVALIDARGUMENT;
124
125
28.7k
    for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
126
27.0k
        if(!tcm->conns[i].callback || tcm->conns[i].connId != connectionId)
127
27.0k
            continue;
128
0
        tcm->conns[i].callback = NULL;
129
0
        tcm->conns[i].context = NULL;
130
0
        tcm->conns[i].application = NULL;
131
0
        tcm->connCount--;
132
0
        if(tcm->connCount == 0 &&
133
0
           tcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING)
134
0
            tcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED;
135
0
        return UA_STATUSCODE_GOOD;
136
27.0k
    }
137
1.69k
    return UA_STATUSCODE_BADNOTFOUND;
138
1.69k
}
139
140
UA_StatusCode
141
TestConnectionManager_inject(UA_ConnectionManager *cm,
142
                             uintptr_t connectionId,
143
                             UA_ConnectionState state,
144
                             const UA_KeyValueMap *params,
145
1.07k
                             const UA_ByteString *msg) {
146
1.07k
    TestCM *tcm = (TestCM *)(void *)cm;
147
1.07k
    if(!tcm)
148
0
        return UA_STATUSCODE_BADINVALIDARGUMENT;
149
150
1.07k
    UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
151
1.07k
    UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
152
1.07k
    const UA_KeyValueMap *injectParams = params ? params : &emptyKvm;
153
1.07k
    UA_ByteString injectMsg = msg ? *msg : emptyMsg;
154
1.07k
    const char *eventName = (injectMsg.length > 0) ? "receive-injected" : "inject";
155
156
18.2k
    for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
157
17.1k
        if(!tcm->conns[i].callback || tcm->conns[i].connId != connectionId)
158
17.1k
            continue;
159
160
0
        testCM_logEvent(tcm, eventName, connectionId, state, &injectMsg);
161
162
0
        tcm->conns[i].callback(cm, tcm->conns[i].connId,
163
0
                             tcm->conns[i].application,
164
0
                             &tcm->conns[i].context,
165
0
                             state, injectParams, injectMsg);
166
167
0
        if(injectMsg.length > 0)
168
0
            tcm->conns[i].rxCount++;
169
170
0
        if(state == UA_CONNECTIONSTATE_CLOSING ||
171
0
           state == UA_CONNECTIONSTATE_CLOSED) {
172
0
            tcm->conns[i].callback = NULL;
173
0
            tcm->conns[i].context = NULL;
174
0
            tcm->conns[i].application = NULL;
175
0
            tcm->connCount--;
176
0
        }
177
178
0
        return UA_STATUSCODE_GOOD;
179
17.1k
    }
180
181
1.07k
    return UA_STATUSCODE_BADNOTFOUND;
182
1.07k
}
183
184
static UA_StatusCode
185
0
testCM_eventSourceStart(UA_EventSource *es) {
186
0
    UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
187
0
    TestCM *tcm = (TestCM *)(void *)cm;
188
0
    UA_assert(tcm);
189
190
0
    const UA_Boolean *enabled = (const UA_Boolean *)
191
0
        UA_KeyValueMap_getScalar(&cm->eventSource.params,
192
0
                                 UA_QUALIFIEDNAME(0, "log-events"),
193
0
                                 &UA_TYPES[UA_TYPES_BOOLEAN]);
194
0
    tcm->logEvents = enabled ? *enabled : false;
195
196
0
    es->state = UA_EVENTSOURCESTATE_STARTED;
197
0
    return UA_STATUSCODE_GOOD;
198
0
}
199
200
static void
201
8.51k
testCM_eventSourceStop(UA_EventSource *es) {
202
8.51k
    UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
203
8.51k
    TestCM *tcm = (TestCM *)(void *)cm;
204
8.51k
    UA_assert(tcm);
205
206
8.51k
    if(tcm->connCount == 0) {
207
8.51k
        es->state = UA_EVENTSOURCESTATE_STOPPED;
208
8.51k
        return;
209
8.51k
    }
210
211
0
    es->state = UA_EVENTSOURCESTATE_STOPPING;
212
213
0
    UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
214
0
    UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
215
0
    for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
216
0
        if(!tcm->conns[i].callback)
217
0
            continue;
218
0
        testCM_logEvent(tcm, "close", tcm->conns[i].connId,
219
0
                        UA_CONNECTIONSTATE_CLOSING, &emptyMsg);
220
0
        tcm->conns[i].callback(cm, tcm->conns[i].connId,
221
0
                                 tcm->conns[i].application,
222
0
                                 &tcm->conns[i].context,
223
0
                                 UA_CONNECTIONSTATE_CLOSING,
224
0
                                 &emptyKvm, emptyMsg);
225
        /* Remove the connection entry so connCount drops to 0 and the CM can
226
         * transition to STOPPED. The callback (e.g. DiscoveryManager) must NOT
227
         * call closeConnection in response to a CLOSING notification — that is
228
         * the CM's responsibility during shutdown. */
229
0
        tcm->conns[i].callback = NULL;
230
0
        tcm->conns[i].context = NULL;
231
0
        tcm->conns[i].application = NULL;
232
0
        tcm->connCount--;
233
0
    }
234
0
    es->state = UA_EVENTSOURCESTATE_STOPPED;
235
0
}
236
237
static UA_StatusCode
238
8.51k
testCM_eventSourceFree(UA_EventSource *es) {
239
8.51k
    UA_ConnectionManager *cm = (UA_ConnectionManager *)es;
240
8.51k
    TestCM *tcm = (TestCM *)(void *)cm;
241
8.51k
    UA_assert(tcm);
242
243
8.51k
    testCM_eventSourceStop(es);
244
8.51k
    UA_KeyValueMap_clear(&cm->eventSource.params);
245
8.51k
    UA_ByteString_clear(&tcm->lastSent);
246
8.51k
    UA_free(tcm);
247
8.51k
    return UA_STATUSCODE_GOOD;
248
8.51k
}
249
250
static UA_StatusCode
251
testCM_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId,
252
3.65k
                          const UA_KeyValueMap *params, UA_ByteString *buf) {
253
3.65k
    TestCM *tcm = (TestCM *)(void *)cm;
254
3.65k
    testCM_logEvent(tcm, "send", connectionId,
255
3.65k
                    UA_CONNECTIONSTATE_ESTABLISHED, buf);
256
    /* Update tx counter if the connection is tracked (opportunistic). */
257
62.0k
    for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
258
58.4k
        if(tcm->conns[i].callback && tcm->conns[i].connId == connectionId) {
259
0
            tcm->conns[i].txCount++;
260
0
            break;
261
0
        }
262
58.4k
    }
263
3.65k
    if(tcm->sendWithConnectionOverload)
264
0
        return tcm->sendWithConnectionOverload(cm, connectionId, params, buf);
265
    /* Default: steal the buffer into lastSent */
266
3.65k
    UA_ByteString_clear(&tcm->lastSent);
267
3.65k
    tcm->lastSent = *buf;
268
3.65k
    UA_ByteString_init(buf);
269
3.65k
    return UA_STATUSCODE_GOOD;
270
3.65k
}
271
272
static UA_StatusCode
273
testCM_openConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params,
274
                      void *application, void *context,
275
0
                      UA_ConnectionManager_connectionCallback cb) {
276
0
    TestCM *tcm = (TestCM *)(void *)cm;
277
0
    (void)params;
278
0
    uintptr_t connId;
279
0
    UA_StatusCode ret = TestConnectionManager_createConnection(cm, application,
280
0
                                                               context, cb, &connId);
281
0
    if(ret != UA_STATUSCODE_GOOD)
282
0
        return ret;
283
0
    testCM_logEvent(tcm, "open", connId, UA_CONNECTIONSTATE_OPENING, NULL);
284
0
    UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
285
0
    UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
286
0
    return TestConnectionManager_inject(cm, connId, UA_CONNECTIONSTATE_ESTABLISHED,
287
0
                                        &emptyKvm, &emptyMsg);
288
0
}
289
290
static UA_StatusCode
291
1.69k
testCM_closeConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
292
1.69k
    TestCM *tcm = (TestCM *)(void *)cm;
293
1.69k
    UA_KeyValueMap emptyKvm = UA_KEYVALUEMAP_NULL;
294
1.69k
    UA_ByteString emptyMsg = UA_BYTESTRING_NULL;
295
1.69k
    testCM_logEvent(tcm, "close", connectionId, UA_CONNECTIONSTATE_CLOSING, &emptyMsg);
296
1.69k
    TestConnectionManager_inject(cm, connectionId, UA_CONNECTIONSTATE_CLOSING,
297
1.69k
                                 &emptyKvm, &emptyMsg);
298
1.69k
    return TestConnectionManager_removeConnection(cm, connectionId);
299
1.69k
}
300
301
UA_StatusCode
302
TestConnectionManager_getCounters(UA_ConnectionManager *cm, uintptr_t connectionId,
303
0
                                   size_t *rxCount, size_t *txCount) {
304
0
    TestCM *tcm = (TestCM *)(void *)cm;
305
0
    for(size_t i = 0; i < TEST_CM_MAX_CONNS; i++) {
306
0
        if(!tcm->conns[i].callback || tcm->conns[i].connId != connectionId)
307
0
            continue;
308
0
        if(rxCount) *rxCount = tcm->conns[i].rxCount;
309
0
        if(txCount) *txCount = tcm->conns[i].txCount;
310
0
        return UA_STATUSCODE_GOOD;
311
0
    }
312
0
    return UA_STATUSCODE_BADNOTFOUND;
313
0
}
314
315
static UA_StatusCode
316
testCM_allocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
317
3.65k
                          UA_ByteString *buf, size_t bufSize) {
318
3.65k
    (void)cm;
319
3.65k
    (void)connectionId;
320
3.65k
    return UA_ByteString_allocBuffer(buf, bufSize);
321
3.65k
}
322
323
static void
324
testCM_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
325
0
                         UA_ByteString *buf) {
326
0
    (void)cm;
327
0
    (void)connectionId;
328
0
    UA_ByteString_clear(buf);
329
0
}
330
331
UA_ConnectionManager *
332
TestConnectionManager_new(const char *protocol,
333
8.51k
                          const TestConnectionManager_CallbackOverloads *overloads) {
334
8.51k
    if(!protocol || !protocol[0])
335
0
        return NULL;
336
337
8.51k
    TestCM *tcm = (TestCM*)UA_calloc(1, sizeof(TestCM));
338
8.51k
    if(!tcm)
339
0
        return NULL;
340
341
8.51k
    tcm->nextConnId = 100;
342
8.51k
    tcm->logEvents = false;
343
344
8.51k
    UA_ConnectionManager *cm = &tcm->cm;
345
8.51k
    cm->eventSource.next = NULL;
346
8.51k
    cm->eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER;
347
8.51k
    cm->eventSource.name = UA_STRING((char*)(uintptr_t)"test-cm");
348
8.51k
    cm->eventSource.eventLoop = NULL;
349
8.51k
    cm->eventSource.params = UA_KEYVALUEMAP_NULL;
350
8.51k
    cm->eventSource.state = UA_EVENTSOURCESTATE_FRESH;
351
8.51k
    cm->eventSource.start = testCM_eventSourceStart;
352
8.51k
    cm->eventSource.stop = testCM_eventSourceStop;
353
8.51k
    cm->eventSource.free = testCM_eventSourceFree;
354
8.51k
    cm->protocol = UA_STRING((char*)(uintptr_t)protocol);
355
8.51k
    cm->openConnection = (overloads && overloads->openConnection) ?
356
8.51k
                         overloads->openConnection : testCM_openConnection;
357
8.51k
    cm->sendWithConnection = testCM_sendWithConnection;
358
8.51k
    cm->closeConnection = (overloads && overloads->closeConnection) ?
359
8.51k
                          overloads->closeConnection : testCM_closeConnection;
360
8.51k
    tcm->sendWithConnectionOverload = overloads ? overloads->sendWithConnection : NULL;
361
8.51k
    cm->allocNetworkBuffer = testCM_allocNetworkBuffer;
362
8.51k
    cm->freeNetworkBuffer = testCM_freeNetworkBuffer;
363
364
8.51k
    return cm;
365
8.51k
}