/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 | } |