/* 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/. */ #include #include #include #include #include "ua_client_internal.h" #define UA_MINMESSAGESIZE 8192 #define UA_SESSION_LOCALNONCELENGTH 32 #define MAX_DATA_SIZE 4096 /* Asynchronous client connection * To prepare an async connection, UA_Client_connectAsync() is called, which does not connect the * client directly. UA_Client_run_iterate() takes care of actually connecting the client: * if client is disconnected: * send hello msg and set the client state to be WAITING_FOR_ACK * (see UA_Client_connect_iterate()) * if client is waiting for the ACK: * call the non-blocking receiving function and register processACKResponseAsync() as its callback * (see receivePacketAsync()) * if ACK is processed (callback called): * processACKResponseAsync() calls openSecureChannelAsync() at the end, which prepares the request * to open secure channel and the client is connected * if client is connected: * call the non-blocking receiving function and register processOPNResponse() as its callback * (see receivePacketAsync()) * if OPN-request processed (callback called) * send session request, where the session response is put into a normal AsyncServiceCall, and when * called, request to activate session is sent, where its response is again put into an AsyncServiceCall * in the very last step responseActivateSession(): * the user defined callback that is passed into UA_Client_connectAsync() is called and the * async connection finalized. * */ /***********************/ /* Open the Connection */ /***********************/ static UA_StatusCode openSecureChannelAsync(UA_Client *client/*, UA_Boolean renew*/); static UA_StatusCode requestSession(UA_Client *client, UA_UInt32 *requestId); static UA_StatusCode requestGetEndpoints(UA_Client *client, UA_UInt32 *requestId); /*receives hello ack, opens secure channel*/ UA_StatusCode processACKResponseAsync(void *application, UA_Connection *connection, UA_ByteString *chunk) { UA_Client *client = (UA_Client*)application; /* Decode the message */ size_t offset = 0; UA_TcpMessageHeader messageHeader; UA_TcpAcknowledgeMessage ackMessage; client->connectStatus = UA_TcpMessageHeader_decodeBinary (chunk, &offset, &messageHeader); client->connectStatus |= UA_TcpAcknowledgeMessage_decodeBinary( chunk, &offset, &ackMessage); if (client->connectStatus != UA_STATUSCODE_GOOD) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Decoding ACK message failed"); return client->connectStatus; } UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Received ACK message"); client->connectStatus = UA_Connection_processHELACK(connection, &client->config.localConnectionConfig, (const UA_ConnectionConfig*)&ackMessage); if(client->connectStatus != UA_STATUSCODE_GOOD) return client->connectStatus; client->state = UA_CLIENTSTATE_CONNECTED; /* Open a SecureChannel. TODO: Select with endpoint */ client->channel.connection = &client->connection; client->connectStatus = openSecureChannelAsync(client/*, false*/); return client->connectStatus; } static UA_StatusCode sendHELMessage(UA_Client *client) { /* Get a buffer */ UA_ByteString message; UA_Connection *conn = &client->connection; UA_StatusCode retval = conn->getSendBuffer(conn, UA_MINMESSAGESIZE, &message); if(retval != UA_STATUSCODE_GOOD) return retval; /* Prepare the HEL message and encode at offset 8 */ UA_TcpHelloMessage hello; UA_String_copy(&client->endpointUrl, &hello.endpointUrl); /* must be less than 4096 bytes */ memcpy(&hello, &client->config.localConnectionConfig, sizeof(UA_ConnectionConfig)); /* same struct layout */ UA_Byte *bufPos = &message.data[8]; /* skip the header */ const UA_Byte *bufEnd = &message.data[message.length]; client->connectStatus = UA_TcpHelloMessage_encodeBinary(&hello, &bufPos, bufEnd); UA_TcpHelloMessage_deleteMembers (&hello); /* Encode the message header at offset 0 */ UA_TcpMessageHeader messageHeader; messageHeader.messageTypeAndChunkType = UA_CHUNKTYPE_FINAL + UA_MESSAGETYPE_HEL; messageHeader.messageSize = (UA_UInt32) ((uintptr_t)bufPos - (uintptr_t)message.data); bufPos = message.data; retval = UA_TcpMessageHeader_encodeBinary(&messageHeader, &bufPos, bufEnd); if(retval != UA_STATUSCODE_GOOD) { conn->releaseSendBuffer(conn, &message); return retval; } /* Send the HEL message */ message.length = messageHeader.messageSize; retval = conn->send (conn, &message); if(retval == UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Sent HEL message"); } else { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Sending HEL failed"); } return retval; } static void processDecodedOPNResponseAsync(void *application, UA_SecureChannel *channel, UA_MessageType messageType, UA_UInt32 requestId, const UA_ByteString *message) { /* Does the request id match? */ UA_Client *client = (UA_Client*)application; if(requestId != client->requestId) { UA_Client_disconnect(client); return; } /* Is the content of the expected type? */ size_t offset = 0; UA_NodeId responseId; UA_NodeId expectedId = UA_NODEID_NUMERIC( 0, UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE].binaryEncodingId); UA_StatusCode retval = UA_NodeId_decodeBinary(message, &offset, &responseId); if(retval != UA_STATUSCODE_GOOD) { UA_Client_disconnect(client); return; } if(!UA_NodeId_equal(&responseId, &expectedId)) { UA_NodeId_deleteMembers(&responseId); UA_Client_disconnect(client); return; } UA_NodeId_deleteMembers (&responseId); /* Decode the response */ UA_OpenSecureChannelResponse response; retval = UA_OpenSecureChannelResponse_decodeBinary(message, &offset, &response); if(retval != UA_STATUSCODE_GOOD) { UA_Client_disconnect(client); return; } /* Response.securityToken.revisedLifetime is UInt32 we need to cast it to * DateTime=Int64 we take 75% of lifetime to start renewing as described in * standard */ client->nextChannelRenewal = UA_DateTime_nowMonotonic() + (UA_DateTime) (response.securityToken.revisedLifetime * (UA_Double) UA_DATETIME_MSEC * 0.75); /* Replace the token and nonce */ UA_ChannelSecurityToken_deleteMembers(&client->channel.securityToken); UA_ByteString_deleteMembers(&client->channel.remoteNonce); client->channel.securityToken = response.securityToken; client->channel.remoteNonce = response.serverNonce; UA_ResponseHeader_deleteMembers(&response.responseHeader); /* the other members were moved */ if(client->channel.state == UA_SECURECHANNELSTATE_OPEN) UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "SecureChannel renewed"); else UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "SecureChannel opened"); client->channel.state = UA_SECURECHANNELSTATE_OPEN; if(client->state < UA_CLIENTSTATE_SECURECHANNEL) setClientState(client, UA_CLIENTSTATE_SECURECHANNEL); } UA_StatusCode processOPNResponseAsync(void *application, UA_Connection *connection, UA_ByteString *chunk) { UA_Client *client = (UA_Client*) application; UA_StatusCode retval = UA_SecureChannel_decryptAddChunk(&client->channel, chunk, true); client->connectStatus = retval; if(retval != UA_STATUSCODE_GOOD) goto error; UA_SecureChannel_processCompleteMessages(&client->channel, client, processDecodedOPNResponseAsync); if(client->state < UA_CLIENTSTATE_SECURECHANNEL) { retval = UA_STATUSCODE_BADSECURECHANNELCLOSED; goto error; } retval = UA_SecureChannel_persistIncompleteMessages(&client->channel); if(retval != UA_STATUSCODE_GOOD) goto error; retval = UA_SecureChannel_generateNewKeys(&client->channel); if(retval != UA_STATUSCODE_GOOD) goto error; /* Following requests and responses */ UA_UInt32 reqId; if(client->endpointsHandshake) retval = requestGetEndpoints (client, &reqId); else retval = requestSession (client, &reqId); if(retval != UA_STATUSCODE_GOOD) goto error; return retval; error: UA_Client_disconnect(client); return retval; } /* OPN messges to renew the channel are sent asynchronous */ static UA_StatusCode openSecureChannelAsync(UA_Client *client/*, UA_Boolean renew*/) { /* Check if sc is still valid */ /*if(renew && client->nextChannelRenewal - UA_DateTime_nowMonotonic () > 0) return UA_STATUSCODE_GOOD;*/ UA_Connection *conn = &client->connection; if(conn->state != UA_CONNECTION_ESTABLISHED) return UA_STATUSCODE_BADSERVERNOTCONNECTED; /* Prepare the OpenSecureChannelRequest */ UA_OpenSecureChannelRequest opnSecRq; UA_OpenSecureChannelRequest_init(&opnSecRq); opnSecRq.requestHeader.timestamp = UA_DateTime_now(); opnSecRq.requestHeader.authenticationToken = client->authenticationToken; /*if(renew) { opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_RENEW; UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Requesting to renew the SecureChannel"); } else {*/ opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_ISSUE; UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Requesting to open a SecureChannel"); //} opnSecRq.securityMode = client->channel.securityMode; opnSecRq.clientNonce = client->channel.localNonce; opnSecRq.requestedLifetime = client->config.secureChannelLifeTime; /* Prepare the entry for the linked list */ UA_UInt32 requestId = ++client->requestId; /*AsyncServiceCall *ac = NULL; if(renew) { ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall)); if (!ac) return UA_STATUSCODE_BADOUTOFMEMORY; ac->callback = (UA_ClientAsyncServiceCallback) processDecodedOPNResponseAsync; ac->responseType = &UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE]; ac->requestId = requestId; ac->userdata = NULL; }*/ /* Send the OPN message */ UA_StatusCode retval = UA_SecureChannel_sendAsymmetricOPNMessage ( &client->channel, requestId, &opnSecRq, &UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST]); client->connectStatus = retval; if(retval != UA_STATUSCODE_GOOD) { client->connectStatus = retval; UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Sending OPN message failed with error %s", UA_StatusCode_name(retval)); UA_Client_disconnect(client); //if(renew) // UA_free(ac); return retval; } UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "OPN message sent"); /* Store the entry for async processing and return */ /*if(renew) { LIST_INSERT_HEAD(&client->asyncServiceCalls, ac, pointers); return retval; }*/ return retval; } static void responseActivateSession(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) { UA_ActivateSessionResponse *activateResponse = (UA_ActivateSessionResponse *) response; if(activateResponse->responseHeader.serviceResult) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "ActivateSession failed with error code %s", UA_StatusCode_name(activateResponse->responseHeader.serviceResult)); } client->connection.state = UA_CONNECTION_ESTABLISHED; setClientState(client, UA_CLIENTSTATE_SESSION); #ifdef UA_ENABLE_SUBSCRIPTIONS /* A new session has been created. We need to clean up the subscriptions */ UA_Client_Subscriptions_clean(client); #endif /* Call onConnect (client_async.c) callback */ if(client->asyncConnectCall.callback) client->asyncConnectCall.callback(client, client->asyncConnectCall.userdata, requestId + 1, &activateResponse->responseHeader.serviceResult); } static UA_StatusCode requestActivateSession (UA_Client *client, UA_UInt32 *requestId) { UA_ActivateSessionRequest request; UA_ActivateSessionRequest_init(&request); request.requestHeader.requestHandle = ++client->requestHandle; request.requestHeader.timestamp = UA_DateTime_now (); request.requestHeader.timeoutHint = 600000; UA_StatusCode retval = UA_ExtensionObject_copy(&client->config.userIdentityToken, &request.userIdentityToken); if(retval != UA_STATUSCODE_GOOD) return retval; /* If not token is set, use anonymous */ if(request.userIdentityToken.encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) { UA_AnonymousIdentityToken *t = UA_AnonymousIdentityToken_new(); if(!t) { UA_ActivateSessionRequest_deleteMembers(&request); return UA_STATUSCODE_BADOUTOFMEMORY; } request.userIdentityToken.content.decoded.data = t; request.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]; request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED; } /* Set the policy-Id from the endpoint. Every IdentityToken starts with a * string. */ retval = UA_String_copy(&client->config.userTokenPolicy.policyId, (UA_String*)request.userIdentityToken.content.decoded.data); #ifdef UA_ENABLE_ENCRYPTION /* Encrypt the UserIdentityToken */ const UA_String *userTokenPolicy = &client->channel.securityPolicy->policyUri; if(client->config.userTokenPolicy.securityPolicyUri.length > 0) userTokenPolicy = &client->config.userTokenPolicy.securityPolicyUri; retval |= encryptUserIdentityToken(client, userTokenPolicy, &request.userIdentityToken); /* This function call is to prepare a client signature */ retval |= signActivateSessionRequest(&client->channel, &request); #endif if(retval != UA_STATUSCODE_GOOD) { UA_ActivateSessionRequest_deleteMembers(&request); client->connectStatus = retval; return retval; } retval = UA_Client_sendAsyncRequest ( client, &request, &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST], (UA_ClientAsyncServiceCallback) responseActivateSession, &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], NULL, requestId); UA_ActivateSessionRequest_deleteMembers(&request); client->connectStatus = retval; return retval; } /* Combination of UA_Client_getEndpointsInternal and getEndpoints */ static void responseGetEndpoints(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) { UA_EndpointDescription* endpointArray = NULL; size_t endpointArraySize = 0; UA_GetEndpointsResponse* resp; resp = (UA_GetEndpointsResponse*)response; if (resp->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { client->connectStatus = resp->responseHeader.serviceResult; UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "GetEndpointRequest failed with error code %s", UA_StatusCode_name (client->connectStatus)); UA_GetEndpointsResponse_deleteMembers(resp); return; } endpointArray = resp->endpoints; endpointArraySize = resp->endpointsSize; resp->endpoints = NULL; resp->endpointsSize = 0; UA_Boolean endpointFound = false; UA_Boolean tokenFound = false; UA_String securityNone = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); UA_String binaryTransport = UA_STRING("http://opcfoundation.org/UA-Profile/" "Transport/uatcp-uasc-uabinary"); // TODO: compare endpoint information with client->endpointUri for(size_t i = 0; i < endpointArraySize; ++i) { UA_EndpointDescription* endpoint = &endpointArray[i]; /* look out for binary transport endpoints */ /* Note: Siemens returns empty ProfileUrl, we will accept it as binary */ if(endpoint->transportProfileUri.length != 0 && !UA_String_equal (&endpoint->transportProfileUri, &binaryTransport)) continue; /* Look for an endpoint corresponding to the client security policy */ if(!UA_String_equal(&endpoint->securityPolicyUri, &client->channel.securityPolicy->policyUri)) continue; endpointFound = true; /* Look for a user token policy with an anonymous token */ for(size_t j = 0; j < endpoint->userIdentityTokensSize; ++j) { UA_UserTokenPolicy* userToken = &endpoint->userIdentityTokens[j]; /* Usertokens also have a security policy... */ if(userToken->securityPolicyUri.length > 0 && !UA_String_equal(&userToken->securityPolicyUri, &securityNone)) continue; /* Does the token type match the client configuration? */ if((userToken->tokenType == UA_USERTOKENTYPE_ANONYMOUS && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] && client->config.userIdentityToken.content.decoded.type != NULL) || (userToken->tokenType == UA_USERTOKENTYPE_USERNAME && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) || (userToken->tokenType == UA_USERTOKENTYPE_CERTIFICATE && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) || (userToken->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN])) continue; /* Endpoint with matching usertokenpolicy found */ tokenFound = true; UA_EndpointDescription_deleteMembers(&client->config.endpoint); UA_EndpointDescription_copy(endpoint, &client->config.endpoint); UA_UserTokenPolicy_deleteMembers(&client->config.userTokenPolicy); UA_UserTokenPolicy_copy(userToken, &client->config.userTokenPolicy); break; } } UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); if(!endpointFound) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable endpoint found"); client->connectStatus = UA_STATUSCODE_BADINTERNALERROR; } else if(!tokenFound) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable UserTokenPolicy found for the possible endpoints"); client->connectStatus = UA_STATUSCODE_BADINTERNALERROR; } requestSession(client, &requestId); } static UA_StatusCode requestGetEndpoints(UA_Client *client, UA_UInt32 *requestId) { UA_GetEndpointsRequest request; UA_GetEndpointsRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; /* assume the endpointurl outlives the service call */ UA_String_copy(&client->endpointUrl, &request.endpointUrl); client->connectStatus = UA_Client_sendAsyncRequest( client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST], (UA_ClientAsyncServiceCallback) responseGetEndpoints, &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE], NULL, requestId); UA_GetEndpointsRequest_deleteMembers(&request); return client->connectStatus; } static void responseSessionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) { UA_CreateSessionResponse *sessionResponse = (UA_CreateSessionResponse *)response; UA_NodeId_copy(&sessionResponse->authenticationToken, &client->authenticationToken); requestActivateSession(client, &requestId); } static UA_StatusCode requestSession(UA_Client *client, UA_UInt32 *requestId) { UA_CreateSessionRequest request; UA_CreateSessionRequest_init(&request); UA_StatusCode retval = UA_STATUSCODE_GOOD; if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN || client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { if(client->channel.localNonce.length != UA_SESSION_LOCALNONCELENGTH) { UA_ByteString_deleteMembers(&client->channel.localNonce); retval = UA_ByteString_allocBuffer(&client->channel.localNonce, UA_SESSION_LOCALNONCELENGTH); if(retval != UA_STATUSCODE_GOOD) return retval; } retval = client->channel.securityPolicy->symmetricModule. generateNonce(client->channel.securityPolicy, &client->channel.localNonce); if(retval != UA_STATUSCODE_GOOD) return retval; } request.requestHeader.requestHandle = ++client->requestHandle; request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; UA_ByteString_copy(&client->channel.localNonce, &request.clientNonce); request.requestedSessionTimeout = client->config.requestedSessionTimeout; request.maxResponseMessageSize = UA_INT32_MAX; UA_String_copy(&client->config.endpoint.endpointUrl, &request.endpointUrl); UA_ApplicationDescription_copy(&client->config.clientDescription, &request.clientDescription); retval = UA_Client_sendAsyncRequest ( client, &request, &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST], (UA_ClientAsyncServiceCallback) responseSessionCallback, &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL, requestId); UA_CreateSessionRequest_deleteMembers(&request); client->connectStatus = retval; return client->connectStatus; } UA_StatusCode UA_Client_connect_iterate(UA_Client *client) { UA_LOG_TRACE(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Client connect iterate"); if (client->connection.state == UA_CONNECTION_ESTABLISHED){ if(client->state < UA_CLIENTSTATE_WAITING_FOR_ACK) { client->connectStatus = sendHELMessage(client); if(client->connectStatus == UA_STATUSCODE_GOOD) { setClientState(client, UA_CLIENTSTATE_WAITING_FOR_ACK); } else { client->connection.close(&client->connection); client->connection.free(&client->connection); } return client->connectStatus; } } /* If server is not connected */ if(client->connection.state == UA_CONNECTION_CLOSED) { client->connectStatus = UA_STATUSCODE_BADCONNECTIONCLOSED; UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_NETWORK, "No connection to server."); } if(client->connectStatus != UA_STATUSCODE_GOOD) { client->connection.close(&client->connection); client->connection.free(&client->connection); } return client->connectStatus; } UA_StatusCode UA_Client_connect_async(UA_Client *client, const char *endpointUrl, UA_ClientAsyncServiceCallback callback, void *userdata) { UA_LOG_TRACE(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Client internal async"); if(client->state >= UA_CLIENTSTATE_WAITING_FOR_ACK) return UA_STATUSCODE_GOOD; UA_ChannelSecurityToken_init(&client->channel.securityToken); client->channel.state = UA_SECURECHANNELSTATE_FRESH; client->endpointsHandshake = true; client->channel.sendSequenceNumber = 0; client->requestId = 0; UA_String_deleteMembers(&client->endpointUrl); client->endpointUrl = UA_STRING_ALLOC(endpointUrl); UA_StatusCode retval = UA_STATUSCODE_GOOD; client->connection = client->config.initConnectionFunc(client->config.localConnectionConfig, client->endpointUrl, client->config.timeout, &client->config.logger); if(client->connection.state != UA_CONNECTION_OPENING) { UA_LOG_TRACE(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Could not init async connection"); retval = UA_STATUSCODE_BADCONNECTIONCLOSED; goto cleanup; } /* Set the channel SecurityMode if not done so far */ if(client->channel.securityMode == UA_MESSAGESECURITYMODE_INVALID) client->channel.securityMode = UA_MESSAGESECURITYMODE_NONE; /* Set the channel SecurityPolicy if not done so far */ if(!client->channel.securityPolicy) { UA_SecurityPolicy *sp = getSecurityPolicy(client, UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None")); if(!sp) { retval = UA_STATUSCODE_BADINTERNALERROR; goto cleanup; } UA_ByteString remoteCertificate = UA_BYTESTRING_NULL; retval = UA_SecureChannel_setSecurityPolicy(&client->channel, sp, &remoteCertificate); if(retval != UA_STATUSCODE_GOOD) goto cleanup; } client->asyncConnectCall.callback = callback; client->asyncConnectCall.userdata = userdata; if(!client->connection.connectCallbackID) { UA_LOG_TRACE(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Adding async connection callback"); retval = UA_Client_addRepeatedCallback( client, client->config.pollConnectionFunc, &client->connection, 100.0, &client->connection.connectCallbackID); if(retval != UA_STATUSCODE_GOOD) goto cleanup; } retval = UA_SecureChannel_generateLocalNonce(&client->channel); if(retval != UA_STATUSCODE_GOOD) goto cleanup; /* Delete async service. TODO: Move this from connect to the disconnect/cleanup phase */ UA_Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSHUTDOWN); #ifdef UA_ENABLE_SUBSCRIPTIONS client->currentlyOutStandingPublishRequests = 0; #endif UA_NodeId_deleteMembers(&client->authenticationToken); /* Generate new local and remote key */ retval = UA_SecureChannel_generateNewKeys(&client->channel); if(retval != UA_STATUSCODE_GOOD) goto cleanup; return retval; cleanup: UA_LOG_TRACE(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Failure during async connect"); UA_Client_disconnect(client); return retval; } /* Async disconnection */ static void sendCloseSecureChannelAsync(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) { UA_NodeId_deleteMembers (&client->authenticationToken); client->requestHandle = 0; UA_SecureChannel *channel = &client->channel; UA_CloseSecureChannelRequest request; UA_CloseSecureChannelRequest_init(&request); request.requestHeader.requestHandle = ++client->requestHandle; request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; request.requestHeader.authenticationToken = client->authenticationToken; UA_SecureChannel_sendSymmetricMessage( channel, ++client->requestId, UA_MESSAGETYPE_CLO, &request, &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST]); UA_SecureChannel_close(&client->channel); UA_SecureChannel_deleteMembers(&client->channel); } static void sendCloseSessionAsync(UA_Client *client, UA_UInt32 *requestId) { UA_CloseSessionRequest request; UA_CloseSessionRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; request.deleteSubscriptions = true; UA_Client_sendAsyncRequest( client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST], (UA_ClientAsyncServiceCallback) sendCloseSecureChannelAsync, &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], NULL, requestId); } UA_StatusCode UA_Client_disconnect_async(UA_Client *client, UA_UInt32 *requestId) { /* Is a session established? */ if (client->state == UA_CLIENTSTATE_SESSION) { client->state = UA_CLIENTSTATE_SESSION_DISCONNECTED; sendCloseSessionAsync(client, requestId); } /* Close the TCP connection * shutdown and close (in tcp.c) are already async*/ if (client->state >= UA_CLIENTSTATE_CONNECTED) client->connection.close(&client->connection); else UA_Client_removeRepeatedCallback(client, client->connection.connectCallbackID); #ifdef UA_ENABLE_SUBSCRIPTIONS // TODO REMOVE WHEN UA_SESSION_RECOVERY IS READY /* We need to clean up the subscriptions */ UA_Client_Subscriptions_clean(client); #endif setClientState(client, UA_CLIENTSTATE_DISCONNECTED); return UA_STATUSCODE_GOOD; }