| /* 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 <open62541/transport_generated.h> |
| #include <open62541/transport_generated_encoding_binary.h> |
| #include <open62541/transport_generated_handling.h> |
| #include <open62541/types_generated_encoding_binary.h> |
| |
| #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; |
| } |