/src/open62541_15/src/client/ua_client.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 | | * Copyright 2015-2020 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | * Copyright 2015-2016 (c) Sten GrĂ¼ner |
7 | | * Copyright 2015-2016 (c) Chris Iatrou |
8 | | * Copyright 2015 (c) hfaham |
9 | | * Copyright 2015-2017 (c) Florian Palm |
10 | | * Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA |
11 | | * Copyright 2015 (c) Holger Jeromin |
12 | | * Copyright 2015 (c) Oleksiy Vasylyev |
13 | | * Copyright 2016 (c) TorbenD |
14 | | * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
15 | | * Copyright 2016 (c) Lykurg |
16 | | * Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB |
17 | | * Copyright 2018 (c) Kalycito Infotech Private Limited |
18 | | * Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart |
19 | | * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) |
20 | | * Copyright 2022 (c) Linutronix GmbH (Author: Muddasir Shakil) |
21 | | */ |
22 | | |
23 | | #include <open62541/transport_generated.h> |
24 | | |
25 | | #include "ua_client_internal.h" |
26 | | #include "../ua_types_encoding_binary.h" |
27 | | |
28 | | static void |
29 | | clientHouseKeeping(UA_Client *client, void *_); |
30 | | |
31 | | /********************/ |
32 | | /* Client Lifecycle */ |
33 | | /********************/ |
34 | | |
35 | | UA_StatusCode |
36 | 0 | UA_ClientConfig_copy(UA_ClientConfig const *src, UA_ClientConfig *dst){ |
37 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
38 | |
|
39 | 0 | retval = UA_ApplicationDescription_copy(&src->clientDescription, &dst->clientDescription); |
40 | 0 | if(retval != UA_STATUSCODE_GOOD) |
41 | 0 | goto cleanup; |
42 | | |
43 | 0 | retval = UA_String_copy(&src->endpointUrl, &dst->endpointUrl); |
44 | 0 | if(retval != UA_STATUSCODE_GOOD) |
45 | 0 | goto cleanup; |
46 | | |
47 | 0 | retval = UA_ExtensionObject_copy(&src->userIdentityToken, &dst->userIdentityToken); |
48 | 0 | if(retval != UA_STATUSCODE_GOOD) |
49 | 0 | goto cleanup; |
50 | | |
51 | 0 | retval = UA_String_copy(&src->securityPolicyUri, &dst->securityPolicyUri); |
52 | 0 | if(retval != UA_STATUSCODE_GOOD) |
53 | 0 | goto cleanup; |
54 | | |
55 | 0 | retval = UA_EndpointDescription_copy(&src->endpoint, &dst->endpoint); |
56 | 0 | if(retval != UA_STATUSCODE_GOOD) |
57 | 0 | goto cleanup; |
58 | | |
59 | 0 | retval = UA_UserTokenPolicy_copy(&src->userTokenPolicy, &dst->userTokenPolicy); |
60 | 0 | if(retval != UA_STATUSCODE_GOOD) |
61 | 0 | goto cleanup; |
62 | | |
63 | 0 | retval = UA_String_copy(&src->applicationUri, &dst->applicationUri); |
64 | 0 | if(retval != UA_STATUSCODE_GOOD) |
65 | 0 | goto cleanup; |
66 | | |
67 | 0 | retval = UA_String_copy(&src->authSecurityPolicyUri, |
68 | 0 | &dst->authSecurityPolicyUri); |
69 | 0 | if(retval != UA_STATUSCODE_GOOD) |
70 | 0 | goto cleanup; |
71 | | |
72 | 0 | retval = UA_String_copy(&src->sessionName, &dst->sessionName); |
73 | 0 | if(retval != UA_STATUSCODE_GOOD) |
74 | 0 | goto cleanup; |
75 | | |
76 | 0 | retval = UA_Array_copy(src->sessionLocaleIds, src->sessionLocaleIdsSize, |
77 | 0 | (void **)&dst->sessionLocaleIds, &UA_TYPES[UA_TYPES_LOCALEID]); |
78 | 0 | if(retval != UA_STATUSCODE_GOOD) |
79 | 0 | goto cleanup; |
80 | | |
81 | 0 | dst->sessionLocaleIdsSize = src->sessionLocaleIdsSize; |
82 | 0 | retval = UA_Array_copy(src->namespaces, src->namespacesSize, |
83 | 0 | (void**)&dst->namespaces, &UA_TYPES[UA_TYPES_STRING]); |
84 | 0 | if(retval != UA_STATUSCODE_GOOD) |
85 | 0 | goto cleanup; |
86 | 0 | dst->namespacesSize = src->namespacesSize; |
87 | 0 | dst->tcpReuseAddr = src->tcpReuseAddr; |
88 | 0 | dst->allowNonePolicyPassword = src->allowNonePolicyPassword; |
89 | 0 | dst->globalNotificationCallback = src->globalNotificationCallback; |
90 | 0 | dst->lifecycleNotificationCallback = src->lifecycleNotificationCallback; |
91 | 0 | dst->serviceNotificationCallback = src->serviceNotificationCallback; |
92 | 0 | #ifdef UA_ENABLE_ENCRYPTION |
93 | 0 | dst->maxTrustListSize = src->maxTrustListSize; |
94 | 0 | dst->maxRejectedListSize = src->maxRejectedListSize; |
95 | 0 | #endif |
96 | 0 | dst->connectivityCheckInterval = src->connectivityCheckInterval; |
97 | 0 | dst->certificateVerification = src->certificateVerification; |
98 | 0 | dst->clientContext = src->clientContext; |
99 | 0 | dst->customDataTypes = src->customDataTypes; |
100 | 0 | dst->eventLoop = src->eventLoop; |
101 | 0 | dst->externalEventLoop = src->externalEventLoop; |
102 | 0 | dst->inactivityCallback = src->inactivityCallback; |
103 | 0 | dst->localConnectionConfig = src->localConnectionConfig; |
104 | 0 | dst->logging = src->logging; |
105 | 0 | dst->noSession = src->noSession; |
106 | 0 | dst->noReconnect = src->noReconnect; |
107 | 0 | dst->noNewSession = src->noNewSession; |
108 | 0 | if(src->certificateVerification.logging == NULL) |
109 | 0 | dst->certificateVerification.logging = dst->logging; |
110 | 0 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
111 | 0 | dst->outStandingPublishRequests = src->outStandingPublishRequests; |
112 | 0 | #endif |
113 | 0 | dst->requestedSessionTimeout = src->requestedSessionTimeout; |
114 | 0 | dst->secureChannelLifeTime = src->secureChannelLifeTime; |
115 | 0 | dst->securityMode = src->securityMode; |
116 | 0 | dst->stateCallback = src->stateCallback; |
117 | 0 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
118 | 0 | dst->subscriptionInactivityCallback = src->subscriptionInactivityCallback; |
119 | 0 | #endif |
120 | 0 | dst->timeout = src->timeout; |
121 | 0 | dst->securityPolicies = src->securityPolicies; |
122 | 0 | dst->securityPoliciesSize = src->securityPoliciesSize; |
123 | 0 | dst->securityPolicyHistory = src->securityPolicyHistory; |
124 | 0 | dst->authSecurityPolicies = src->authSecurityPolicies; |
125 | 0 | dst->authSecurityPoliciesSize = src->authSecurityPoliciesSize; |
126 | 0 | #ifdef UA_ENABLE_ENCRYPTION |
127 | 0 | dst->privateKeyPasswordCallback = src->privateKeyPasswordCallback; |
128 | 0 | #endif |
129 | |
|
130 | 0 | cleanup: |
131 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
132 | | /* _clear will free the plugins in dst that are a shallow copy from src. */ |
133 | 0 | dst->authSecurityPolicies = NULL; |
134 | 0 | dst->certificateVerification.context = NULL; |
135 | 0 | dst->eventLoop = NULL; |
136 | 0 | dst->logging = NULL; |
137 | 0 | dst->securityPolicies = NULL; |
138 | 0 | dst->securityPolicyHistory = NULL; |
139 | 0 | UA_ClientConfig_clear(dst); |
140 | 0 | } |
141 | 0 | return retval; |
142 | 0 | } |
143 | | |
144 | | UA_Client * |
145 | 40 | UA_Client_newWithConfig(const UA_ClientConfig *config) { |
146 | 40 | if(!config) |
147 | 0 | return NULL; |
148 | 40 | UA_Client *client = (UA_Client*)UA_malloc(sizeof(UA_Client)); |
149 | 40 | if(!client) |
150 | 0 | return NULL; |
151 | 40 | memset(client, 0, sizeof(UA_Client)); |
152 | 40 | client->config = *config; |
153 | | |
154 | 40 | UA_SecureChannel_init(&client->channel); |
155 | 40 | client->channel.config = client->config.localConnectionConfig; |
156 | 40 | client->connectStatus = UA_STATUSCODE_GOOD; |
157 | | |
158 | 40 | #if UA_MULTITHREADING >= 100 |
159 | 40 | UA_LOCK_INIT(&client->clientMutex); |
160 | 40 | #endif |
161 | | |
162 | | /* Initialize the namespace mapping */ |
163 | 40 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
164 | 40 | size_t initialNs = 2 + config->namespacesSize; |
165 | 40 | client->namespaces = (UA_String*)UA_calloc(initialNs, sizeof(UA_String)); |
166 | 40 | if(!client->namespaces) |
167 | 0 | goto error; |
168 | | |
169 | 40 | client->namespacesSize = initialNs; |
170 | 40 | client->namespaces[0] = UA_STRING_ALLOC("http://opcfoundation.org/UA/"); |
171 | 40 | client->namespaces[1] = UA_STRING_NULL; /* Gets set when we connect to the server */ |
172 | 40 | for(size_t i = 0; i < config->namespacesSize; i++) { |
173 | 0 | res |= UA_String_copy(&config->namespaces[i], &client->namespaces[i+2]); |
174 | 0 | } |
175 | 40 | if(res != UA_STATUSCODE_GOOD) |
176 | 0 | goto error; |
177 | | |
178 | 40 | return client; |
179 | | |
180 | 0 | error: |
181 | 0 | memset(&client->config, 0, sizeof(UA_ClientConfig)); |
182 | 0 | UA_Client_delete(client); |
183 | 0 | return NULL; |
184 | 40 | } |
185 | | |
186 | | void |
187 | 40 | UA_ClientConfig_clear(UA_ClientConfig *config) { |
188 | 40 | UA_ApplicationDescription_clear(&config->clientDescription); |
189 | 40 | UA_String_clear(&config->endpointUrl); |
190 | 40 | UA_ExtensionObject_clear(&config->userIdentityToken); |
191 | | |
192 | | /* Delete the SecurityPolicies for Authentication */ |
193 | 40 | if(config->authSecurityPolicies != 0) { |
194 | 80 | for(size_t i = 0; i < config->authSecurityPoliciesSize; i++) |
195 | 40 | config->authSecurityPolicies[i].clear(&config->authSecurityPolicies[i]); |
196 | 40 | UA_free(config->authSecurityPolicies); |
197 | 40 | config->authSecurityPolicies = 0; |
198 | 40 | } |
199 | 40 | UA_String_clear(&config->securityPolicyUri); |
200 | 40 | UA_String_clear(&config->authSecurityPolicyUri); |
201 | | |
202 | 40 | UA_EndpointDescription_clear(&config->endpoint); |
203 | 40 | UA_UserTokenPolicy_clear(&config->userTokenPolicy); |
204 | | |
205 | 40 | UA_String_clear(&config->applicationUri); |
206 | | |
207 | 40 | if(config->certificateVerification.clear) |
208 | 40 | config->certificateVerification.clear(&config->certificateVerification); |
209 | | |
210 | | /* Delete the SecurityPolicies */ |
211 | 40 | if(config->securityPolicies != 0) { |
212 | 80 | for(size_t i = 0; i < config->securityPoliciesSize; i++) |
213 | 40 | config->securityPolicies[i].clear(&config->securityPolicies[i]); |
214 | 40 | UA_free(config->securityPolicies); |
215 | 40 | config->securityPolicies = 0; |
216 | 40 | } |
217 | | |
218 | 40 | while(config->securityPolicyHistory) { |
219 | 0 | UA_ClientConfig_PolicyHistory *previous = config->securityPolicyHistory; |
220 | 0 | config->securityPolicyHistory = previous->next; |
221 | 0 | for(size_t i = 0; i < previous->policiesSize; i++) |
222 | 0 | previous->policies[i].clear(&previous->policies[i]); |
223 | 0 | UA_free(previous->policies); |
224 | 0 | UA_free(previous); |
225 | 0 | } |
226 | | |
227 | | /* Stop and delete the EventLoop */ |
228 | 40 | UA_EventLoop *el = config->eventLoop; |
229 | 40 | if(el && !config->externalEventLoop) { |
230 | 40 | if(el->state != UA_EVENTLOOPSTATE_FRESH && |
231 | 0 | el->state != UA_EVENTLOOPSTATE_STOPPED) { |
232 | 0 | el->stop(el); |
233 | 0 | while(el->state != UA_EVENTLOOPSTATE_STOPPED) { |
234 | 0 | el->run(el, 100); |
235 | 0 | } |
236 | 0 | } |
237 | 40 | el->free(el); |
238 | 40 | config->eventLoop = NULL; |
239 | 40 | } |
240 | | |
241 | | /* Logging */ |
242 | 40 | if(config->logging != NULL && config->logging->clear != NULL) |
243 | 40 | config->logging->clear(config->logging); |
244 | 40 | config->logging = NULL; |
245 | | |
246 | 40 | UA_String_clear(&config->sessionName); |
247 | 40 | if(config->sessionLocaleIdsSize > 0 && config->sessionLocaleIds) { |
248 | 0 | UA_Array_delete(config->sessionLocaleIds, |
249 | 0 | config->sessionLocaleIdsSize, &UA_TYPES[UA_TYPES_LOCALEID]); |
250 | 0 | } |
251 | 40 | config->sessionLocaleIds = NULL; |
252 | 40 | config->sessionLocaleIdsSize = 0; |
253 | | |
254 | 40 | UA_Array_delete(config->namespaces, config->namespacesSize, &UA_TYPES[UA_TYPES_STRING]); |
255 | 40 | config->namespaces = NULL; |
256 | 40 | config->namespacesSize = 0; |
257 | 40 | config->securityPoliciesSize = 0; |
258 | 40 | config->authSecurityPoliciesSize = 0; |
259 | | |
260 | | /* Custom Data Types */ |
261 | 40 | UA_cleanupDataTypeWithCustom(config->customDataTypes); |
262 | | |
263 | 40 | #ifdef UA_ENABLE_ENCRYPTION |
264 | 40 | config->privateKeyPasswordCallback = NULL; |
265 | 40 | #endif |
266 | 40 | } |
267 | | |
268 | | void |
269 | 0 | UA_ClientConfig_delete(UA_ClientConfig *config){ |
270 | 0 | UA_assert(config != NULL); |
271 | 0 | UA_ClientConfig_clear(config); |
272 | 0 | UA_free(config); |
273 | 0 | } |
274 | | |
275 | | static void |
276 | 40 | UA_Client_clear(UA_Client *client) { |
277 | | /* Prevent new async service calls in UA_Client_AsyncService_removeAll */ |
278 | 40 | UA_SessionState oldState = client->sessionState; |
279 | 40 | client->sessionState = UA_SESSIONSTATE_CLOSING; |
280 | | |
281 | | /* Delete the async service calls with BADHSUTDOWN */ |
282 | 40 | __Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSHUTDOWN); |
283 | | |
284 | | /* Reset to the old state to properly close the session */ |
285 | 40 | client->sessionState = oldState; |
286 | | |
287 | 40 | UA_Client_disconnect(client); |
288 | | |
289 | | /* Prevent reconnection attempts during the EventLoop teardown |
290 | | * in UA_ClientConfig_clear */ |
291 | 40 | client->connectStatus = UA_STATUSCODE_BADSHUTDOWN; |
292 | | |
293 | 40 | UA_String_clear(&client->discoveryUrl); |
294 | 40 | UA_EndpointDescription_clear(&client->endpoint); |
295 | | |
296 | 40 | UA_ByteString_clear(&client->serverSessionNonce); |
297 | 40 | UA_ByteString_clear(&client->clientSessionNonce); |
298 | | |
299 | | /* Delete the subscriptions */ |
300 | 40 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
301 | 40 | __Client_Subscriptions_clear(client); |
302 | 40 | #endif |
303 | | |
304 | | /* Remove the internal regular callback */ |
305 | 40 | UA_Client_removeCallback(client, client->houseKeepingCallbackId); |
306 | 40 | client->houseKeepingCallbackId = 0; |
307 | | |
308 | | /* Clean up the SecureChannel */ |
309 | 40 | UA_SecureChannel_clear(&client->channel); |
310 | | |
311 | | /* Free the namespace mapping */ |
312 | 40 | UA_Array_delete(client->namespaces, client->namespacesSize, |
313 | 40 | &UA_TYPES[UA_TYPES_STRING]); |
314 | 40 | client->namespaces = NULL; |
315 | 40 | client->namespacesSize = 0; |
316 | | |
317 | | /* Call the application notification callback */ |
318 | 40 | UA_ClientConfig *config = &client->config; |
319 | 40 | if(config->lifecycleNotificationCallback) |
320 | 0 | config->lifecycleNotificationCallback(client, UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPED, |
321 | 0 | UA_KEYVALUEMAP_NULL); |
322 | 40 | if(config->globalNotificationCallback) |
323 | 40 | config->globalNotificationCallback(client, UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPED, |
324 | 40 | UA_KEYVALUEMAP_NULL); |
325 | | |
326 | 40 | #if UA_MULTITHREADING >= 100 |
327 | 40 | UA_LOCK_DESTROY(&client->clientMutex); |
328 | 40 | #endif |
329 | 40 | } |
330 | | |
331 | | void |
332 | 40 | UA_Client_delete(UA_Client* client) { |
333 | 40 | UA_Client_disconnect(client); |
334 | 40 | UA_Client_clear(client); |
335 | 40 | UA_ClientConfig_clear(&client->config); |
336 | 40 | UA_free(client); |
337 | 40 | } |
338 | | |
339 | | void |
340 | | UA_Client_getState(UA_Client *client, UA_SecureChannelState *channelState, |
341 | 0 | UA_SessionState *sessionState, UA_StatusCode *connectStatus) { |
342 | 0 | lockClient(client); |
343 | 0 | if(channelState) |
344 | 0 | *channelState = client->channel.state; |
345 | 0 | if(sessionState) |
346 | 0 | *sessionState = client->sessionState; |
347 | 0 | if(connectStatus) |
348 | 0 | *connectStatus = client->connectStatus; |
349 | 0 | unlockClient(client); |
350 | 0 | } |
351 | | |
352 | | UA_ClientConfig * |
353 | 80 | UA_Client_getConfig(UA_Client *client) { |
354 | 80 | if(!client) |
355 | 0 | return NULL; |
356 | 80 | return &client->config; |
357 | 80 | } |
358 | | |
359 | | #if UA_LOGLEVEL <= 300 |
360 | | static const char *channelStateTexts[14] = { |
361 | | "Closed", "ReverseListening", "Connecting", "Connected", "ReverseConnected", "RHESent", "HELSent", "HELReceived", "ACKSent", |
362 | | "AckReceived", "OPNSent", "Open", "Closing"}; |
363 | | static const char *sessionStateTexts[6] = |
364 | | {"Closed", "CreateRequested", "Created", |
365 | | "ActivateRequested", "Activated", "Closing"}; |
366 | | #endif |
367 | | |
368 | | void |
369 | 0 | setConnectStatus(UA_Client *client, UA_StatusCode status) { |
370 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
371 | |
|
372 | 0 | client->connectStatus = status; |
373 | 0 | if(status != UA_STATUSCODE_GOOD) |
374 | 0 | closeSecureChannel(client); |
375 | |
|
376 | 0 | notifyClientState(client); |
377 | 0 | } |
378 | | |
379 | | void |
380 | 80 | notifyClientState(UA_Client *client) { |
381 | 80 | UA_LOCK_ASSERT(&client->clientMutex); |
382 | | |
383 | 80 | if(client->connectStatus == client->oldConnectStatus && |
384 | 40 | client->channel.state == client->oldChannelState && |
385 | 40 | client->sessionState == client->oldSessionState) |
386 | 40 | return; |
387 | | |
388 | | #if UA_LOGLEVEL <= 300 |
389 | | UA_Boolean info = (client->connectStatus != UA_STATUSCODE_GOOD); |
390 | | if(client->oldChannelState != client->channel.state) |
391 | | info |= (client->channel.state == UA_SECURECHANNELSTATE_OPEN || |
392 | | client->channel.state == UA_SECURECHANNELSTATE_CLOSED); |
393 | | if(client->oldSessionState != client->sessionState) |
394 | | info |= (client->sessionState == UA_SESSIONSTATE_CREATED || |
395 | | client->sessionState == UA_SESSIONSTATE_ACTIVATED || |
396 | | client->sessionState == UA_SESSIONSTATE_CLOSED); |
397 | | |
398 | | const char *channelStateText = channelStateTexts[client->channel.state]; |
399 | | const char *sessionStateText = sessionStateTexts[client->sessionState]; |
400 | | const char *connectStatusText = UA_StatusCode_name(client->connectStatus); |
401 | | |
402 | | if(info) |
403 | | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
404 | | "Client Status: ChannelState: %s, SessionState: %s, ConnectStatus: %s", |
405 | | channelStateText, sessionStateText, connectStatusText); |
406 | | else |
407 | | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT, |
408 | | "Client Status: ChannelState: %s, SessionState: %s, ConnectStatus: %s", |
409 | | channelStateText, sessionStateText, connectStatusText); |
410 | | #endif |
411 | | |
412 | 40 | client->oldConnectStatus = client->connectStatus; |
413 | 40 | client->oldChannelState = client->channel.state; |
414 | 40 | client->oldSessionState = client->sessionState; |
415 | | |
416 | 40 | if(client->config.stateCallback) |
417 | 0 | client->config.stateCallback(client, client->channel.state, |
418 | 0 | client->sessionState, client->connectStatus); |
419 | 40 | } |
420 | | |
421 | | /****************/ |
422 | | /* Raw Services */ |
423 | | /****************/ |
424 | | |
425 | | /* For both synchronous and asynchronous service calls */ |
426 | | static UA_StatusCode |
427 | | sendRequest(UA_Client *client, const void *request, |
428 | 0 | const UA_DataType *requestType, UA_UInt32 *requestId) { |
429 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
430 | | |
431 | | /* Renew SecureChannel if necessary */ |
432 | 0 | __Client_renewSecureChannel(client); |
433 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
434 | 0 | return client->connectStatus; |
435 | | |
436 | 0 | UA_EventLoop *el = client->config.eventLoop; |
437 | | |
438 | | /* Adjusting the request header. The const attribute is violated, but we |
439 | | * reset to the original state before returning. Use the AuthenticationToken |
440 | | * only once the session is active (or to activate / close it). */ |
441 | 0 | UA_RequestHeader *rr = (UA_RequestHeader*)(uintptr_t)request; |
442 | 0 | UA_NodeId oldToken = rr->authenticationToken; /* Put back in place later */ |
443 | |
|
444 | 0 | if(client->sessionState == UA_SESSIONSTATE_ACTIVATED || |
445 | 0 | requestType == &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST] || |
446 | 0 | requestType == &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST]) |
447 | 0 | rr->authenticationToken = client->authenticationToken; |
448 | 0 | rr->timestamp = el->dateTime_now(el); |
449 | | |
450 | | /* Create a unique handle >100,000 if not manually defined. The handle is |
451 | | * not necessarily unique when manually defined and used to cancel async |
452 | | * service requests. */ |
453 | 0 | if(rr->requestHandle == 0) { |
454 | 0 | if(UA_UNLIKELY(client->requestHandle < 100000)) |
455 | 0 | client->requestHandle = 100000; |
456 | 0 | rr->requestHandle = ++client->requestHandle; |
457 | 0 | } |
458 | | |
459 | | /* Set the timeout hint if not manually defined */ |
460 | 0 | if(rr->timeoutHint == 0) |
461 | 0 | rr->timeoutHint = client->config.timeout; |
462 | | |
463 | | /* Generate the request id */ |
464 | 0 | UA_UInt32 rqId = ++client->requestId; |
465 | |
|
466 | 0 | #ifdef UA_ENABLE_TYPEDESCRIPTION |
467 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel, |
468 | 0 | "Sending request with RequestId %u of type %s", |
469 | 0 | (unsigned)rqId, requestType->typeName); |
470 | | #else |
471 | | UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel, |
472 | | "Sending request with RequestId %u of type %" PRIu32, |
473 | | (unsigned)rqId, requestType->binaryEncodingId.identifier.numeric); |
474 | | #endif |
475 | | |
476 | | /* Send the message */ |
477 | 0 | UA_StatusCode retval = |
478 | 0 | UA_SecureChannel_sendMSG(&client->channel, rqId, rr, requestType); |
479 | |
|
480 | 0 | rr->authenticationToken = oldToken; /* Set back to the original token */ |
481 | | |
482 | | /* Sending failed. The SecureChannel cannot recover from that. Call |
483 | | * closeSecureChannel to a) close from our end and b) set the session to |
484 | | * non-activated. */ |
485 | 0 | if(retval != UA_STATUSCODE_GOOD) |
486 | 0 | closeSecureChannel(client); |
487 | | |
488 | | /* Return the request id */ |
489 | 0 | *requestId = rqId; |
490 | 0 | return retval; |
491 | 0 | } |
492 | | |
493 | | static const UA_NodeId |
494 | | serviceFaultId = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SERVICEFAULT_ENCODING_DEFAULTBINARY}}; |
495 | | |
496 | | /* Look for the async callback in the linked list, execute and delete it */ |
497 | | static UA_StatusCode |
498 | | processMSGResponse(UA_Client *client, UA_UInt32 requestId, |
499 | 0 | const UA_ByteString *msg) { |
500 | 0 | UA_ClientConfig *config = &client->config; |
501 | | |
502 | | /* Find the callback */ |
503 | 0 | AsyncServiceCall *ac; |
504 | 0 | LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) { |
505 | 0 | if(ac->requestId == requestId) |
506 | 0 | break; |
507 | 0 | } |
508 | | |
509 | | /* Part 6, 6.7.6: After the security validation is complete the receiver |
510 | | * shall verify the RequestId and the SequenceNumber. If these checks fail a |
511 | | * Bad_SecurityChecksFailed error is reported. The RequestId only needs to |
512 | | * be verified by the Client since only the Client knows if it is valid or |
513 | | * not. |
514 | | * |
515 | | * But! If an async request has timed out, the RequestId is no longer found. |
516 | | * In theory the server should send an error back before the timeout. But |
517 | | * not all do. In these cases we don't want to close the entire |
518 | | * SecureChannel and just log a warning. The service callback has already |
519 | | * been notified about the timeout before. */ |
520 | 0 | if(!ac) { |
521 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_CLIENT, |
522 | 0 | "Request with unknown RequestId %u", requestId); |
523 | 0 | return UA_STATUSCODE_GOOD; |
524 | 0 | } |
525 | | |
526 | 0 | UA_Response asyncResponse; |
527 | 0 | UA_Response *response = (ac->syncResponse) ? ac->syncResponse : &asyncResponse; |
528 | 0 | const UA_DataType *responseType = ac->responseType; |
529 | | /* Also initialize before decoding the type NodeId: errors there already |
530 | | * invoke the callback and clear the response. ServiceFault decoding only |
531 | | * initializes the header, so initialize the full expected response type. */ |
532 | 0 | UA_init(response, responseType); |
533 | | |
534 | | /* Dequeue ac. We might disconnect the client (remove all ac) in the callback. */ |
535 | 0 | LIST_REMOVE(ac, pointers); |
536 | | |
537 | | /* Decode the response type */ |
538 | 0 | size_t offset = 0; |
539 | 0 | UA_NodeId responseTypeId; |
540 | 0 | UA_StatusCode retval = UA_NodeId_decodeBinary(msg, &offset, &responseTypeId); |
541 | 0 | if(retval != UA_STATUSCODE_GOOD) |
542 | 0 | goto process; |
543 | | |
544 | | /* Verify the type of the response */ |
545 | 0 | if(!UA_NodeId_equal(&responseTypeId, &ac->responseType->binaryEncodingId)) { |
546 | 0 | if(UA_NodeId_equal(&responseTypeId, &serviceFaultId)) { |
547 | | /* Decode as a ServiceFault, i.e. only the response header */ |
548 | 0 | UA_LOG_DEBUG(config->logging, UA_LOGCATEGORY_CLIENT, |
549 | 0 | "Received a ServiceFault response"); |
550 | 0 | responseType = &UA_TYPES[UA_TYPES_SERVICEFAULT]; |
551 | 0 | } else { |
552 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_CLIENT, |
553 | 0 | "Service response type does not match"); |
554 | 0 | retval = UA_STATUSCODE_BADCOMMUNICATIONERROR; |
555 | 0 | goto process; /* Do not decode */ |
556 | 0 | } |
557 | 0 | } |
558 | | |
559 | | /* Decode the response */ |
560 | 0 | #ifdef UA_ENABLE_TYPEDESCRIPTION |
561 | 0 | UA_LOG_DEBUG(config->logging, UA_LOGCATEGORY_CLIENT, |
562 | 0 | "Decode a message of type %s", responseType->typeName); |
563 | | #else |
564 | | UA_LOG_DEBUG(config->logging, UA_LOGCATEGORY_CLIENT, |
565 | | "Decode a message of type %" PRIu32, |
566 | | responseTypeId.identifier.numeric); |
567 | | #endif |
568 | |
|
569 | 0 | UA_DecodeBinaryOptions opt; |
570 | 0 | memset(&opt, 0, sizeof(UA_DecodeBinaryOptions)); |
571 | 0 | opt.customTypes = config->customDataTypes; |
572 | 0 | opt.namespaceMapping = client->channel.namespaceMapping; |
573 | 0 | retval = UA_decodeBinaryInternal(msg, &offset, response, responseType, &opt); |
574 | |
|
575 | 0 | process: |
576 | | /* Process the received MSG response */ |
577 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
578 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_CLIENT, |
579 | 0 | "Could not decode the response with RequestId %u with status %s", |
580 | 0 | (unsigned)requestId, UA_StatusCode_name(retval)); |
581 | 0 | response->responseHeader.serviceResult = retval; |
582 | 0 | } |
583 | | |
584 | | /* The Session closed. The current response is processed with the return code. |
585 | | * The next request first recreates a session. */ |
586 | 0 | if(responseType != &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE] && |
587 | 0 | (response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONIDINVALID || |
588 | 0 | response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONCLOSED)) { |
589 | | /* Clean up the session information and reset the state */ |
590 | 0 | cleanupSession(client); |
591 | |
|
592 | 0 | if(config->noNewSession) { |
593 | | /* Configuration option to not create a new Session. Disconnect the |
594 | | * client. */ |
595 | 0 | client->connectStatus = response->responseHeader.serviceResult; |
596 | 0 | UA_LOG_ERROR(config->logging, UA_LOGCATEGORY_CLIENT, |
597 | 0 | "Session cannot be activated with StatusCode %s. " |
598 | 0 | "The client is configured not to create a new Session.", |
599 | 0 | UA_StatusCode_name(client->connectStatus)); |
600 | 0 | closeSecureChannel(client); |
601 | 0 | } else { |
602 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_CLIENT, |
603 | 0 | "Session no longer valid. A new Session is created for the next " |
604 | 0 | "Service request but we do not re-send the current request."); |
605 | 0 | } |
606 | 0 | } |
607 | | |
608 | | /* Prepare the notification payload */ |
609 | 0 | UA_ApplicationNotificationType nt; |
610 | 0 | static UA_THREAD_LOCAL UA_KeyValuePair notifyPayload[4] = { |
611 | 0 | {{0, UA_STRING_STATIC("securechannel-id")}, {0}}, |
612 | 0 | {{0, UA_STRING_STATIC("session-id")}, {0}}, |
613 | 0 | {{0, UA_STRING_STATIC("request-id")}, {0}}, |
614 | 0 | {{0, UA_STRING_STATIC("service-type")}, {0}} |
615 | 0 | }; |
616 | 0 | UA_KeyValueMap notifyPayloadMap = {4, notifyPayload}; |
617 | 0 | if(config->globalNotificationCallback || config->serviceNotificationCallback) { |
618 | 0 | UA_Variant_setScalar(¬ifyPayload[0].value, |
619 | 0 | &client->channel.securityToken.channelId, |
620 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
621 | 0 | UA_Variant_setScalar(¬ifyPayload[1].value, &client->sessionId, |
622 | 0 | &UA_TYPES[UA_TYPES_NODEID]); |
623 | 0 | UA_Variant_setScalar(¬ifyPayload[2].value, &requestId, |
624 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
625 | 0 | UA_Variant_setScalar(¬ifyPayload[3].value, |
626 | 0 | (void *)(uintptr_t)&ac->responseType->typeId, |
627 | 0 | &UA_TYPES[UA_TYPES_NODEID]); |
628 | 0 | } |
629 | |
|
630 | 0 | if(ac->callback) { |
631 | | /* Notify with UA_APPLICATIONNOTIFICATIONTYPE_SERVICE_BEGIN before the |
632 | | * service response is processed asynchronously */ |
633 | 0 | nt = UA_APPLICATIONNOTIFICATIONTYPE_SERVICE_BEGIN; |
634 | 0 | if(config->serviceNotificationCallback) |
635 | 0 | config->serviceNotificationCallback(client, nt, notifyPayloadMap); |
636 | 0 | if(config->globalNotificationCallback) |
637 | 0 | config->globalNotificationCallback(client, nt, notifyPayloadMap); |
638 | | |
639 | | /* Call the async callback */ |
640 | 0 | ac->callback(client, ac->userdata, requestId, response); |
641 | 0 | } |
642 | | |
643 | | /* Always notify with UA_APPLICATIONNOTIFICATIONTYPE_SERVICE_END that the |
644 | | * service was processed. For the synchronous case this gets called before |
645 | | * the response is returned together with the main control flow. */ |
646 | 0 | nt = UA_APPLICATIONNOTIFICATIONTYPE_SERVICE_END; |
647 | 0 | if(config->serviceNotificationCallback) |
648 | 0 | config->serviceNotificationCallback(client, nt, notifyPayloadMap); |
649 | 0 | if(config->globalNotificationCallback) |
650 | 0 | config->globalNotificationCallback(client, nt, notifyPayloadMap); |
651 | | |
652 | | /* Clean up */ |
653 | 0 | UA_NodeId_clear(&responseTypeId); |
654 | 0 | if(!ac->syncResponse) { |
655 | 0 | UA_clear(response, ac->responseType); |
656 | 0 | UA_free(ac); |
657 | 0 | } else { |
658 | | /* Return a special status code after processing a synchronous message. |
659 | | * This makes the client return control immediately. */ |
660 | 0 | ac->syncResponse = NULL; /* Indicate that response was received */ |
661 | 0 | if(retval == UA_STATUSCODE_GOOD) |
662 | 0 | retval = UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY; |
663 | 0 | } |
664 | 0 | return retval; |
665 | 0 | } |
666 | | |
667 | | UA_StatusCode |
668 | | processServiceResponse(UA_Client *client, UA_SecureChannel *channel, |
669 | | UA_MessageType messageType, UA_UInt32 requestId, |
670 | 40 | UA_ByteString *message) { |
671 | 40 | if(!UA_SecureChannel_isConnected(channel)) { |
672 | 0 | if(messageType == UA_MESSAGETYPE_MSG) { |
673 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard MSG message " |
674 | 0 | "with RequestId %u as the SecureChannel is not connected", |
675 | 0 | requestId); |
676 | 0 | } else { |
677 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard message " |
678 | 0 | "as the SecureChannel is not connected"); |
679 | 0 | } |
680 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
681 | 0 | } |
682 | | |
683 | 40 | switch(messageType) { |
684 | 0 | case UA_MESSAGETYPE_RHE: |
685 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process RHE message"); |
686 | 0 | processRHEMessage(client, message); |
687 | 0 | return UA_STATUSCODE_GOOD; |
688 | 0 | case UA_MESSAGETYPE_ACK: |
689 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process ACK message"); |
690 | 0 | processACKResponse(client, message); |
691 | 0 | return UA_STATUSCODE_GOOD; |
692 | 0 | case UA_MESSAGETYPE_OPN: |
693 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process OPN message"); |
694 | 0 | processOPNResponse(client, message); |
695 | 0 | return UA_STATUSCODE_GOOD; |
696 | 0 | case UA_MESSAGETYPE_ERR: |
697 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process ERR message"); |
698 | 0 | processERRResponse(client, message); |
699 | 0 | return UA_STATUSCODE_GOOD; |
700 | 0 | case UA_MESSAGETYPE_MSG: |
701 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process MSG message " |
702 | 0 | "with RequestId %u", requestId); |
703 | 0 | return processMSGResponse(client, requestId, message); |
704 | 40 | default: |
705 | 40 | UA_LOG_TRACE_CHANNEL(client->config.logging, channel, |
706 | 40 | "Invalid message type"); |
707 | 40 | channel->state = UA_SECURECHANNELSTATE_CLOSING; |
708 | 40 | return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
709 | 40 | } |
710 | 40 | } |
711 | | |
712 | | void |
713 | | __Client_Service(UA_Client *client, const void *request, |
714 | | const UA_DataType *requestType, void *response, |
715 | 20 | const UA_DataType *responseType) { |
716 | 20 | UA_ResponseHeader *respHeader = (UA_ResponseHeader*)response; |
717 | | |
718 | | /* Initialize. Response is valied in case of aborting. */ |
719 | 20 | UA_init(response, responseType); |
720 | | |
721 | | /* Verify that the EventLoop is running */ |
722 | 20 | UA_EventLoop *el = client->config.eventLoop; |
723 | 20 | if(!el || el->state != UA_EVENTLOOPSTATE_STARTED) { |
724 | 20 | respHeader->serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
725 | 20 | return; |
726 | 20 | } |
727 | | |
728 | | /* Check that the SecureChannel is open and also a Session active (if we |
729 | | * want a Session). Otherwise reopen. */ |
730 | 0 | if(!isFullyConnected(client)) { |
731 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
732 | 0 | "Re-establish the connection for the synchronous service call"); |
733 | 0 | connectSync(client); |
734 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) { |
735 | 0 | respHeader->serviceResult = client->connectStatus; |
736 | 0 | return; |
737 | 0 | } |
738 | 0 | } |
739 | | |
740 | | /* Store the channelId to detect if the channel was changed by a |
741 | | * reconnection within the EventLoop run method. */ |
742 | 0 | UA_UInt32 channelId = client->channel.securityToken.channelId; |
743 | | |
744 | | /* Send the request */ |
745 | 0 | UA_UInt32 requestId = 0; |
746 | 0 | UA_StatusCode retval = sendRequest(client, request, requestType, &requestId); |
747 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
748 | | /* If sending failed, the status is set to closing. The SecureChannel is |
749 | | * the actually closed in the next iteration of the EventLoop. */ |
750 | 0 | UA_assert(client->channel.state == UA_SECURECHANNELSTATE_CLOSING || |
751 | 0 | client->channel.state == UA_SECURECHANNELSTATE_CLOSED); |
752 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
753 | 0 | "Sending the request failed with status %s", |
754 | 0 | UA_StatusCode_name(retval)); |
755 | 0 | notifyClientState(client); |
756 | 0 | respHeader->serviceResult = retval; |
757 | 0 | return; |
758 | 0 | } |
759 | | |
760 | | /* Temporarily insert an AsyncServiceCall */ |
761 | 0 | const UA_RequestHeader *rh = (const UA_RequestHeader*)request; |
762 | 0 | AsyncServiceCall ac; |
763 | 0 | ac.callback = NULL; |
764 | 0 | ac.userdata = NULL; |
765 | 0 | ac.responseType = responseType; |
766 | 0 | ac.syncResponse = (UA_Response*)response; |
767 | 0 | ac.requestId = requestId; |
768 | 0 | ac.start = el->dateTime_nowMonotonic(el); /* Start timeout after sending */ |
769 | 0 | ac.timeout = rh->timeoutHint; |
770 | 0 | ac.requestHandle = rh->requestHandle; |
771 | 0 | if(ac.timeout == 0) |
772 | 0 | ac.timeout = UA_UINT32_MAX; /* 0 -> unlimited */ |
773 | |
|
774 | 0 | LIST_INSERT_HEAD(&client->asyncServiceCalls, &ac, pointers); |
775 | | |
776 | | /* Time until which the request has to be answered */ |
777 | 0 | UA_DateTime maxDate = ac.start + ((UA_DateTime)ac.timeout * UA_DATETIME_MSEC); |
778 | | |
779 | | /* Run the EventLoop until the request was processed, the request has timed |
780 | | * out or the client connection fails */ |
781 | 0 | UA_UInt32 timeout_remaining = ac.timeout; |
782 | 0 | while(true) { |
783 | | /* Unlock before dropping into the EventLoop. The client lock is |
784 | | * re-taken in the network callback if an event occurs. */ |
785 | 0 | retval = el->run(el, timeout_remaining); |
786 | | |
787 | | /* Was the response received? In that case we can directly return. The |
788 | | * ac was already removed from the internal linked list. */ |
789 | 0 | if(ac.syncResponse == NULL) |
790 | 0 | return; |
791 | | |
792 | | /* Check the status. Do not try to resend if the connection breaks. |
793 | | * Leave this to the application-level user. For example, we do not want |
794 | | * to call a method twice is the connection broke after sending the |
795 | | * request. */ |
796 | 0 | if(retval != UA_STATUSCODE_GOOD) |
797 | 0 | break; |
798 | | |
799 | | /* The connection was lost */ |
800 | 0 | retval = client->connectStatus; |
801 | 0 | if(retval != UA_STATUSCODE_GOOD) |
802 | 0 | break; |
803 | | |
804 | | /* The channel is no longer the same or was closed */ |
805 | 0 | if(channelId != client->channel.securityToken.channelId) { |
806 | 0 | retval = UA_STATUSCODE_BADSECURECHANNELCLOSED; |
807 | 0 | break; |
808 | 0 | } |
809 | | |
810 | | /* Update the remaining timeout or break */ |
811 | 0 | UA_DateTime now = ac.start = el->dateTime_nowMonotonic(el); |
812 | 0 | if(now > maxDate) { |
813 | 0 | retval = UA_STATUSCODE_BADTIMEOUT; |
814 | 0 | break; |
815 | 0 | } |
816 | 0 | timeout_remaining = (UA_UInt32)((maxDate - now) / UA_DATETIME_MSEC); |
817 | 0 | } |
818 | | |
819 | | /* Detach from the internal async service list */ |
820 | 0 | LIST_REMOVE(&ac, pointers); |
821 | | |
822 | | /* Return the status code */ |
823 | 0 | respHeader->serviceResult = retval; |
824 | 0 | } |
825 | | |
826 | | /***********************************/ |
827 | | /* Handling of Async Service Calls */ |
828 | | /***********************************/ |
829 | | |
830 | | static void |
831 | | __Client_AsyncService_cancel(UA_Client *client, AsyncServiceCall *ac, |
832 | 0 | UA_StatusCode statusCode) { |
833 | | /* Set the status for the synchronous service call. Don't free the ac. */ |
834 | 0 | if(ac->syncResponse) { |
835 | 0 | ac->syncResponse->responseHeader.serviceResult = statusCode; |
836 | 0 | ac->syncResponse = NULL; /* Indicate the async service call was processed */ |
837 | 0 | return; |
838 | 0 | } |
839 | | |
840 | 0 | if(ac->callback) { |
841 | | /* Create an empty response with the statuscode and call the callback */ |
842 | 0 | UA_Response response; |
843 | 0 | UA_init(&response, ac->responseType); |
844 | 0 | response.responseHeader.serviceResult = statusCode; |
845 | 0 | ac->callback(client, ac->userdata, ac->requestId, &response); |
846 | | |
847 | | /* Clean up the response. The user callback might move data into it. For |
848 | | * whatever reasons. */ |
849 | 0 | UA_clear(&response, ac->responseType); |
850 | 0 | } |
851 | |
|
852 | 0 | UA_free(ac); |
853 | 0 | } |
854 | | |
855 | | void |
856 | 120 | __Client_AsyncService_removeAll(UA_Client *client, UA_StatusCode statusCode) { |
857 | | /* Make this function reentrant. One of the async callbacks could indirectly |
858 | | * operate on the list. Moving all elements to a local list before iterating |
859 | | * that. */ |
860 | 120 | UA_AsyncServiceList asyncServiceCalls = client->asyncServiceCalls; |
861 | 120 | LIST_INIT(&client->asyncServiceCalls); |
862 | 120 | if(asyncServiceCalls.lh_first) |
863 | 0 | asyncServiceCalls.lh_first->pointers.le_prev = &asyncServiceCalls.lh_first; |
864 | | |
865 | | /* Cancel and remove the elements from the local list */ |
866 | 120 | AsyncServiceCall *ac, *ac_tmp; |
867 | 120 | LIST_FOREACH_SAFE(ac, &asyncServiceCalls, pointers, ac_tmp) { |
868 | 0 | LIST_REMOVE(ac, pointers); |
869 | 0 | __Client_AsyncService_cancel(client, ac, statusCode); |
870 | 0 | } |
871 | 120 | } |
872 | | |
873 | | UA_StatusCode |
874 | | __Client_AsyncService(UA_Client *client, const void *request, |
875 | | const UA_DataType *requestType, |
876 | | UA_ClientAsyncServiceCallback callback, |
877 | | const UA_DataType *responseType, |
878 | 0 | void *userdata, UA_UInt32 *requestId) { |
879 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
880 | | |
881 | | /* Is the SecureChannel connected? */ |
882 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_OPEN) { |
883 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
884 | 0 | "SecureChannel must be connected to send request"); |
885 | 0 | return UA_STATUSCODE_BADSERVERNOTCONNECTED; |
886 | 0 | } |
887 | | |
888 | | /* Prepare the entry for the linked list */ |
889 | 0 | AsyncServiceCall *ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall)); |
890 | 0 | if(!ac) |
891 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
892 | | |
893 | | /* Call the service and set the requestId */ |
894 | 0 | UA_StatusCode retval = sendRequest(client, request, requestType, &ac->requestId); |
895 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
896 | | /* If sending failed, the status is set to closing. The SecureChannel is |
897 | | * the actually closed in the next iteration of the EventLoop. */ |
898 | 0 | UA_assert(client->channel.state == UA_SECURECHANNELSTATE_CLOSING || |
899 | 0 | client->channel.state == UA_SECURECHANNELSTATE_CLOSED); |
900 | 0 | UA_free(ac); |
901 | 0 | notifyClientState(client); |
902 | 0 | return retval; |
903 | 0 | } |
904 | | |
905 | | /* Set up the AsyncServiceCall for processing the response */ |
906 | 0 | UA_EventLoop *el = client->config.eventLoop; |
907 | 0 | const UA_RequestHeader *rh = (const UA_RequestHeader*)request; |
908 | 0 | ac->callback = callback; |
909 | 0 | ac->responseType = responseType; |
910 | 0 | ac->userdata = userdata; |
911 | 0 | ac->syncResponse = NULL; |
912 | 0 | ac->start = el->dateTime_nowMonotonic(el); |
913 | 0 | ac->timeout = rh->timeoutHint; |
914 | 0 | ac->requestHandle = rh->requestHandle; |
915 | 0 | if(ac->timeout == 0) |
916 | 0 | ac->timeout = UA_UINT32_MAX; /* 0 -> unlimited */ |
917 | |
|
918 | 0 | LIST_INSERT_HEAD(&client->asyncServiceCalls, ac, pointers); |
919 | | |
920 | | /* Return the generated request id */ |
921 | 0 | if(requestId) |
922 | 0 | *requestId = ac->requestId; |
923 | | |
924 | | /* Notify the userland if a change happened */ |
925 | 0 | notifyClientState(client); |
926 | |
|
927 | 0 | return UA_STATUSCODE_GOOD; |
928 | 0 | } |
929 | | |
930 | | static UA_StatusCode |
931 | 0 | cancelByRequestHandle(UA_Client *client, UA_UInt32 requestHandle, UA_UInt32 *cancelCount) { |
932 | 0 | UA_CancelRequest creq; |
933 | 0 | UA_CancelRequest_init(&creq); |
934 | 0 | creq.requestHandle = requestHandle; |
935 | 0 | UA_CancelResponse cresp; |
936 | 0 | UA_CancelResponse_init(&cresp); |
937 | 0 | __Client_Service(client, &creq, &UA_TYPES[UA_TYPES_CANCELREQUEST], |
938 | 0 | &cresp, &UA_TYPES[UA_TYPES_CANCELRESPONSE]); |
939 | 0 | if(cancelCount) |
940 | 0 | *cancelCount = cresp.cancelCount; |
941 | 0 | UA_StatusCode res = cresp.responseHeader.serviceResult; |
942 | 0 | UA_CancelResponse_clear(&cresp); |
943 | 0 | return res; |
944 | 0 | } |
945 | | |
946 | | UA_StatusCode |
947 | | UA_Client_cancelByRequestHandle(UA_Client *client, UA_UInt32 requestHandle, |
948 | 0 | UA_UInt32 *cancelCount) { |
949 | 0 | lockClient(client); |
950 | 0 | UA_StatusCode res = cancelByRequestHandle(client, requestHandle, cancelCount); |
951 | 0 | unlockClient(client); |
952 | 0 | return res; |
953 | 0 | } |
954 | | |
955 | | UA_StatusCode |
956 | | UA_Client_cancelByRequestId(UA_Client *client, UA_UInt32 requestId, |
957 | 0 | UA_UInt32 *cancelCount) { |
958 | 0 | lockClient(client); |
959 | 0 | UA_StatusCode res = UA_STATUSCODE_BADNOTFOUND; |
960 | 0 | AsyncServiceCall *ac; |
961 | 0 | LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) { |
962 | 0 | if(ac->requestId != requestId) |
963 | 0 | continue; |
964 | 0 | res = cancelByRequestHandle(client, ac->requestHandle, cancelCount); |
965 | 0 | break; |
966 | 0 | } |
967 | 0 | unlockClient(client); |
968 | 0 | return res; |
969 | 0 | } |
970 | | |
971 | | /*******************/ |
972 | | /* Timed Callbacks */ |
973 | | /*******************/ |
974 | | |
975 | | UA_StatusCode |
976 | | UA_Client_addTimedCallback(UA_Client *client, UA_ClientCallback callback, |
977 | 0 | void *data, UA_DateTime date, UA_UInt64 *callbackId) { |
978 | 0 | if(!client->config.eventLoop) |
979 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
980 | 0 | lockClient(client); |
981 | 0 | UA_StatusCode res = client->config.eventLoop-> |
982 | 0 | addTimer(client->config.eventLoop, (UA_Callback)callback, |
983 | 0 | client, data, 0.0, &date, UA_TIMERPOLICY_ONCE, callbackId); |
984 | 0 | unlockClient(client); |
985 | 0 | return res; |
986 | 0 | } |
987 | | |
988 | | UA_StatusCode |
989 | | UA_Client_addRepeatedCallback(UA_Client *client, UA_ClientCallback callback, |
990 | 0 | void *data, UA_Double interval_ms, UA_UInt64 *callbackId) { |
991 | 0 | if(!client->config.eventLoop) |
992 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
993 | 0 | lockClient(client); |
994 | 0 | UA_StatusCode res = client->config.eventLoop-> |
995 | 0 | addTimer(client->config.eventLoop, (UA_Callback)callback, client, data, |
996 | 0 | interval_ms, NULL, UA_TIMERPOLICY_CURRENTTIME, callbackId); |
997 | 0 | unlockClient(client); |
998 | 0 | return res; |
999 | 0 | } |
1000 | | |
1001 | | UA_StatusCode |
1002 | | UA_Client_changeRepeatedCallbackInterval(UA_Client *client, UA_UInt64 callbackId, |
1003 | 0 | UA_Double interval_ms) { |
1004 | 0 | if(!client->config.eventLoop) |
1005 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1006 | 0 | lockClient(client); |
1007 | 0 | UA_StatusCode res = client->config.eventLoop-> |
1008 | 0 | modifyTimer(client->config.eventLoop, callbackId, interval_ms, |
1009 | 0 | NULL, UA_TIMERPOLICY_CURRENTTIME); |
1010 | 0 | unlockClient(client); |
1011 | 0 | return res; |
1012 | 0 | } |
1013 | | |
1014 | | void |
1015 | 40 | UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId) { |
1016 | 40 | if(!client->config.eventLoop) |
1017 | 0 | return; |
1018 | 40 | lockClient(client); |
1019 | 40 | client->config.eventLoop->removeTimer(client->config.eventLoop, callbackId); |
1020 | 40 | unlockClient(client); |
1021 | 40 | } |
1022 | | |
1023 | | /**********************/ |
1024 | | /* Housekeeping Tasks */ |
1025 | | /**********************/ |
1026 | | |
1027 | | static void |
1028 | 0 | asyncServiceTimeoutCheck(UA_Client *client) { |
1029 | | /* Make this function reentrant. One of the async callbacks could indirectly |
1030 | | * operate on the list. Moving all elements to a local list before iterating |
1031 | | * that. */ |
1032 | 0 | UA_EventLoop *el = client->config.eventLoop; |
1033 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
1034 | 0 | UA_AsyncServiceList asyncServiceCalls; |
1035 | 0 | AsyncServiceCall *ac, *ac_tmp; |
1036 | 0 | LIST_INIT(&asyncServiceCalls); |
1037 | 0 | LIST_FOREACH_SAFE(ac, &client->asyncServiceCalls, pointers, ac_tmp) { |
1038 | 0 | if(!ac->timeout) |
1039 | 0 | continue; |
1040 | 0 | if(ac->start + (UA_DateTime)(ac->timeout * UA_DATETIME_MSEC) <= now) { |
1041 | 0 | LIST_REMOVE(ac, pointers); |
1042 | 0 | LIST_INSERT_HEAD(&asyncServiceCalls, ac, pointers); |
1043 | 0 | } |
1044 | 0 | } |
1045 | | |
1046 | | /* Cancel and remove the elements from the local list */ |
1047 | 0 | LIST_FOREACH_SAFE(ac, &asyncServiceCalls, pointers, ac_tmp) { |
1048 | 0 | LIST_REMOVE(ac, pointers); |
1049 | | /* Reset the pointers to pacify clang-analyzer */ |
1050 | 0 | ac->pointers.le_next = NULL; |
1051 | 0 | ac->pointers.le_prev = NULL; |
1052 | 0 | __Client_AsyncService_cancel(client, ac, UA_STATUSCODE_BADTIMEOUT); |
1053 | 0 | } |
1054 | 0 | } |
1055 | | |
1056 | | static void |
1057 | | backgroundConnectivityCallback(UA_Client *client, void *userdata, |
1058 | 0 | UA_UInt32 requestId, const UA_ReadResponse *response) { |
1059 | 0 | lockClient(client); |
1060 | 0 | if(response->responseHeader.serviceResult == UA_STATUSCODE_BADTIMEOUT) { |
1061 | 0 | if(client->config.inactivityCallback) |
1062 | 0 | client->config.inactivityCallback(client); |
1063 | 0 | } |
1064 | 0 | UA_EventLoop *el = client->config.eventLoop; |
1065 | 0 | client->pendingConnectivityCheck = false; |
1066 | 0 | client->lastConnectivityCheck = el->dateTime_nowMonotonic(el); |
1067 | 0 | unlockClient(client); |
1068 | 0 | } |
1069 | | |
1070 | | static void |
1071 | 0 | __Client_backgroundConnectivity(UA_Client *client) { |
1072 | 0 | if(!client->config.connectivityCheckInterval) |
1073 | 0 | return; |
1074 | | |
1075 | | /* Only probe the server once the Session is activated. A Read sent |
1076 | | * before the server returns ActivateSessionResponse triggers a |
1077 | | * ServiceFault that aborts a slow connect (issue #8195). */ |
1078 | 0 | if(client->sessionState != UA_SESSIONSTATE_ACTIVATED) |
1079 | 0 | return; |
1080 | | |
1081 | 0 | if(client->pendingConnectivityCheck) |
1082 | 0 | return; |
1083 | | |
1084 | 0 | UA_EventLoop *el = client->config.eventLoop; |
1085 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
1086 | 0 | UA_DateTime nextDate = client->lastConnectivityCheck + |
1087 | 0 | (UA_DateTime)(client->config.connectivityCheckInterval * UA_DATETIME_MSEC); |
1088 | 0 | if(now <= nextDate) |
1089 | 0 | return; |
1090 | | |
1091 | | /* Prepare the request */ |
1092 | 0 | UA_ReadValueId rvid; |
1093 | 0 | UA_ReadValueId_init(&rvid); |
1094 | 0 | rvid.attributeId = UA_ATTRIBUTEID_VALUE; |
1095 | 0 | rvid.nodeId = UA_NS0ID(SERVER_SERVERSTATUS_STATE); |
1096 | 0 | UA_ReadRequest request; |
1097 | 0 | UA_ReadRequest_init(&request); |
1098 | 0 | request.nodesToRead = &rvid; |
1099 | 0 | request.nodesToReadSize = 1; |
1100 | 0 | UA_StatusCode retval = |
1101 | 0 | __Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_READREQUEST], |
1102 | 0 | (UA_ClientAsyncServiceCallback)backgroundConnectivityCallback, |
1103 | 0 | &UA_TYPES[UA_TYPES_READRESPONSE], NULL, NULL); |
1104 | 0 | if(retval == UA_STATUSCODE_GOOD) |
1105 | 0 | client->pendingConnectivityCheck = true; |
1106 | 0 | } |
1107 | | |
1108 | | /* Regular housekeeping activities in the client -- called via a cyclic callback */ |
1109 | | static void |
1110 | 0 | clientHouseKeeping(UA_Client *client, void *_) { |
1111 | 0 | lockClient(client); |
1112 | |
|
1113 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1114 | 0 | "Internally check the the client state and " |
1115 | 0 | "required activities"); |
1116 | | |
1117 | | /* Renew Secure Channel */ |
1118 | 0 | __Client_renewSecureChannel(client); |
1119 | | |
1120 | | /* Send read requests from time to time to test the connectivity */ |
1121 | 0 | __Client_backgroundConnectivity(client); |
1122 | |
|
1123 | 0 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
1124 | | /* Feed the server PublishRequests for the Subscriptions */ |
1125 | 0 | __Client_Subscriptions_backgroundPublish(client); |
1126 | | |
1127 | | /* Check for inactive Subscriptions */ |
1128 | 0 | __Client_Subscriptions_backgroundPublishInactivityCheck(client); |
1129 | 0 | #endif |
1130 | | |
1131 | | /* Did async services time out? Process callbacks with an error code */ |
1132 | 0 | asyncServiceTimeoutCheck(client); |
1133 | | |
1134 | | /* Log and notify user if the client state has changed */ |
1135 | 0 | notifyClientState(client); |
1136 | |
|
1137 | 0 | unlockClient(client); |
1138 | 0 | } |
1139 | | |
1140 | | UA_StatusCode |
1141 | 0 | __UA_Client_startup(UA_Client *client) { |
1142 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1143 | |
|
1144 | 0 | UA_ClientConfig *config = &client->config; |
1145 | 0 | UA_EventLoop *el = config->eventLoop; |
1146 | 0 | UA_CHECK_ERROR(el != NULL, |
1147 | 0 | return UA_STATUSCODE_BADINTERNALERROR, |
1148 | 0 | config->logging, UA_LOGCATEGORY_CLIENT, |
1149 | 0 | "No EventLoop configured"); |
1150 | | |
1151 | | /* Set up the repeated timer callback for checking the internal state. Like |
1152 | | * in the public API UA_Client_addRepeatedCallback, but without locking the |
1153 | | * mutex again */ |
1154 | 0 | UA_StatusCode rv = UA_STATUSCODE_GOOD; |
1155 | 0 | if(!client->houseKeepingCallbackId) { |
1156 | 0 | rv = el->addTimer(el, (UA_Callback)clientHouseKeeping, |
1157 | 0 | client, NULL, 1000.0, NULL, |
1158 | 0 | UA_TIMERPOLICY_CURRENTTIME, |
1159 | 0 | &client->houseKeepingCallbackId); |
1160 | 0 | UA_CHECK_STATUS(rv, return rv); |
1161 | 0 | } |
1162 | | |
1163 | | /* Start the EventLoop? */ |
1164 | 0 | if(el->state == UA_EVENTLOOPSTATE_FRESH) { |
1165 | 0 | rv = el->start(el); |
1166 | 0 | UA_CHECK_STATUS(rv, return rv); |
1167 | 0 | } |
1168 | | |
1169 | | /* Call the application notification callback */ |
1170 | 0 | if(config->lifecycleNotificationCallback) |
1171 | 0 | config->lifecycleNotificationCallback(client, UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STARTED, |
1172 | 0 | UA_KEYVALUEMAP_NULL); |
1173 | 0 | if(config->globalNotificationCallback) |
1174 | 0 | config->globalNotificationCallback(client, UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STARTED, |
1175 | 0 | UA_KEYVALUEMAP_NULL); |
1176 | |
|
1177 | 0 | return UA_STATUSCODE_GOOD; |
1178 | 0 | } |
1179 | | |
1180 | | UA_StatusCode |
1181 | 0 | UA_Client_run_iterate(UA_Client *client, UA_UInt32 timeout) { |
1182 | | /* Make sure the EventLoop has been started */ |
1183 | 0 | lockClient(client); |
1184 | 0 | UA_StatusCode rv = __UA_Client_startup(client); |
1185 | 0 | unlockClient(client); |
1186 | 0 | UA_CHECK_STATUS(rv, return rv); |
1187 | | |
1188 | | /* All timers and network events are triggered in the EventLoop. Release the |
1189 | | * client lock before. The callbacks from the EventLoop take the lock |
1190 | | * again. */ |
1191 | 0 | UA_EventLoop *el = client->config.eventLoop; |
1192 | 0 | rv = el->run(el, timeout); |
1193 | 0 | UA_CHECK_STATUS(rv, return rv); |
1194 | 0 | return client->connectStatus; |
1195 | 0 | } |
1196 | | |
1197 | | const UA_DataType * |
1198 | 0 | UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId) { |
1199 | 0 | return UA_findDataTypeWithCustom(typeId, client->config.customDataTypes); |
1200 | 0 | } |
1201 | | |
1202 | | /*************************/ |
1203 | | /* Connection Attributes */ |
1204 | | /*************************/ |
1205 | | |
1206 | | #define UA_CONNECTIONATTRIBUTESSIZE 3 |
1207 | | static const UA_QualifiedName connectionAttributes[UA_CONNECTIONATTRIBUTESSIZE] = { |
1208 | | {0, UA_STRING_STATIC("serverDescription")}, |
1209 | | {0, UA_STRING_STATIC("securityPolicyUri")}, |
1210 | | {0, UA_STRING_STATIC("securityMode")} |
1211 | | }; |
1212 | | |
1213 | | static UA_StatusCode |
1214 | | getConnectionttribute(UA_Client *client, const UA_QualifiedName key, |
1215 | 0 | UA_Variant *outValue, UA_Boolean copy) { |
1216 | 0 | if(!outValue) |
1217 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1218 | | |
1219 | 0 | UA_Variant localAttr; |
1220 | |
|
1221 | 0 | if(UA_QualifiedName_equal(&key, &connectionAttributes[0])) { |
1222 | | /* ServerDescription */ |
1223 | 0 | UA_Variant_setScalar(&localAttr, &client->endpoint.server, |
1224 | 0 | &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); |
1225 | 0 | } else if(UA_QualifiedName_equal(&key, &connectionAttributes[1])) { |
1226 | | /* SecurityPolicyUri */ |
1227 | 0 | const UA_SecurityPolicy *sp = client->channel.securityPolicy; |
1228 | 0 | if(!sp) |
1229 | 0 | return UA_STATUSCODE_BADNOTCONNECTED; |
1230 | 0 | UA_Variant_setScalar(&localAttr, (void*)(uintptr_t)&sp->policyUri, |
1231 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
1232 | 0 | } else if(UA_QualifiedName_equal(&key, &connectionAttributes[2])) { |
1233 | | /* SecurityMode */ |
1234 | 0 | UA_Variant_setScalar(&localAttr, &client->channel.securityMode, |
1235 | 0 | &UA_TYPES[UA_TYPES_MESSAGESECURITYMODE]); |
1236 | 0 | } else { |
1237 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1238 | 0 | } |
1239 | | |
1240 | 0 | if(copy) |
1241 | 0 | return UA_Variant_copy(&localAttr, outValue); |
1242 | | |
1243 | 0 | localAttr.storageType = UA_VARIANT_DATA_NODELETE; |
1244 | 0 | *outValue = localAttr; |
1245 | 0 | return UA_STATUSCODE_GOOD; |
1246 | 0 | } |
1247 | | |
1248 | | UA_StatusCode |
1249 | | UA_Client_getConnectionAttribute(UA_Client *client, const UA_QualifiedName key, |
1250 | 0 | UA_Variant *outValue) { |
1251 | 0 | lockClient(client); |
1252 | 0 | UA_StatusCode res = getConnectionttribute(client, key, outValue, false); |
1253 | 0 | unlockClient(client); |
1254 | 0 | return res; |
1255 | 0 | } |
1256 | | |
1257 | | UA_StatusCode |
1258 | | UA_Client_getConnectionAttributeCopy(UA_Client *client, const UA_QualifiedName key, |
1259 | 0 | UA_Variant *outValue) { |
1260 | 0 | lockClient(client); |
1261 | 0 | UA_StatusCode res = getConnectionttribute(client, key, outValue, true); |
1262 | 0 | unlockClient(client); |
1263 | 0 | return res; |
1264 | 0 | } |
1265 | | |
1266 | | UA_StatusCode |
1267 | | UA_Client_getConnectionAttribute_scalar(UA_Client *client, |
1268 | | const UA_QualifiedName key, |
1269 | | const UA_DataType *type, |
1270 | 0 | void *outValue) { |
1271 | 0 | lockClient(client); |
1272 | |
|
1273 | 0 | UA_Variant attr; |
1274 | 0 | UA_StatusCode res = getConnectionttribute(client, key, &attr, false); |
1275 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1276 | 0 | unlockClient(client); |
1277 | 0 | return res; |
1278 | 0 | } |
1279 | | |
1280 | 0 | if(!UA_Variant_hasScalarType(&attr, type)) { |
1281 | 0 | unlockClient(client); |
1282 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1283 | 0 | } |
1284 | | |
1285 | 0 | memcpy(outValue, attr.data, type->memSize); |
1286 | |
|
1287 | 0 | unlockClient(client); |
1288 | 0 | return UA_STATUSCODE_GOOD; |
1289 | 0 | } |
1290 | | |
1291 | | /* Namespace Mapping */ |
1292 | | |
1293 | | UA_StatusCode |
1294 | | UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index, |
1295 | 0 | UA_String *nsUri) { |
1296 | 0 | lockClient(client); |
1297 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
1298 | 0 | if(index < client->namespacesSize) |
1299 | 0 | res = UA_String_copy(&client->namespaces[index], nsUri); |
1300 | 0 | else |
1301 | 0 | res = UA_STATUSCODE_BADNOTFOUND; |
1302 | 0 | unlockClient(client); |
1303 | 0 | return res; |
1304 | 0 | } |
1305 | | |
1306 | | UA_StatusCode |
1307 | | UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri, |
1308 | 0 | UA_UInt16 *outIndex) { |
1309 | 0 | lockClient(client); |
1310 | 0 | for(size_t i = 0; i < client->namespacesSize; i++) { |
1311 | 0 | if(UA_String_equal(&nsUri, &client->namespaces[i])) { |
1312 | 0 | *outIndex = (UA_UInt16)i; |
1313 | 0 | unlockClient(client); |
1314 | 0 | return UA_STATUSCODE_GOOD; |
1315 | 0 | } |
1316 | 0 | } |
1317 | 0 | unlockClient(client); |
1318 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1319 | 0 | } |
1320 | | |
1321 | | UA_StatusCode |
1322 | | UA_Client_addNamespace(UA_Client *client, const UA_String nsUri, |
1323 | 0 | UA_UInt16 *outIndex) { |
1324 | 0 | UA_StatusCode res = UA_Client_getNamespaceIndex(client, nsUri, outIndex); |
1325 | 0 | if(res == UA_STATUSCODE_GOOD) |
1326 | 0 | return res; |
1327 | 0 | lockClient(client); |
1328 | 0 | res = UA_Array_appendCopy((void**)&client->namespaces, &client->namespacesSize, |
1329 | 0 | &nsUri, &UA_TYPES[UA_TYPES_STRING]); |
1330 | 0 | if(res == UA_STATUSCODE_GOOD) |
1331 | 0 | *outIndex = (UA_UInt16)(client->namespacesSize - 1); |
1332 | 0 | unlockClient(client); |
1333 | 0 | return res; |
1334 | 0 | } |
1335 | | |
1336 | 120 | void lockClient(UA_Client *client) { |
1337 | 120 | if(UA_LIKELY(client->config.eventLoop && client->config.eventLoop->lock)) |
1338 | 120 | client->config.eventLoop->lock(client->config.eventLoop); |
1339 | 120 | UA_LOCK(&client->clientMutex); |
1340 | 120 | } |
1341 | | |
1342 | 120 | void unlockClient(UA_Client *client) { |
1343 | 120 | if(UA_LIKELY(client->config.eventLoop && client->config.eventLoop->unlock)) |
1344 | 120 | client->config.eventLoop->unlock(client->config.eventLoop); |
1345 | 120 | UA_UNLOCK(&client->clientMutex); |
1346 | 120 | } |
1347 | | |
1348 | | /**********************/ |
1349 | | /* Connect Shorthands */ |
1350 | | /**********************/ |
1351 | | |
1352 | | UA_StatusCode |
1353 | | UA_ClientConfig_setAuthenticationUsername(UA_ClientConfig *config, |
1354 | | const char *username, |
1355 | 0 | const char *password) { |
1356 | 0 | UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new(); |
1357 | 0 | if(!identityToken) |
1358 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1359 | 0 | identityToken->userName = UA_STRING_ALLOC(username); |
1360 | 0 | identityToken->password = UA_BYTESTRING_ALLOC(password); |
1361 | 0 | UA_ExtensionObject_clear(&config->userIdentityToken); |
1362 | 0 | UA_ExtensionObject_setValue(&config->userIdentityToken, identityToken, |
1363 | 0 | &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]); |
1364 | 0 | return UA_STATUSCODE_GOOD; |
1365 | 0 | } |
1366 | | |
1367 | | UA_StatusCode |
1368 | 0 | UA_Client_connect(UA_Client *client, const char *endpointUrl) { |
1369 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
1370 | 0 | cc->noSession = false; |
1371 | 0 | return __UA_Client_connect(client, false, endpointUrl); |
1372 | 0 | } |
1373 | | |
1374 | | UA_StatusCode |
1375 | 0 | UA_Client_connectSecureChannel(UA_Client *client, const char *endpointUrl) { |
1376 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
1377 | 0 | cc->noSession = true; /* Don't open a Session */ |
1378 | 0 | return __UA_Client_connect(client, false, endpointUrl); |
1379 | 0 | } |
1380 | | |
1381 | | UA_StatusCode |
1382 | 0 | UA_Client_connectAsync(UA_Client *client, const char *endpointUrl) { |
1383 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
1384 | 0 | cc->noSession = false; /* Open a Session */ |
1385 | 0 | return __UA_Client_connect(client, true, endpointUrl); |
1386 | 0 | } |
1387 | | |
1388 | | UA_StatusCode |
1389 | 0 | UA_Client_connectSecureChannelAsync(UA_Client *client, const char *endpointUrl) { |
1390 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
1391 | 0 | cc->noSession = true; /* Don't open a Session */ |
1392 | 0 | return __UA_Client_connect(client, true, endpointUrl); |
1393 | 0 | } |
1394 | | |
1395 | | UA_StatusCode |
1396 | | UA_Client_connectUsername(UA_Client *client, const char *endpointUrl, |
1397 | 0 | const char *username, const char *password) { |
1398 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
1399 | 0 | UA_StatusCode res = UA_ClientConfig_setAuthenticationUsername(cc, username, password); |
1400 | 0 | if(res != UA_STATUSCODE_GOOD) |
1401 | 0 | return res; |
1402 | 0 | return UA_Client_connect(client, endpointUrl); |
1403 | 0 | } |
1404 | | |
1405 | | /**********************/ |
1406 | | /* Service Shorthands */ |
1407 | | /**********************/ |
1408 | | |
1409 | | void |
1410 | | __UA_Client_Service(UA_Client *client, const void *request, |
1411 | | const UA_DataType *requestType, void *response, |
1412 | 0 | const UA_DataType *responseType) { |
1413 | 0 | lockClient(client); |
1414 | 0 | __Client_Service(client, request, requestType, response, responseType); |
1415 | 0 | unlockClient(client); |
1416 | 0 | } |
1417 | | |
1418 | | UA_ReadResponse |
1419 | 0 | UA_Client_Service_read(UA_Client *client, const UA_ReadRequest request) { |
1420 | 0 | UA_ReadResponse response; |
1421 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_READREQUEST], |
1422 | 0 | &response, &UA_TYPES[UA_TYPES_READRESPONSE]); |
1423 | 0 | return response; |
1424 | 0 | } |
1425 | | |
1426 | | UA_WriteResponse |
1427 | 0 | UA_Client_Service_write(UA_Client *client, const UA_WriteRequest request) { |
1428 | 0 | UA_WriteResponse response; |
1429 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_WRITEREQUEST], |
1430 | 0 | &response, &UA_TYPES[UA_TYPES_WRITERESPONSE]); |
1431 | 0 | return response; |
1432 | 0 | } |
1433 | | |
1434 | | #ifdef UA_ENABLE_HISTORIZING |
1435 | | |
1436 | | UA_HistoryReadResponse |
1437 | | UA_Client_Service_historyRead(UA_Client *client, |
1438 | 0 | const UA_HistoryReadRequest request) { |
1439 | 0 | UA_HistoryReadResponse response; |
1440 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_HISTORYREADREQUEST], |
1441 | 0 | &response, &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE]); |
1442 | 0 | return response; |
1443 | 0 | } |
1444 | | |
1445 | | UA_HistoryUpdateResponse |
1446 | | UA_Client_Service_historyUpdate(UA_Client *client, |
1447 | 0 | const UA_HistoryUpdateRequest request) { |
1448 | 0 | UA_HistoryUpdateResponse response; |
1449 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_HISTORYUPDATEREQUEST], |
1450 | 0 | &response, &UA_TYPES[UA_TYPES_HISTORYUPDATERESPONSE]); |
1451 | 0 | return response; |
1452 | 0 | } |
1453 | | |
1454 | | #endif |
1455 | | |
1456 | | UA_CallResponse |
1457 | | UA_Client_Service_call(UA_Client *client, |
1458 | 0 | const UA_CallRequest request) { |
1459 | 0 | UA_CallResponse response; |
1460 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CALLREQUEST], |
1461 | 0 | &response, &UA_TYPES[UA_TYPES_CALLRESPONSE]); |
1462 | 0 | return response; |
1463 | 0 | } |
1464 | | |
1465 | | UA_AddNodesResponse |
1466 | | UA_Client_Service_addNodes(UA_Client *client, |
1467 | 0 | const UA_AddNodesRequest request) { |
1468 | 0 | UA_AddNodesResponse response; |
1469 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_ADDNODESREQUEST], |
1470 | 0 | &response, &UA_TYPES[UA_TYPES_ADDNODESRESPONSE]); |
1471 | 0 | return response; |
1472 | 0 | } |
1473 | | |
1474 | | UA_AddReferencesResponse |
1475 | | UA_Client_Service_addReferences(UA_Client *client, |
1476 | 0 | const UA_AddReferencesRequest request) { |
1477 | 0 | UA_AddReferencesResponse response; |
1478 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_ADDREFERENCESREQUEST], |
1479 | 0 | &response, &UA_TYPES[UA_TYPES_ADDREFERENCESRESPONSE]); |
1480 | 0 | return response; |
1481 | 0 | } |
1482 | | |
1483 | | UA_DeleteNodesResponse |
1484 | | UA_Client_Service_deleteNodes(UA_Client *client, |
1485 | 0 | const UA_DeleteNodesRequest request) { |
1486 | 0 | UA_DeleteNodesResponse response; |
1487 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETENODESREQUEST], |
1488 | 0 | &response, &UA_TYPES[UA_TYPES_DELETENODESRESPONSE]); |
1489 | 0 | return response; |
1490 | 0 | } |
1491 | | |
1492 | | UA_DeleteReferencesResponse |
1493 | | UA_Client_Service_deleteReferences(UA_Client *client, |
1494 | 0 | const UA_DeleteReferencesRequest request) { |
1495 | 0 | UA_DeleteReferencesResponse response; |
1496 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETEREFERENCESREQUEST], |
1497 | 0 | &response, &UA_TYPES[UA_TYPES_DELETEREFERENCESRESPONSE]); |
1498 | 0 | return response; |
1499 | 0 | } |
1500 | | |
1501 | | UA_BrowseResponse |
1502 | | UA_Client_Service_browse(UA_Client *client, |
1503 | 0 | const UA_BrowseRequest request) { |
1504 | 0 | UA_BrowseResponse response; |
1505 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_BROWSEREQUEST], |
1506 | 0 | &response, &UA_TYPES[UA_TYPES_BROWSERESPONSE]); |
1507 | 0 | return response; |
1508 | 0 | } |
1509 | | |
1510 | | UA_BrowseNextResponse |
1511 | | UA_Client_Service_browseNext(UA_Client *client, |
1512 | 0 | const UA_BrowseNextRequest request) { |
1513 | 0 | UA_BrowseNextResponse response; |
1514 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], |
1515 | 0 | &response, &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE]); |
1516 | 0 | return response; |
1517 | 0 | } |
1518 | | |
1519 | | UA_TranslateBrowsePathsToNodeIdsResponse |
1520 | | UA_Client_Service_translateBrowsePathsToNodeIds(UA_Client *client, |
1521 | 0 | const UA_TranslateBrowsePathsToNodeIdsRequest request) { |
1522 | 0 | UA_TranslateBrowsePathsToNodeIdsResponse response; |
1523 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST], |
1524 | 0 | &response, &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE]); |
1525 | 0 | return response; |
1526 | 0 | } |
1527 | | |
1528 | | UA_RegisterNodesResponse |
1529 | | UA_Client_Service_registerNodes(UA_Client *client, |
1530 | 0 | const UA_RegisterNodesRequest request) { |
1531 | 0 | UA_RegisterNodesResponse response; |
1532 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_REGISTERNODESREQUEST], |
1533 | 0 | &response, &UA_TYPES[UA_TYPES_REGISTERNODESRESPONSE]); |
1534 | 0 | return response; |
1535 | 0 | } |
1536 | | |
1537 | | UA_UnregisterNodesResponse |
1538 | | UA_Client_Service_unregisterNodes(UA_Client *client, |
1539 | 0 | const UA_UnregisterNodesRequest request) { |
1540 | 0 | UA_UnregisterNodesResponse response; |
1541 | 0 | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_UNREGISTERNODESREQUEST], |
1542 | 0 | &response, &UA_TYPES[UA_TYPES_UNREGISTERNODESRESPONSE]); |
1543 | 0 | return response; |
1544 | 0 | } |
1545 | | |
1546 | | #ifdef UA_ENABLE_QUERY |
1547 | | |
1548 | | UA_QueryFirstResponse |
1549 | | UA_Client_Service_queryFirst(UA_Client *client, |
1550 | | const UA_QueryFirstRequest request) { |
1551 | | UA_QueryFirstResponse response; |
1552 | | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_QUERYFIRSTREQUEST], |
1553 | | &response, &UA_TYPES[UA_TYPES_QUERYFIRSTRESPONSE]); |
1554 | | return response; |
1555 | | } |
1556 | | |
1557 | | UA_QueryNextResponse |
1558 | | UA_Client_Service_queryNext(UA_Client *client, const UA_QueryNextRequest request) { |
1559 | | UA_QueryNextResponse response; |
1560 | | __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_QUERYNEXTREQUEST], |
1561 | | &response, &UA_TYPES[UA_TYPES_QUERYNEXTRESPONSE]); |
1562 | | return response; |
1563 | | } |
1564 | | |
1565 | | #endif |
1566 | | |
1567 | | /********************/ |
1568 | | /* Async Shorthands */ |
1569 | | /********************/ |
1570 | | |
1571 | | UA_StatusCode |
1572 | | __UA_Client_AsyncService(UA_Client *client, const void *request, |
1573 | | const UA_DataType *requestType, |
1574 | | UA_ClientAsyncServiceCallback callback, |
1575 | | const UA_DataType *responseType, |
1576 | 0 | void *userdata, UA_UInt32 *requestId) { |
1577 | 0 | lockClient(client); |
1578 | 0 | UA_StatusCode res = |
1579 | 0 | __Client_AsyncService(client, request, requestType, callback, responseType, |
1580 | 0 | userdata, requestId); |
1581 | 0 | unlockClient(client); |
1582 | 0 | return res; |
1583 | 0 | } |
1584 | | |
1585 | | UA_StatusCode |
1586 | | UA_Client_sendAsyncReadRequest(UA_Client *client, UA_ReadRequest *request, |
1587 | | UA_ClientAsyncReadCallback readCallback, |
1588 | 0 | void *userdata, UA_UInt32 *reqId) { |
1589 | 0 | return __UA_Client_AsyncService(client, request, &UA_TYPES[UA_TYPES_READREQUEST], |
1590 | 0 | (UA_ClientAsyncServiceCallback)readCallback, |
1591 | 0 | &UA_TYPES[UA_TYPES_READRESPONSE], userdata, reqId); |
1592 | 0 | } |
1593 | | |
1594 | | UA_StatusCode |
1595 | | UA_Client_sendAsyncWriteRequest(UA_Client *client, UA_WriteRequest *request, |
1596 | | UA_ClientAsyncWriteCallback writeCallback, |
1597 | 0 | void *userdata, UA_UInt32 *reqId) { |
1598 | 0 | return __UA_Client_AsyncService(client, request, &UA_TYPES[UA_TYPES_WRITEREQUEST], |
1599 | 0 | (UA_ClientAsyncServiceCallback)writeCallback, |
1600 | 0 | &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata, reqId); |
1601 | 0 | } |
1602 | | |
1603 | | UA_StatusCode |
1604 | | UA_Client_sendAsyncBrowseRequest(UA_Client *client, UA_BrowseRequest *request, |
1605 | | UA_ClientAsyncBrowseCallback browseCallback, |
1606 | 0 | void *userdata, UA_UInt32 *reqId) { |
1607 | 0 | return __UA_Client_AsyncService(client, request, &UA_TYPES[UA_TYPES_BROWSEREQUEST], |
1608 | 0 | (UA_ClientAsyncServiceCallback)browseCallback, |
1609 | 0 | &UA_TYPES[UA_TYPES_BROWSERESPONSE], userdata, reqId); |
1610 | 0 | } |
1611 | | |
1612 | | UA_StatusCode |
1613 | | UA_Client_sendAsyncBrowseNextRequest(UA_Client *client, |
1614 | | UA_BrowseNextRequest *request, |
1615 | | UA_ClientAsyncBrowseNextCallback browseNextCallback, |
1616 | 0 | void *userdata, UA_UInt32 *reqId) { |
1617 | 0 | return __UA_Client_AsyncService(client, request, &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], |
1618 | 0 | (UA_ClientAsyncServiceCallback)browseNextCallback, |
1619 | 0 | &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE], userdata, reqId); |
1620 | 0 | } |