mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
1243 lines
46 KiB
C
1243 lines
46 KiB
C
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2015-2020 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
* Copyright 2015-2016 (c) Sten Grüner
|
|
* Copyright 2015-2016 (c) Chris Iatrou
|
|
* Copyright 2015 (c) hfaham
|
|
* Copyright 2015-2017 (c) Florian Palm
|
|
* Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA
|
|
* Copyright 2015 (c) Holger Jeromin
|
|
* Copyright 2015 (c) Oleksiy Vasylyev
|
|
* Copyright 2016 (c) TorbenD
|
|
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
|
|
* Copyright 2016 (c) Lykurg
|
|
* Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
|
|
* Copyright 2018 (c) Kalycito Infotech Private Limited
|
|
* Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart
|
|
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
|
|
* Copyright 2022 (c) Linutronix GmbH (Author: Muddasir Shakil)
|
|
*/
|
|
|
|
#include <open62541/transport_generated.h>
|
|
|
|
#include "ua_client_internal.h"
|
|
#include "../ua_types_encoding_binary.h"
|
|
|
|
static void
|
|
clientHouseKeeping(UA_Client *client, void *_);
|
|
|
|
/********************/
|
|
/* Client Lifecycle */
|
|
/********************/
|
|
|
|
UA_StatusCode
|
|
UA_ClientConfig_copy(UA_ClientConfig const *src, UA_ClientConfig *dst){
|
|
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
|
retval = UA_ApplicationDescription_copy(&src->clientDescription, &dst->clientDescription);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto cleanup;
|
|
|
|
retval = UA_ExtensionObject_copy(&src->userIdentityToken, &dst->userIdentityToken);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto cleanup;
|
|
|
|
retval = UA_String_copy(&src->securityPolicyUri, &dst->securityPolicyUri);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto cleanup;
|
|
|
|
retval = UA_EndpointDescription_copy(&src->endpoint, &dst->endpoint);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto cleanup;
|
|
|
|
retval = UA_UserTokenPolicy_copy(&src->userTokenPolicy, &dst->userTokenPolicy);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto cleanup;
|
|
|
|
retval = UA_Array_copy(src->sessionLocaleIds, src->sessionLocaleIdsSize,
|
|
(void **)&dst->sessionLocaleIds, &UA_TYPES[UA_TYPES_LOCALEID]);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto cleanup;
|
|
|
|
dst->sessionLocaleIdsSize = src->sessionLocaleIdsSize;
|
|
dst->connectivityCheckInterval = src->connectivityCheckInterval;
|
|
dst->certificateVerification = src->certificateVerification;
|
|
dst->clientContext = src->clientContext;
|
|
dst->customDataTypes = src->customDataTypes;
|
|
dst->eventLoop = src->eventLoop;
|
|
dst->externalEventLoop = src->externalEventLoop;
|
|
dst->inactivityCallback = src->inactivityCallback;
|
|
dst->localConnectionConfig = src->localConnectionConfig;
|
|
dst->logging = src->logging;
|
|
if(src->certificateVerification.logging == NULL)
|
|
dst->certificateVerification.logging = dst->logging;
|
|
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
|
dst->outStandingPublishRequests = src->outStandingPublishRequests;
|
|
#endif
|
|
dst->requestedSessionTimeout = src->requestedSessionTimeout;
|
|
dst->secureChannelLifeTime = src->secureChannelLifeTime;
|
|
dst->securityMode = src->securityMode;
|
|
dst->stateCallback = src->stateCallback;
|
|
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
|
dst->subscriptionInactivityCallback = src->subscriptionInactivityCallback;
|
|
#endif
|
|
dst->timeout = src->timeout;
|
|
dst->userTokenPolicy = src->userTokenPolicy;
|
|
dst->securityPolicies = src->securityPolicies;
|
|
dst->securityPoliciesSize = src->securityPoliciesSize;
|
|
dst->authSecurityPolicies = src->authSecurityPolicies;
|
|
dst->authSecurityPoliciesSize = src->authSecurityPoliciesSize;
|
|
|
|
cleanup:
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
/* _clear will free the plugins in dst that are a shallow copy from src. */
|
|
dst->authSecurityPolicies = NULL;
|
|
dst->certificateVerification.context = NULL;
|
|
dst->eventLoop = NULL;
|
|
dst->logging = NULL;
|
|
dst->securityPolicies = NULL;
|
|
UA_ClientConfig_clear(dst);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
UA_Client *
|
|
UA_Client_newWithConfig(const UA_ClientConfig *config) {
|
|
if(!config)
|
|
return NULL;
|
|
UA_Client *client = (UA_Client*)UA_malloc(sizeof(UA_Client));
|
|
if(!client)
|
|
return NULL;
|
|
memset(client, 0, sizeof(UA_Client));
|
|
client->config = *config;
|
|
|
|
UA_SecureChannel_init(&client->channel);
|
|
client->channel.config = client->config.localConnectionConfig;
|
|
client->connectStatus = UA_STATUSCODE_GOOD;
|
|
|
|
#if UA_MULTITHREADING >= 100
|
|
UA_LOCK_INIT(&client->clientMutex);
|
|
#endif
|
|
|
|
/* Initialize the namespace mapping */
|
|
size_t initialNs = 2 + config->namespacesSize;
|
|
client->namespaces = (UA_String*)UA_calloc(initialNs, sizeof(UA_String));
|
|
if(!client->namespaces)
|
|
goto error;
|
|
client->namespacesSize = initialNs;
|
|
client->namespaces[0] = UA_STRING_ALLOC("http://opcfoundation.org/UA/");
|
|
client->namespaces[1] = UA_STRING_NULL; /* Gets set when we connect to the server */
|
|
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
|
for(size_t i = 0; i < config->namespacesSize; i++) {
|
|
res |= UA_String_copy(&client->namespaces[i+2], &config->namespaces[i]);
|
|
}
|
|
if(res != UA_STATUSCODE_GOOD)
|
|
goto error;
|
|
|
|
return client;
|
|
|
|
error:
|
|
memset(&client->config, 0, sizeof(UA_ClientConfig));
|
|
UA_Client_delete(client);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
UA_ClientConfig_clear(UA_ClientConfig *config) {
|
|
UA_ApplicationDescription_clear(&config->clientDescription);
|
|
UA_String_clear(&config->endpointUrl);
|
|
UA_ExtensionObject_clear(&config->userIdentityToken);
|
|
|
|
/* Delete the SecurityPolicies for Authentication */
|
|
if(config->authSecurityPolicies != 0) {
|
|
for(size_t i = 0; i < config->authSecurityPoliciesSize; i++)
|
|
config->authSecurityPolicies[i].clear(&config->authSecurityPolicies[i]);
|
|
UA_free(config->authSecurityPolicies);
|
|
config->authSecurityPolicies = 0;
|
|
}
|
|
UA_String_clear(&config->securityPolicyUri);
|
|
UA_String_clear(&config->authSecurityPolicyUri);
|
|
|
|
UA_EndpointDescription_clear(&config->endpoint);
|
|
UA_UserTokenPolicy_clear(&config->userTokenPolicy);
|
|
|
|
UA_String_clear(&config->applicationUri);
|
|
|
|
if(config->certificateVerification.clear)
|
|
config->certificateVerification.clear(&config->certificateVerification);
|
|
|
|
/* Delete the SecurityPolicies */
|
|
if(config->securityPolicies != 0) {
|
|
for(size_t i = 0; i < config->securityPoliciesSize; i++)
|
|
config->securityPolicies[i].clear(&config->securityPolicies[i]);
|
|
UA_free(config->securityPolicies);
|
|
config->securityPolicies = 0;
|
|
}
|
|
|
|
/* Stop and delete the EventLoop */
|
|
UA_EventLoop *el = config->eventLoop;
|
|
if(el && !config->externalEventLoop) {
|
|
if(el->state != UA_EVENTLOOPSTATE_FRESH &&
|
|
el->state != UA_EVENTLOOPSTATE_STOPPED) {
|
|
el->stop(el);
|
|
while(el->state != UA_EVENTLOOPSTATE_STOPPED) {
|
|
el->run(el, 100);
|
|
}
|
|
}
|
|
el->free(el);
|
|
config->eventLoop = NULL;
|
|
}
|
|
|
|
/* Logging */
|
|
if(config->logging != NULL && config->logging->clear != NULL)
|
|
config->logging->clear(config->logging);
|
|
config->logging = NULL;
|
|
|
|
UA_String_clear(&config->sessionName);
|
|
if(config->sessionLocaleIdsSize > 0 && config->sessionLocaleIds) {
|
|
UA_Array_delete(config->sessionLocaleIds,
|
|
config->sessionLocaleIdsSize, &UA_TYPES[UA_TYPES_LOCALEID]);
|
|
}
|
|
config->sessionLocaleIds = NULL;
|
|
config->sessionLocaleIdsSize = 0;
|
|
|
|
/* Custom Data Types */
|
|
UA_cleanupDataTypeWithCustom(config->customDataTypes);
|
|
|
|
#ifdef UA_ENABLE_ENCRYPTION
|
|
config->privateKeyPasswordCallback = NULL;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
UA_ClientConfig_delete(UA_ClientConfig *config){
|
|
UA_assert(config != NULL);
|
|
UA_ClientConfig_clear(config);
|
|
UA_free(config);
|
|
}
|
|
|
|
static void
|
|
UA_Client_clear(UA_Client *client) {
|
|
/* Prevent new async service calls in UA_Client_AsyncService_removeAll */
|
|
UA_SessionState oldState = client->sessionState;
|
|
client->sessionState = UA_SESSIONSTATE_CLOSING;
|
|
|
|
/* Delete the async service calls with BADHSUTDOWN */
|
|
__Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSHUTDOWN);
|
|
|
|
/* Reset to the old state to properly close the session */
|
|
client->sessionState = oldState;
|
|
|
|
UA_Client_disconnect(client);
|
|
UA_String_clear(&client->discoveryUrl);
|
|
UA_EndpointDescription_clear(&client->endpoint);
|
|
|
|
UA_ByteString_clear(&client->serverSessionNonce);
|
|
UA_ByteString_clear(&client->clientSessionNonce);
|
|
|
|
/* Delete the subscriptions */
|
|
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
|
__Client_Subscriptions_clear(client);
|
|
#endif
|
|
|
|
/* Remove the internal regular callback */
|
|
UA_Client_removeCallback(client, client->houseKeepingCallbackId);
|
|
client->houseKeepingCallbackId = 0;
|
|
|
|
/* Clean up the SecureChannel */
|
|
UA_SecureChannel_clear(&client->channel);
|
|
|
|
/* Free the namespace mapping */
|
|
UA_Array_delete(client->namespaces, client->namespacesSize,
|
|
&UA_TYPES[UA_TYPES_STRING]);
|
|
client->namespaces = NULL;
|
|
client->namespacesSize = 0;
|
|
|
|
#if UA_MULTITHREADING >= 100
|
|
UA_LOCK_DESTROY(&client->clientMutex);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
UA_Client_delete(UA_Client* client) {
|
|
UA_Client_disconnect(client);
|
|
UA_Client_clear(client);
|
|
UA_ClientConfig_clear(&client->config);
|
|
UA_free(client);
|
|
}
|
|
|
|
void
|
|
UA_Client_getState(UA_Client *client, UA_SecureChannelState *channelState,
|
|
UA_SessionState *sessionState, UA_StatusCode *connectStatus) {
|
|
UA_LOCK(&client->clientMutex);
|
|
if(channelState)
|
|
*channelState = client->channel.state;
|
|
if(sessionState)
|
|
*sessionState = client->sessionState;
|
|
if(connectStatus)
|
|
*connectStatus = client->connectStatus;
|
|
UA_UNLOCK(&client->clientMutex);
|
|
}
|
|
|
|
UA_ClientConfig *
|
|
UA_Client_getConfig(UA_Client *client) {
|
|
if(!client)
|
|
return NULL;
|
|
return &client->config;
|
|
}
|
|
|
|
#if UA_LOGLEVEL <= 300
|
|
static const char *channelStateTexts[14] = {
|
|
"Fresh", "ReverseListening", "Connecting", "Connected", "ReverseConnected", "RHESent", "HELSent", "HELReceived", "ACKSent",
|
|
"AckReceived", "OPNSent", "Open", "Closing", "Closed"};
|
|
static const char *sessionStateTexts[6] =
|
|
{"Closed", "CreateRequested", "Created",
|
|
"ActivateRequested", "Activated", "Closing"};
|
|
#endif
|
|
|
|
void
|
|
notifyClientState(UA_Client *client) {
|
|
UA_LOCK_ASSERT(&client->clientMutex);
|
|
|
|
if(client->connectStatus == client->oldConnectStatus &&
|
|
client->channel.state == client->oldChannelState &&
|
|
client->sessionState == client->oldSessionState)
|
|
return;
|
|
|
|
#if UA_LOGLEVEL <= 300
|
|
UA_Boolean info = (client->connectStatus != UA_STATUSCODE_GOOD);
|
|
if(client->oldChannelState != client->channel.state)
|
|
info |= (client->channel.state == UA_SECURECHANNELSTATE_OPEN ||
|
|
client->channel.state == UA_SECURECHANNELSTATE_CLOSED);
|
|
if(client->oldSessionState != client->sessionState)
|
|
info |= (client->sessionState == UA_SESSIONSTATE_CREATED ||
|
|
client->sessionState == UA_SESSIONSTATE_ACTIVATED ||
|
|
client->sessionState == UA_SESSIONSTATE_CLOSED);
|
|
|
|
const char *channelStateText = channelStateTexts[client->channel.state];
|
|
const char *sessionStateText = sessionStateTexts[client->sessionState];
|
|
const char *connectStatusText = UA_StatusCode_name(client->connectStatus);
|
|
|
|
if(info)
|
|
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Client Status: ChannelState: %s, SessionState: %s, ConnectStatus: %s",
|
|
channelStateText, sessionStateText, connectStatusText);
|
|
else
|
|
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Client Status: ChannelState: %s, SessionState: %s, ConnectStatus: %s",
|
|
channelStateText, sessionStateText, connectStatusText);
|
|
#endif
|
|
|
|
client->oldConnectStatus = client->connectStatus;
|
|
client->oldChannelState = client->channel.state;
|
|
client->oldSessionState = client->sessionState;
|
|
|
|
UA_UNLOCK(&client->clientMutex);
|
|
if(client->config.stateCallback)
|
|
client->config.stateCallback(client, client->channel.state,
|
|
client->sessionState, client->connectStatus);
|
|
UA_LOCK(&client->clientMutex);
|
|
}
|
|
|
|
/****************/
|
|
/* Raw Services */
|
|
/****************/
|
|
|
|
/* For both synchronous and asynchronous service calls */
|
|
static UA_StatusCode
|
|
sendRequest(UA_Client *client, const void *request,
|
|
const UA_DataType *requestType, UA_UInt32 *requestId) {
|
|
UA_LOCK_ASSERT(&client->clientMutex);
|
|
|
|
/* Renew SecureChannel if necessary */
|
|
__Client_renewSecureChannel(client);
|
|
if(client->connectStatus != UA_STATUSCODE_GOOD)
|
|
return client->connectStatus;
|
|
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
|
|
/* Adjusting the request header. The const attribute is violated, but we
|
|
* reset to the original state before returning. Use the AuthenticationToken
|
|
* only once the session is active (or to activate / close it). */
|
|
UA_RequestHeader *rr = (UA_RequestHeader*)(uintptr_t)request;
|
|
UA_NodeId oldToken = rr->authenticationToken; /* Put back in place later */
|
|
|
|
if(client->sessionState == UA_SESSIONSTATE_ACTIVATED ||
|
|
requestType == &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST] ||
|
|
requestType == &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST])
|
|
rr->authenticationToken = client->authenticationToken;
|
|
rr->timestamp = el->dateTime_now(el);
|
|
|
|
/* Create a unique handle >100,000 if not manually defined. The handle is
|
|
* not necessarily unique when manually defined and used to cancel async
|
|
* service requests. */
|
|
if(rr->requestHandle == 0) {
|
|
if(UA_UNLIKELY(client->requestHandle < 100000))
|
|
client->requestHandle = 100000;
|
|
rr->requestHandle = ++client->requestHandle;
|
|
}
|
|
|
|
/* Set the timeout hint if not manually defined */
|
|
if(rr->timeoutHint == 0)
|
|
rr->timeoutHint = client->config.timeout;
|
|
|
|
/* Generate the request id */
|
|
UA_UInt32 rqId = ++client->requestId;
|
|
|
|
#ifdef UA_ENABLE_TYPEDESCRIPTION
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel,
|
|
"Sending request with RequestId %u of type %s",
|
|
(unsigned)rqId, requestType->typeName);
|
|
#else
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel,
|
|
"Sending request with RequestId %u of type %" PRIu32,
|
|
(unsigned)rqId, requestType->binaryEncodingId.identifier.numeric);
|
|
#endif
|
|
|
|
/* Send the message */
|
|
UA_StatusCode retval =
|
|
UA_SecureChannel_sendSymmetricMessage(&client->channel, rqId,
|
|
UA_MESSAGETYPE_MSG, rr, requestType);
|
|
|
|
rr->authenticationToken = oldToken; /* Set back to the original token */
|
|
|
|
/* Sending failed. The SecureChannel cannot recover from that. Call
|
|
* closeSecureChannel to a) close from our end and b) set the session to
|
|
* non-activated. */
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
closeSecureChannel(client);
|
|
|
|
/* Return the request id */
|
|
*requestId = rqId;
|
|
return retval;
|
|
}
|
|
|
|
static const UA_NodeId
|
|
serviceFaultId = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SERVICEFAULT_ENCODING_DEFAULTBINARY}};
|
|
|
|
/* Look for the async callback in the linked list, execute and delete it */
|
|
static UA_StatusCode
|
|
processMSGResponse(UA_Client *client, UA_UInt32 requestId,
|
|
const UA_ByteString *msg) {
|
|
/* Find the callback */
|
|
AsyncServiceCall *ac;
|
|
LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) {
|
|
if(ac->requestId == requestId)
|
|
break;
|
|
}
|
|
|
|
/* Part 6, 6.7.6: After the security validation is complete the receiver
|
|
* shall verify the RequestId and the SequenceNumber. If these checks fail a
|
|
* Bad_SecurityChecksFailed error is reported. The RequestId only needs to
|
|
* be verified by the Client since only the Client knows if it is valid or
|
|
* not.*/
|
|
if(!ac) {
|
|
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Request with unknown RequestId %u", requestId);
|
|
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
|
}
|
|
|
|
UA_Response asyncResponse;
|
|
UA_Response *response = (ac->syncResponse) ? ac->syncResponse : &asyncResponse;
|
|
const UA_DataType *responseType = ac->responseType;
|
|
|
|
/* Dequeue ac. We might disconnect the client (remove all ac) in the callback. */
|
|
LIST_REMOVE(ac, pointers);
|
|
|
|
/* Decode the response type */
|
|
size_t offset = 0;
|
|
UA_NodeId responseTypeId;
|
|
UA_StatusCode retval = UA_NodeId_decodeBinary(msg, &offset, &responseTypeId);
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
goto process;
|
|
|
|
/* Verify the type of the response */
|
|
if(!UA_NodeId_equal(&responseTypeId, &ac->responseType->binaryEncodingId)) {
|
|
/* Initialize before switching the responseType to ServiceFault.
|
|
* Otherwise the decoding will leave fields from the original response
|
|
* type uninitialized. */
|
|
UA_init(response, ac->responseType);
|
|
if(UA_NodeId_equal(&responseTypeId, &serviceFaultId)) {
|
|
/* Decode as a ServiceFault, i.e. only the response header */
|
|
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Received a ServiceFault response");
|
|
responseType = &UA_TYPES[UA_TYPES_SERVICEFAULT];
|
|
} else {
|
|
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Service response type does not match");
|
|
retval = UA_STATUSCODE_BADCOMMUNICATIONERROR;
|
|
goto process; /* Do not decode */
|
|
}
|
|
}
|
|
|
|
/* Decode the response */
|
|
#ifdef UA_ENABLE_TYPEDESCRIPTION
|
|
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Decode a message of type %s", responseType->typeName);
|
|
#else
|
|
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Decode a message of type %" PRIu32,
|
|
responseTypeId.identifier.numeric);
|
|
#endif
|
|
|
|
UA_DecodeBinaryOptions opt;
|
|
memset(&opt, 0, sizeof(UA_DecodeBinaryOptions));
|
|
opt.customTypes = client->config.customDataTypes;
|
|
retval = UA_decodeBinaryInternal(msg, &offset, response, responseType, &opt);
|
|
|
|
process:
|
|
/* Process the received MSG response */
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Could not decode the response with RequestId %u with status %s",
|
|
(unsigned)requestId, UA_StatusCode_name(retval));
|
|
response->responseHeader.serviceResult = retval;
|
|
}
|
|
|
|
/* The Session closed. The current response is processed with the return code.
|
|
* The next request first recreates a session. */
|
|
if(responseType != &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE] &&
|
|
(response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONIDINVALID ||
|
|
response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONCLOSED)) {
|
|
/* Clean up the session information and reset the state */
|
|
cleanupSession(client);
|
|
|
|
if(client->config.noNewSession) {
|
|
/* Configuration option to not create a new Session. Disconnect the
|
|
* client. */
|
|
client->connectStatus = response->responseHeader.serviceResult;
|
|
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Session cannot be activated with StatusCode %s. "
|
|
"The client is configured not to create a new Session.",
|
|
UA_StatusCode_name(client->connectStatus));
|
|
closeSecureChannel(client);
|
|
} else {
|
|
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Session no longer valid. A new Session is created for the next "
|
|
"Service request but we do not re-send the current request.");
|
|
}
|
|
}
|
|
|
|
/* Call the async callback. This is the only thread with access to ac. So we
|
|
* can just unlock for the callback into userland. */
|
|
UA_UNLOCK(&client->clientMutex);
|
|
if(ac->callback)
|
|
ac->callback(client, ac->userdata, requestId, response);
|
|
UA_LOCK(&client->clientMutex);
|
|
|
|
/* Clean up */
|
|
UA_NodeId_clear(&responseTypeId);
|
|
if(!ac->syncResponse) {
|
|
UA_clear(response, ac->responseType);
|
|
UA_free(ac);
|
|
} else {
|
|
/* Return a special status code after processing a synchronous message.
|
|
* This makes the client return control immediately. */
|
|
ac->syncResponse = NULL; /* Indicate that response was received */
|
|
if(retval == UA_STATUSCODE_GOOD)
|
|
retval = UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
UA_StatusCode
|
|
processServiceResponse(UA_Client *client, UA_SecureChannel *channel,
|
|
UA_MessageType messageType, UA_UInt32 requestId,
|
|
UA_ByteString *message) {
|
|
if(!UA_SecureChannel_isConnected(channel)) {
|
|
if(messageType == UA_MESSAGETYPE_MSG) {
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard MSG message "
|
|
"with RequestId %u as the SecureChannel is not connected",
|
|
requestId);
|
|
} else {
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard message "
|
|
"as the SecureChannel is not connected");
|
|
}
|
|
return UA_STATUSCODE_BADCONNECTIONCLOSED;
|
|
}
|
|
|
|
switch(messageType) {
|
|
case UA_MESSAGETYPE_RHE:
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process RHE message");
|
|
processRHEMessage(client, message);
|
|
return UA_STATUSCODE_GOOD;
|
|
case UA_MESSAGETYPE_ACK:
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process ACK message");
|
|
processACKResponse(client, message);
|
|
return UA_STATUSCODE_GOOD;
|
|
case UA_MESSAGETYPE_OPN:
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process OPN message");
|
|
processOPNResponse(client, message);
|
|
return UA_STATUSCODE_GOOD;
|
|
case UA_MESSAGETYPE_ERR:
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process ERR message");
|
|
processERRResponse(client, message);
|
|
return UA_STATUSCODE_GOOD;
|
|
case UA_MESSAGETYPE_MSG:
|
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Process MSG message "
|
|
"with RequestId %u", requestId);
|
|
return processMSGResponse(client, requestId, message);
|
|
default:
|
|
UA_LOG_TRACE_CHANNEL(client->config.logging, channel,
|
|
"Invalid message type");
|
|
channel->state = UA_SECURECHANNELSTATE_CLOSING;
|
|
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
|
|
}
|
|
}
|
|
|
|
void
|
|
__Client_Service(UA_Client *client, const void *request,
|
|
const UA_DataType *requestType, void *response,
|
|
const UA_DataType *responseType) {
|
|
UA_ResponseHeader *respHeader = (UA_ResponseHeader*)response;
|
|
|
|
/* Initialize. Response is valied in case of aborting. */
|
|
UA_init(response, responseType);
|
|
|
|
/* Verify that the EventLoop is running */
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
if(!el || el->state != UA_EVENTLOOPSTATE_STARTED) {
|
|
respHeader->serviceResult = UA_STATUSCODE_BADINTERNALERROR;
|
|
return;
|
|
}
|
|
|
|
/* Check that the SecureChannel is open and also a Session active (if we
|
|
* want a Session). Otherwise reopen. */
|
|
if(!isFullyConnected(client)) {
|
|
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Re-establish the connection for the synchronous service call");
|
|
connectSync(client);
|
|
if(client->connectStatus != UA_STATUSCODE_GOOD) {
|
|
respHeader->serviceResult = client->connectStatus;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Store the channelId to detect if the channel was changed by a
|
|
* reconnection within the EventLoop run method. */
|
|
UA_UInt32 channelId = client->channel.securityToken.channelId;
|
|
|
|
/* Send the request */
|
|
UA_UInt32 requestId = 0;
|
|
UA_StatusCode retval = sendRequest(client, request, requestType, &requestId);
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
/* If sending failed, the status is set to closing. The SecureChannel is
|
|
* the actually closed in the next iteration of the EventLoop. */
|
|
UA_assert(client->channel.state == UA_SECURECHANNELSTATE_CLOSING ||
|
|
client->channel.state == UA_SECURECHANNELSTATE_CLOSED);
|
|
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Sending the request failed with status %s",
|
|
UA_StatusCode_name(retval));
|
|
notifyClientState(client);
|
|
respHeader->serviceResult = retval;
|
|
return;
|
|
}
|
|
|
|
/* Temporarily insert an AsyncServiceCall */
|
|
const UA_RequestHeader *rh = (const UA_RequestHeader*)request;
|
|
AsyncServiceCall ac;
|
|
ac.callback = NULL;
|
|
ac.userdata = NULL;
|
|
ac.responseType = responseType;
|
|
ac.syncResponse = (UA_Response*)response;
|
|
ac.requestId = requestId;
|
|
ac.start = el->dateTime_nowMonotonic(el); /* Start timeout after sending */
|
|
ac.timeout = rh->timeoutHint;
|
|
ac.requestHandle = rh->requestHandle;
|
|
if(ac.timeout == 0)
|
|
ac.timeout = UA_UINT32_MAX; /* 0 -> unlimited */
|
|
|
|
LIST_INSERT_HEAD(&client->asyncServiceCalls, &ac, pointers);
|
|
|
|
/* Time until which the request has to be answered */
|
|
UA_DateTime maxDate = ac.start + ((UA_DateTime)ac.timeout * UA_DATETIME_MSEC);
|
|
|
|
/* Run the EventLoop until the request was processed, the request has timed
|
|
* out or the client connection fails */
|
|
UA_UInt32 timeout_remaining = ac.timeout;
|
|
while(true) {
|
|
/* Unlock before dropping into the EventLoop. The client lock is
|
|
* re-taken in the network callback if an event occurs. */
|
|
UA_UNLOCK(&client->clientMutex);
|
|
retval = el->run(el, timeout_remaining);
|
|
UA_LOCK(&client->clientMutex);
|
|
|
|
/* Was the response received? In that case we can directly return. The
|
|
* ac was already removed from the internal linked list. */
|
|
if(ac.syncResponse == NULL)
|
|
return;
|
|
|
|
/* Check the status. Do not try to resend if the connection breaks.
|
|
* Leave this to the application-level user. For example, we do not want
|
|
* to call a method twice is the connection broke after sending the
|
|
* request. */
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
break;
|
|
|
|
/* The connection was lost */
|
|
retval = client->connectStatus;
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
break;
|
|
|
|
/* The channel is no longer the same or was closed */
|
|
if(channelId != client->channel.securityToken.channelId) {
|
|
retval = UA_STATUSCODE_BADSECURECHANNELCLOSED;
|
|
break;
|
|
}
|
|
|
|
/* Update the remaining timeout or break */
|
|
UA_DateTime now = ac.start = el->dateTime_nowMonotonic(el);
|
|
if(now > maxDate) {
|
|
retval = UA_STATUSCODE_BADTIMEOUT;
|
|
break;
|
|
}
|
|
timeout_remaining = (UA_UInt32)((maxDate - now) / UA_DATETIME_MSEC);
|
|
}
|
|
|
|
/* Detach from the internal async service list */
|
|
LIST_REMOVE(&ac, pointers);
|
|
|
|
/* Return the status code */
|
|
respHeader->serviceResult = retval;
|
|
}
|
|
|
|
void
|
|
__UA_Client_Service(UA_Client *client, const void *request,
|
|
const UA_DataType *requestType, void *response,
|
|
const UA_DataType *responseType) {
|
|
UA_LOCK(&client->clientMutex);
|
|
__Client_Service(client, request, requestType, response, responseType);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
}
|
|
|
|
/***********************************/
|
|
/* Handling of Async Service Calls */
|
|
/***********************************/
|
|
|
|
static void
|
|
__Client_AsyncService_cancel(UA_Client *client, AsyncServiceCall *ac,
|
|
UA_StatusCode statusCode) {
|
|
/* Set the status for the synchronous service call. Don't free the ac. */
|
|
if(ac->syncResponse) {
|
|
ac->syncResponse->responseHeader.serviceResult = statusCode;
|
|
ac->syncResponse = NULL; /* Indicate the async service call was processed */
|
|
return;
|
|
}
|
|
|
|
if(ac->callback) {
|
|
/* Create an empty response with the statuscode and call the callback */
|
|
UA_Response response;
|
|
UA_init(&response, ac->responseType);
|
|
response.responseHeader.serviceResult = statusCode;
|
|
UA_UNLOCK(&client->clientMutex);
|
|
ac->callback(client, ac->userdata, ac->requestId, &response);
|
|
UA_LOCK(&client->clientMutex);
|
|
|
|
/* Clean up the response. The user callback might move data into it. For
|
|
* whatever reasons. */
|
|
UA_clear(&response, ac->responseType);
|
|
}
|
|
|
|
UA_free(ac);
|
|
}
|
|
|
|
void
|
|
__Client_AsyncService_removeAll(UA_Client *client, UA_StatusCode statusCode) {
|
|
/* Make this function reentrant. One of the async callbacks could indirectly
|
|
* operate on the list. Moving all elements to a local list before iterating
|
|
* that. */
|
|
UA_AsyncServiceList asyncServiceCalls = client->asyncServiceCalls;
|
|
LIST_INIT(&client->asyncServiceCalls);
|
|
if(asyncServiceCalls.lh_first)
|
|
asyncServiceCalls.lh_first->pointers.le_prev = &asyncServiceCalls.lh_first;
|
|
|
|
/* Cancel and remove the elements from the local list */
|
|
AsyncServiceCall *ac, *ac_tmp;
|
|
LIST_FOREACH_SAFE(ac, &asyncServiceCalls, pointers, ac_tmp) {
|
|
LIST_REMOVE(ac, pointers);
|
|
__Client_AsyncService_cancel(client, ac, statusCode);
|
|
}
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_modifyAsyncCallback(UA_Client *client, UA_UInt32 requestId,
|
|
void *userdata, UA_ClientAsyncServiceCallback callback) {
|
|
UA_LOCK(&client->clientMutex);
|
|
AsyncServiceCall *ac;
|
|
UA_StatusCode res = UA_STATUSCODE_BADNOTFOUND;
|
|
LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) {
|
|
if(ac->requestId == requestId) {
|
|
ac->callback = callback;
|
|
ac->userdata = userdata;
|
|
res = UA_STATUSCODE_GOOD;
|
|
break;
|
|
}
|
|
}
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
__Client_AsyncService(UA_Client *client, const void *request,
|
|
const UA_DataType *requestType,
|
|
UA_ClientAsyncServiceCallback callback,
|
|
const UA_DataType *responseType,
|
|
void *userdata, UA_UInt32 *requestId) {
|
|
UA_LOCK_ASSERT(&client->clientMutex);
|
|
|
|
/* Is the SecureChannel connected? */
|
|
if(client->channel.state != UA_SECURECHANNELSTATE_OPEN) {
|
|
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"SecureChannel must be connected to send request");
|
|
return UA_STATUSCODE_BADSERVERNOTCONNECTED;
|
|
}
|
|
|
|
/* Prepare the entry for the linked list */
|
|
AsyncServiceCall *ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall));
|
|
if(!ac)
|
|
return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
|
/* Call the service and set the requestId */
|
|
UA_StatusCode retval = sendRequest(client, request, requestType, &ac->requestId);
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
/* If sending failed, the status is set to closing. The SecureChannel is
|
|
* the actually closed in the next iteration of the EventLoop. */
|
|
UA_assert(client->channel.state == UA_SECURECHANNELSTATE_CLOSING ||
|
|
client->channel.state == UA_SECURECHANNELSTATE_CLOSED);
|
|
UA_free(ac);
|
|
notifyClientState(client);
|
|
return retval;
|
|
}
|
|
|
|
/* Set up the AsyncServiceCall for processing the response */
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
const UA_RequestHeader *rh = (const UA_RequestHeader*)request;
|
|
ac->callback = callback;
|
|
ac->responseType = responseType;
|
|
ac->userdata = userdata;
|
|
ac->syncResponse = NULL;
|
|
ac->start = el->dateTime_nowMonotonic(el);
|
|
ac->timeout = rh->timeoutHint;
|
|
ac->requestHandle = rh->requestHandle;
|
|
if(ac->timeout == 0)
|
|
ac->timeout = UA_UINT32_MAX; /* 0 -> unlimited */
|
|
|
|
LIST_INSERT_HEAD(&client->asyncServiceCalls, ac, pointers);
|
|
|
|
/* Return the generated request id */
|
|
if(requestId)
|
|
*requestId = ac->requestId;
|
|
|
|
/* Notify the userland if a change happened */
|
|
notifyClientState(client);
|
|
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
UA_StatusCode
|
|
__UA_Client_AsyncService(UA_Client *client, const void *request,
|
|
const UA_DataType *requestType,
|
|
UA_ClientAsyncServiceCallback callback,
|
|
const UA_DataType *responseType,
|
|
void *userdata, UA_UInt32 *requestId) {
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res =
|
|
__Client_AsyncService(client, request, requestType, callback, responseType,
|
|
userdata, requestId);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
cancelByRequestHandle(UA_Client *client, UA_UInt32 requestHandle, UA_UInt32 *cancelCount) {
|
|
UA_CancelRequest creq;
|
|
UA_CancelRequest_init(&creq);
|
|
creq.requestHandle = requestHandle;
|
|
UA_CancelResponse cresp;
|
|
UA_CancelResponse_init(&cresp);
|
|
__Client_Service(client, &creq, &UA_TYPES[UA_TYPES_CANCELREQUEST],
|
|
&cresp, &UA_TYPES[UA_TYPES_CANCELRESPONSE]);
|
|
if(cancelCount)
|
|
*cancelCount = cresp.cancelCount;
|
|
UA_StatusCode res = cresp.responseHeader.serviceResult;
|
|
UA_CancelResponse_clear(&cresp);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_cancelByRequestHandle(UA_Client *client, UA_UInt32 requestHandle,
|
|
UA_UInt32 *cancelCount) {
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = cancelByRequestHandle(client, requestHandle, cancelCount);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_cancelByRequestId(UA_Client *client, UA_UInt32 requestId,
|
|
UA_UInt32 *cancelCount) {
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = UA_STATUSCODE_BADNOTFOUND;
|
|
AsyncServiceCall *ac;
|
|
LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) {
|
|
if(ac->requestId != requestId)
|
|
continue;
|
|
res = cancelByRequestHandle(client, ac->requestHandle, cancelCount);
|
|
break;
|
|
}
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
/*******************/
|
|
/* Timed Callbacks */
|
|
/*******************/
|
|
|
|
UA_StatusCode
|
|
UA_Client_addTimedCallback(UA_Client *client, UA_ClientCallback callback,
|
|
void *data, UA_DateTime date, UA_UInt64 *callbackId) {
|
|
if(!client->config.eventLoop)
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = client->config.eventLoop->
|
|
addTimer(client->config.eventLoop, (UA_Callback)callback,
|
|
client, data, 0.0, &date, UA_TIMERPOLICY_ONCE, callbackId);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_addRepeatedCallback(UA_Client *client, UA_ClientCallback callback,
|
|
void *data, UA_Double interval_ms, UA_UInt64 *callbackId) {
|
|
if(!client->config.eventLoop)
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = client->config.eventLoop->
|
|
addTimer(client->config.eventLoop, (UA_Callback)callback, client, data,
|
|
interval_ms, NULL, UA_TIMERPOLICY_CURRENTTIME, callbackId);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_changeRepeatedCallbackInterval(UA_Client *client, UA_UInt64 callbackId,
|
|
UA_Double interval_ms) {
|
|
if(!client->config.eventLoop)
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = client->config.eventLoop->
|
|
modifyTimer(client->config.eventLoop, callbackId, interval_ms,
|
|
NULL, UA_TIMERPOLICY_CURRENTTIME);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
void
|
|
UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId) {
|
|
if(!client->config.eventLoop)
|
|
return;
|
|
UA_LOCK(&client->clientMutex);
|
|
client->config.eventLoop->removeTimer(client->config.eventLoop, callbackId);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
}
|
|
|
|
/**********************/
|
|
/* Housekeeping Tasks */
|
|
/**********************/
|
|
|
|
static void
|
|
asyncServiceTimeoutCheck(UA_Client *client) {
|
|
/* Make this function reentrant. One of the async callbacks could indirectly
|
|
* operate on the list. Moving all elements to a local list before iterating
|
|
* that. */
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
UA_DateTime now = el->dateTime_nowMonotonic(el);
|
|
UA_AsyncServiceList asyncServiceCalls;
|
|
AsyncServiceCall *ac, *ac_tmp;
|
|
LIST_INIT(&asyncServiceCalls);
|
|
LIST_FOREACH_SAFE(ac, &client->asyncServiceCalls, pointers, ac_tmp) {
|
|
if(!ac->timeout)
|
|
continue;
|
|
if(ac->start + (UA_DateTime)(ac->timeout * UA_DATETIME_MSEC) <= now) {
|
|
LIST_REMOVE(ac, pointers);
|
|
LIST_INSERT_HEAD(&asyncServiceCalls, ac, pointers);
|
|
}
|
|
}
|
|
|
|
/* Cancel and remove the elements from the local list */
|
|
LIST_FOREACH_SAFE(ac, &asyncServiceCalls, pointers, ac_tmp) {
|
|
LIST_REMOVE(ac, pointers);
|
|
__Client_AsyncService_cancel(client, ac, UA_STATUSCODE_BADTIMEOUT);
|
|
}
|
|
}
|
|
|
|
static void
|
|
backgroundConnectivityCallback(UA_Client *client, void *userdata,
|
|
UA_UInt32 requestId, const UA_ReadResponse *response) {
|
|
UA_LOCK(&client->clientMutex);
|
|
if(response->responseHeader.serviceResult == UA_STATUSCODE_BADTIMEOUT) {
|
|
if(client->config.inactivityCallback) {
|
|
UA_UNLOCK(&client->clientMutex);
|
|
client->config.inactivityCallback(client);
|
|
UA_LOCK(&client->clientMutex);
|
|
}
|
|
}
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
client->pendingConnectivityCheck = false;
|
|
client->lastConnectivityCheck = el->dateTime_nowMonotonic(el);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
}
|
|
|
|
static void
|
|
__Client_backgroundConnectivity(UA_Client *client) {
|
|
if(!client->config.connectivityCheckInterval)
|
|
return;
|
|
|
|
if(client->pendingConnectivityCheck)
|
|
return;
|
|
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
UA_DateTime now = el->dateTime_nowMonotonic(el);
|
|
UA_DateTime nextDate = client->lastConnectivityCheck +
|
|
(UA_DateTime)(client->config.connectivityCheckInterval * UA_DATETIME_MSEC);
|
|
if(now <= nextDate)
|
|
return;
|
|
|
|
/* Prepare the request */
|
|
UA_ReadValueId rvid;
|
|
UA_ReadValueId_init(&rvid);
|
|
rvid.attributeId = UA_ATTRIBUTEID_VALUE;
|
|
rvid.nodeId = UA_NS0ID(SERVER_SERVERSTATUS_STATE);
|
|
UA_ReadRequest request;
|
|
UA_ReadRequest_init(&request);
|
|
request.nodesToRead = &rvid;
|
|
request.nodesToReadSize = 1;
|
|
UA_StatusCode retval =
|
|
__Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_READREQUEST],
|
|
(UA_ClientAsyncServiceCallback)backgroundConnectivityCallback,
|
|
&UA_TYPES[UA_TYPES_READRESPONSE], NULL, NULL);
|
|
if(retval == UA_STATUSCODE_GOOD)
|
|
client->pendingConnectivityCheck = true;
|
|
}
|
|
|
|
/* Regular housekeeping activities in the client -- called via a cyclic callback */
|
|
static void
|
|
clientHouseKeeping(UA_Client *client, void *_) {
|
|
UA_LOCK(&client->clientMutex);
|
|
|
|
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"Internally check the the client state and "
|
|
"required activities");
|
|
|
|
/* Renew Secure Channel */
|
|
__Client_renewSecureChannel(client);
|
|
|
|
/* Send read requests from time to time to test the connectivity */
|
|
__Client_backgroundConnectivity(client);
|
|
|
|
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
|
/* Feed the server PublishRequests for the Subscriptions */
|
|
__Client_Subscriptions_backgroundPublish(client);
|
|
|
|
/* Check for inactive Subscriptions */
|
|
__Client_Subscriptions_backgroundPublishInactivityCheck(client);
|
|
#endif
|
|
|
|
/* Did async services time out? Process callbacks with an error code */
|
|
asyncServiceTimeoutCheck(client);
|
|
|
|
/* Log and notify user if the client state has changed */
|
|
notifyClientState(client);
|
|
|
|
UA_UNLOCK(&client->clientMutex);
|
|
}
|
|
|
|
UA_StatusCode
|
|
__UA_Client_startup(UA_Client *client) {
|
|
UA_LOCK_ASSERT(&client->clientMutex);
|
|
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
UA_CHECK_ERROR(el != NULL,
|
|
return UA_STATUSCODE_BADINTERNALERROR,
|
|
client->config.logging, UA_LOGCATEGORY_CLIENT,
|
|
"No EventLoop configured");
|
|
|
|
/* Set up the repeated timer callback for checking the internal state. Like
|
|
* in the public API UA_Client_addRepeatedCallback, but without locking the
|
|
* mutex again */
|
|
UA_StatusCode rv = UA_STATUSCODE_GOOD;
|
|
if(!client->houseKeepingCallbackId) {
|
|
rv = el->addTimer(el, (UA_Callback)clientHouseKeeping,
|
|
client, NULL, 1000.0, NULL,
|
|
UA_TIMERPOLICY_CURRENTTIME,
|
|
&client->houseKeepingCallbackId);
|
|
UA_CHECK_STATUS(rv, return rv);
|
|
}
|
|
|
|
/* Start the EventLoop? */
|
|
if(el->state == UA_EVENTLOOPSTATE_FRESH) {
|
|
rv = el->start(el);
|
|
UA_CHECK_STATUS(rv, return rv);
|
|
}
|
|
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_run_iterate(UA_Client *client, UA_UInt32 timeout) {
|
|
/* Make sure the EventLoop has been started */
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode rv = __UA_Client_startup(client);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
UA_CHECK_STATUS(rv, return rv);
|
|
|
|
/* All timers and network events are triggered in the EventLoop. Release the
|
|
* client lock before. The callbacks from the EventLoop take the lock
|
|
* again. */
|
|
UA_EventLoop *el = client->config.eventLoop;
|
|
rv = el->run(el, timeout);
|
|
UA_CHECK_STATUS(rv, return rv);
|
|
return client->connectStatus;
|
|
}
|
|
|
|
const UA_DataType *
|
|
UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId) {
|
|
return UA_findDataTypeWithCustom(typeId, client->config.customDataTypes);
|
|
}
|
|
|
|
/*************************/
|
|
/* Connection Attributes */
|
|
/*************************/
|
|
|
|
#define UA_CONNECTIONATTRIBUTESSIZE 3
|
|
static const UA_QualifiedName connectionAttributes[UA_CONNECTIONATTRIBUTESSIZE] = {
|
|
{0, UA_STRING_STATIC("serverDescription")},
|
|
{0, UA_STRING_STATIC("securityPolicyUri")},
|
|
{0, UA_STRING_STATIC("securityMode")}
|
|
};
|
|
|
|
static UA_StatusCode
|
|
getConnectionttribute(UA_Client *client, const UA_QualifiedName key,
|
|
UA_Variant *outValue, UA_Boolean copy) {
|
|
if(!outValue)
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
|
UA_Variant localAttr;
|
|
|
|
if(UA_QualifiedName_equal(&key, &connectionAttributes[0])) {
|
|
/* ServerDescription */
|
|
UA_Variant_setScalar(&localAttr, &client->endpoint.server,
|
|
&UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
|
|
} else if(UA_QualifiedName_equal(&key, &connectionAttributes[1])) {
|
|
/* SecurityPolicyUri */
|
|
const UA_SecurityPolicy *sp = client->channel.securityPolicy;
|
|
if(!sp)
|
|
return UA_STATUSCODE_BADNOTCONNECTED;
|
|
UA_Variant_setScalar(&localAttr, (void*)(uintptr_t)&sp->policyUri,
|
|
&UA_TYPES[UA_TYPES_STRING]);
|
|
} else if(UA_QualifiedName_equal(&key, &connectionAttributes[2])) {
|
|
/* SecurityMode */
|
|
UA_Variant_setScalar(&localAttr, &client->channel.securityMode,
|
|
&UA_TYPES[UA_TYPES_MESSAGESECURITYMODE]);
|
|
} else {
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
if(copy)
|
|
return UA_Variant_copy(&localAttr, outValue);
|
|
|
|
localAttr.storageType = UA_VARIANT_DATA_NODELETE;
|
|
*outValue = localAttr;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_getConnectionAttribute(UA_Client *client, const UA_QualifiedName key,
|
|
UA_Variant *outValue) {
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = getConnectionttribute(client, key, outValue, false);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_getConnectionAttributeCopy(UA_Client *client, const UA_QualifiedName key,
|
|
UA_Variant *outValue) {
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = getConnectionttribute(client, key, outValue, true);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_getConnectionAttribute_scalar(UA_Client *client,
|
|
const UA_QualifiedName key,
|
|
const UA_DataType *type,
|
|
void *outValue) {
|
|
UA_LOCK(&client->clientMutex);
|
|
|
|
UA_Variant attr;
|
|
UA_StatusCode res = getConnectionttribute(client, key, &attr, false);
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
if(!UA_Variant_hasScalarType(&attr, type)) {
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return UA_STATUSCODE_BADNOTFOUND;
|
|
}
|
|
|
|
memcpy(outValue, attr.data, type->memSize);
|
|
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
/* Namespace Mapping */
|
|
|
|
UA_StatusCode
|
|
UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index,
|
|
UA_String *nsUri) {
|
|
UA_LOCK(&client->clientMutex);
|
|
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
|
if(index > client->namespacesSize)
|
|
res = UA_String_copy(&client->namespaces[index], nsUri);
|
|
else
|
|
res = UA_STATUSCODE_BADNOTFOUND;
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri,
|
|
UA_UInt16 *outIndex) {
|
|
UA_LOCK(&client->clientMutex);
|
|
for(size_t i = 0; i < client->namespacesSize; i++) {
|
|
if(UA_String_equal(&nsUri, &client->namespaces[i])) {
|
|
*outIndex = (UA_UInt16)i;
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
}
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return UA_STATUSCODE_BADNOTFOUND;
|
|
}
|
|
|
|
UA_StatusCode
|
|
UA_Client_addNamespace(UA_Client *client, const UA_String nsUri,
|
|
UA_UInt16 *outIndex) {
|
|
UA_StatusCode res = UA_Client_getNamespaceIndex(client, nsUri, outIndex);
|
|
if(res == UA_STATUSCODE_GOOD)
|
|
return res;
|
|
UA_LOCK(&client->clientMutex);
|
|
res = UA_Array_appendCopy((void**)&client->namespaces, &client->namespacesSize,
|
|
&nsUri, &UA_TYPES[UA_TYPES_STRING]);
|
|
if(res == UA_STATUSCODE_GOOD)
|
|
*outIndex = (UA_UInt16)(client->namespacesSize - 1);
|
|
UA_UNLOCK(&client->clientMutex);
|
|
return res;
|
|
}
|