#include "ua_client.h" #include "ua_client_internal.h" #include "ua_connection_internal.h" #include "ua_types_encoding_binary.h" #include "ua_types_generated_encoding_binary.h" #include "ua_transport_generated.h" #include "ua_transport_generated_handling.h" #include "ua_transport_generated_encoding_binary.h" #include "ua_util.h" #include "ua_nodeids.h" /* Listen with a timeout until at least one complete chunk is received */ static UA_StatusCode Connection_receiveChunk(UA_Connection *connection, UA_ByteString * UA_RESTRICT message, UA_Boolean * UA_RESTRICT realloced, UA_UInt32 timeout) { UA_StatusCode retval = UA_STATUSCODE_GOOD; *realloced = false; UA_DateTime maxDate = UA_DateTime_nowMonotonic() + (timeout * UA_MSEC_TO_DATETIME); /* Receive packets until one complete message is assembled */ do { UA_DateTime now = UA_DateTime_nowMonotonic(); if(now > maxDate) return UA_STATUSCODE_GOODNONCRITICALTIMEOUT; UA_UInt32 thisTimeout = (UA_UInt32)((maxDate - now) / UA_MSEC_TO_DATETIME); retval = connection->recv(connection, message, thisTimeout); retval |= UA_Connection_completeMessages(connection, message, realloced); } while(retval == UA_STATUSCODE_GOOD && message->length == 0); return retval; } /*********************/ /* Create and Delete */ /*********************/ static void UA_Client_init(UA_Client* client, UA_ClientConfig config) { memset(client, 0, sizeof(UA_Client)); client->channel.connection = &client->connection; client->config = config; } UA_Client * UA_Client_new(UA_ClientConfig config) { UA_Client *client = (UA_Client*)UA_calloc(1, sizeof(UA_Client)); if(!client) return NULL; UA_Client_init(client, config); return client; } static void UA_Client_deleteMembers(UA_Client* client) { UA_Client_disconnect(client); UA_SecureChannel_deleteMembersCleanup(&client->channel); UA_Connection_deleteMembers(&client->connection); if(client->endpointUrl.data) UA_String_deleteMembers(&client->endpointUrl); UA_UserTokenPolicy_deleteMembers(&client->token); UA_NodeId_deleteMembers(&client->authenticationToken); if(client->username.data) UA_String_deleteMembers(&client->username); if(client->password.data) UA_String_deleteMembers(&client->password); #ifdef UA_ENABLE_SUBSCRIPTIONS UA_Client_NotificationsAckNumber *n, *tmp; LIST_FOREACH_SAFE(n, &client->pendingNotificationsAcks, listEntry, tmp) { LIST_REMOVE(n, listEntry); free(n); } UA_Client_Subscription *sub, *tmps; LIST_FOREACH_SAFE(sub, &client->subscriptions, listEntry, tmps) UA_Client_Subscriptions_forceDelete(client, sub); /* force local removal */ #endif } void UA_Client_reset(UA_Client* client){ UA_Client_deleteMembers(client); UA_Client_init(client, client->config); } void UA_Client_delete(UA_Client* client){ UA_Client_deleteMembers(client); UA_free(client); } UA_ClientState UA_EXPORT UA_Client_getState(UA_Client *client) { if(!client) return UA_CLIENTSTATE_ERRORED; return client->state; } /*************************/ /* Manage the Connection */ /*************************/ #define UA_MINMESSAGESIZE 8192 static UA_StatusCode HelAckHandshake(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 */ hello.maxChunkCount = conn->localConf.maxChunkCount; hello.maxMessageSize = conn->localConf.maxMessageSize; hello.protocolVersion = conn->localConf.protocolVersion; hello.receiveBufferSize = conn->localConf.recvBufferSize; hello.sendBufferSize = conn->localConf.sendBufferSize; size_t offset = 8; retval = UA_TcpHelloMessage_encodeBinary(&hello, &message, &offset); 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)offset; offset = 0; retval |= UA_TcpMessageHeader_encodeBinary(&messageHeader, &message, &offset); 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_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK, "Sending HEL failed"); return retval; } UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_NETWORK, "Sent HEL message"); /* Loop until we have a complete chunk */ UA_ByteString reply = UA_BYTESTRING_NULL; UA_Boolean realloced = false; retval = Connection_receiveChunk(conn, &reply, &realloced, client->config.timeout); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK, "Receiving ACK message failed"); return retval; } /* Decode the message */ offset = 0; UA_TcpAcknowledgeMessage ackMessage; retval = UA_TcpMessageHeader_decodeBinary(&reply, &offset, &messageHeader); retval |= UA_TcpAcknowledgeMessage_decodeBinary(&reply, &offset, &ackMessage); /* Free the message buffer */ if(!realloced) conn->releaseRecvBuffer(conn, &reply); else UA_ByteString_deleteMembers(&reply); /* Store remote connection settings and adjust local configuration to not exceed the limits */ if(retval == UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_NETWORK, "Received ACK message"); conn->remoteConf.maxChunkCount = ackMessage.maxChunkCount; /* may be zero -> unlimited */ conn->remoteConf.maxMessageSize = ackMessage.maxMessageSize; /* may be zero -> unlimited */ conn->remoteConf.protocolVersion = ackMessage.protocolVersion; conn->remoteConf.sendBufferSize = ackMessage.sendBufferSize; conn->remoteConf.recvBufferSize = ackMessage.receiveBufferSize; if(conn->remoteConf.recvBufferSize < conn->localConf.sendBufferSize) conn->localConf.sendBufferSize = conn->remoteConf.recvBufferSize; if(conn->remoteConf.sendBufferSize < conn->localConf.recvBufferSize) conn->localConf.recvBufferSize = conn->remoteConf.sendBufferSize; conn->state = UA_CONNECTION_ESTABLISHED; } else { UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK, "Decoding ACK message failed"); } UA_TcpAcknowledgeMessage_deleteMembers(&ackMessage); return retval; } static UA_StatusCode SecureChannelHandshake(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; UA_SecureConversationMessageHeader messageHeader; messageHeader.messageHeader.messageTypeAndChunkType = UA_MESSAGETYPE_OPN + UA_CHUNKTYPE_FINAL; if(renew) messageHeader.secureChannelId = client->channel.securityToken.channelId; else messageHeader.secureChannelId = 0; UA_SequenceHeader seqHeader; seqHeader.sequenceNumber = ++client->channel.sendSequenceNumber; seqHeader.requestId = ++client->requestId; UA_AsymmetricAlgorithmSecurityHeader asymHeader; UA_AsymmetricAlgorithmSecurityHeader_init(&asymHeader); asymHeader.securityPolicyUri = UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#None"); /* id of opensecurechannelrequest */ UA_NodeId requestType = UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST].binaryEncodingId); UA_OpenSecureChannelRequest opnSecRq; UA_OpenSecureChannelRequest_init(&opnSecRq); opnSecRq.requestHeader.timestamp = UA_DateTime_now(); opnSecRq.requestHeader.authenticationToken = client->authenticationToken; opnSecRq.requestedLifetime = client->config.secureChannelLifeTime; 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"); } UA_ByteString_copy(&client->channel.clientNonce, &opnSecRq.clientNonce); opnSecRq.securityMode = UA_MESSAGESECURITYMODE_NONE; UA_ByteString message; UA_StatusCode retval = conn->getSendBuffer(conn, conn->remoteConf.recvBufferSize, &message); if(retval != UA_STATUSCODE_GOOD) { UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader); UA_OpenSecureChannelRequest_deleteMembers(&opnSecRq); return retval; } size_t offset = 12; retval = UA_AsymmetricAlgorithmSecurityHeader_encodeBinary(&asymHeader, &message, &offset); retval |= UA_SequenceHeader_encodeBinary(&seqHeader, &message, &offset); retval |= UA_NodeId_encodeBinary(&requestType, &message, &offset); retval |= UA_OpenSecureChannelRequest_encodeBinary(&opnSecRq, &message, &offset); messageHeader.messageHeader.messageSize = (UA_UInt32)offset; offset = 0; retval |= UA_SecureConversationMessageHeader_encodeBinary(&messageHeader, &message, &offset); UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader); UA_OpenSecureChannelRequest_deleteMembers(&opnSecRq); if(retval != UA_STATUSCODE_GOOD) { client->connection.releaseSendBuffer(&client->connection, &message); return retval; } message.length = messageHeader.messageHeader.messageSize; retval = conn->send(conn, &message); if(retval != UA_STATUSCODE_GOOD) return retval; UA_ByteString reply = UA_BYTESTRING_NULL; UA_Boolean realloced = false; retval = Connection_receiveChunk(conn, &reply, &realloced, client->config.timeout); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Receiving OpenSecureChannelResponse failed"); return retval; } /* Decode the header */ offset = 0; UA_SecureConversationMessageHeader_decodeBinary(&reply, &offset, &messageHeader); UA_AsymmetricAlgorithmSecurityHeader_decodeBinary(&reply, &offset, &asymHeader); UA_SequenceHeader_decodeBinary(&reply, &offset, &seqHeader); UA_NodeId_decodeBinary(&reply, &offset, &requestType); UA_NodeId expectedRequest = UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE].binaryEncodingId); if(!UA_NodeId_equal(&requestType, &expectedRequest)) { UA_ByteString_deleteMembers(&reply); UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader); UA_NodeId_deleteMembers(&requestType); UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT, "Reply answers the wrong request. Expected OpenSecureChannelResponse."); return UA_STATUSCODE_BADINTERNALERROR; } /* Save the sequence number from server */ client->channel.receiveSequenceNumber = seqHeader.sequenceNumber; /* Decode the response */ UA_OpenSecureChannelResponse response; retval = UA_OpenSecureChannelResponse_decodeBinary(&reply, &offset, &response); /* Free the message */ if(!realloced) conn->releaseRecvBuffer(conn, &reply); else UA_ByteString_deleteMembers(&reply); /* Results in either the StatusCode of decoding or the service */ retval |= response.responseHeader.serviceResult; if(retval == UA_STATUSCODE_GOOD) { /* 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_MSEC_TO_DATETIME * 0.75); /* Replace the old nonce */ UA_ChannelSecurityToken_deleteMembers(&client->channel.securityToken); UA_ChannelSecurityToken_copy(&response.securityToken, &client->channel.securityToken); UA_ByteString_deleteMembers(&client->channel.serverNonce); UA_ByteString_copy(&response.serverNonce, &client->channel.serverNonce); if(renew) UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "SecureChannel renewed"); else UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "SecureChannel opened"); } else { if(renew) UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "SecureChannel could not be renewed " "with error code %s", UA_StatusCode_name(retval)); else UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "SecureChannel could not be opened " "with error code %s", UA_StatusCode_name(retval)); } /* Clean up */ UA_OpenSecureChannelResponse_deleteMembers(&response); UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader); return retval; } static UA_StatusCode ActivateSession(UA_Client *client) { UA_ActivateSessionRequest request; UA_ActivateSessionRequest_init(&request); request.requestHeader.requestHandle = ++client->requestHandle; request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 600000; //manual ExtensionObject encoding of the identityToken if(client->authenticationMethod == UA_CLIENTAUTHENTICATION_NONE) { UA_AnonymousIdentityToken* identityToken = UA_AnonymousIdentityToken_new(); UA_AnonymousIdentityToken_init(identityToken); UA_String_copy(&client->token.policyId, &identityToken->policyId); request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED; request.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]; request.userIdentityToken.content.decoded.data = identityToken; } else { UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new(); UA_UserNameIdentityToken_init(identityToken); UA_String_copy(&client->token.policyId, &identityToken->policyId); UA_String_copy(&client->username, &identityToken->userName); UA_String_copy(&client->password, &identityToken->password); request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED; request.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]; request.userIdentityToken.content.decoded.data = identityToken; } UA_ActivateSessionResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST], &response, &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE]); if(response.responseHeader.serviceResult) { UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT, "ActivateSession failed with statuscode 0x%08x", response.responseHeader.serviceResult); } UA_StatusCode retval = response.responseHeader.serviceResult; UA_ActivateSessionRequest_deleteMembers(&request); UA_ActivateSessionResponse_deleteMembers(&response); return retval; } /* Gets a list of endpoints. Memory is allocated for endpointDescription array */ static UA_StatusCode GetEndpoints(UA_Client *client, size_t* endpointDescriptionsSize, UA_EndpointDescription** endpointDescriptions) { UA_GetEndpointsRequest request; UA_GetEndpointsRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; // assume the endpointurl outlives the service call request.endpointUrl = client->endpointUrl; UA_GetEndpointsResponse response; UA_GetEndpointsResponse_init(&response); __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST], &response, &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE]); if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_StatusCode retval = response.responseHeader.serviceResult; UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT, "GetEndpointRequest failed with statuscode 0x%08x", retval); UA_GetEndpointsResponse_deleteMembers(&response); return retval; } *endpointDescriptions = response.endpoints; *endpointDescriptionsSize = response.endpointsSize; response.endpoints = NULL; response.endpointsSize = 0; UA_GetEndpointsResponse_deleteMembers(&response); return UA_STATUSCODE_GOOD; } static UA_StatusCode EndpointsHandshake(UA_Client *client) { UA_EndpointDescription* endpointArray = NULL; size_t endpointArraySize = 0; UA_StatusCode retval = GetEndpoints(client, &endpointArraySize, &endpointArray); if(retval != UA_STATUSCODE_GOOD) return retval; 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 out for an endpoint without security */ if(!UA_String_equal(&endpoint->securityPolicyUri, &securityNone)) continue; /* endpoint with no security found */ 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; /* UA_CLIENTAUTHENTICATION_NONE == UA_USERTOKENTYPE_ANONYMOUS * UA_CLIENTAUTHENTICATION_USERNAME == UA_USERTOKENTYPE_USERNAME * TODO: Check equivalence for other types when adding the support */ if((int)client->authenticationMethod != (int)userToken->tokenType) continue; /* Endpoint with matching usertokenpolicy found */ tokenFound = true; UA_UserTokenPolicy_copy(userToken, &client->token); 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"); retval = UA_STATUSCODE_BADINTERNALERROR; } else if(!tokenFound) { UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable UserTokenPolicy found for the possible endpoints"); retval = UA_STATUSCODE_BADINTERNALERROR; } return retval; } static UA_StatusCode SessionHandshake(UA_Client *client) { UA_CreateSessionRequest request; UA_CreateSessionRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; UA_ByteString_copy(&client->channel.clientNonce, &request.clientNonce); request.requestedSessionTimeout = 1200000; request.maxResponseMessageSize = UA_INT32_MAX; UA_String_copy(&client->endpointUrl, &request.endpointUrl); UA_CreateSessionResponse response; UA_CreateSessionResponse_init(&response); __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST], &response, &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE]); UA_NodeId_copy(&response.authenticationToken, &client->authenticationToken); UA_StatusCode retval = response.responseHeader.serviceResult; UA_CreateSessionRequest_deleteMembers(&request); UA_CreateSessionResponse_deleteMembers(&response); return retval; } static UA_StatusCode CloseSession(UA_Client *client) { UA_CloseSessionRequest request; UA_CloseSessionRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; request.deleteSubscriptions = true; UA_CloseSessionResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST], &response, &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE]); UA_StatusCode retval = response.responseHeader.serviceResult; UA_CloseSessionRequest_deleteMembers(&request); UA_CloseSessionResponse_deleteMembers(&response); return retval; } static UA_StatusCode CloseSecureChannel(UA_Client *client) { 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; UA_NodeId_copy(&client->authenticationToken, &request.requestHeader.authenticationToken); UA_SecureConversationMessageHeader msgHeader; msgHeader.messageHeader.messageTypeAndChunkType = UA_MESSAGETYPE_CLO + UA_CHUNKTYPE_FINAL; msgHeader.secureChannelId = channel->securityToken.channelId; UA_SymmetricAlgorithmSecurityHeader symHeader; symHeader.tokenId = channel->securityToken.tokenId; UA_SequenceHeader seqHeader; seqHeader.sequenceNumber = ++channel->sendSequenceNumber; seqHeader.requestId = ++client->requestId; UA_NodeId typeId = UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST].binaryEncodingId); UA_ByteString message; UA_Connection *conn = &client->connection; UA_StatusCode retval = conn->getSendBuffer(conn, conn->remoteConf.recvBufferSize, &message); if(retval != UA_STATUSCODE_GOOD){ UA_CloseSecureChannelRequest_deleteMembers(&request); return retval; } size_t offset = 12; retval |= UA_SymmetricAlgorithmSecurityHeader_encodeBinary(&symHeader, &message, &offset); retval |= UA_SequenceHeader_encodeBinary(&seqHeader, &message, &offset); retval |= UA_NodeId_encodeBinary(&typeId, &message, &offset); retval |= UA_encodeBinary(&request, &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST], NULL, NULL, &message, &offset); msgHeader.messageHeader.messageSize = (UA_UInt32)offset; offset = 0; retval |= UA_SecureConversationMessageHeader_encodeBinary(&msgHeader, &message, &offset); if(retval == UA_STATUSCODE_GOOD) { message.length = msgHeader.messageHeader.messageSize; retval = conn->send(conn, &message); } else { conn->releaseSendBuffer(conn, &message); } conn->close(conn); UA_CloseSecureChannelRequest_deleteMembers(&request); return retval; } UA_StatusCode UA_Client_getEndpoints(UA_Client *client, const char *serverUrl, size_t* endpointDescriptionsSize, UA_EndpointDescription** endpointDescriptions) { if(client->state == UA_CLIENTSTATE_CONNECTED) return UA_STATUSCODE_GOOD; if(client->state == UA_CLIENTSTATE_ERRORED) UA_Client_reset(client); UA_StatusCode retval = UA_STATUSCODE_GOOD; client->connection = client->config.connectionFunc(UA_ConnectionConfig_standard, serverUrl, client->config.logger); if(client->connection.state != UA_CONNECTION_OPENING) { retval = UA_STATUSCODE_BADCONNECTIONCLOSED; goto cleanup; } client->endpointUrl = UA_STRING_ALLOC(serverUrl); if(!client->endpointUrl.data) { retval = UA_STATUSCODE_BADOUTOFMEMORY; goto cleanup; } client->connection.localConf = client->config.localConnectionConfig; retval = HelAckHandshake(client); if(retval == UA_STATUSCODE_GOOD) retval = SecureChannelHandshake(client, false); if(retval == UA_STATUSCODE_GOOD) retval = GetEndpoints(client, endpointDescriptionsSize, endpointDescriptions); /* always cleanup */ cleanup: UA_Client_disconnect(client); UA_Client_reset(client); return retval; } UA_StatusCode UA_Client_connect_username(UA_Client *client, const char *endpointUrl, const char *username, const char *password){ client->authenticationMethod=UA_CLIENTAUTHENTICATION_USERNAME; client->username = UA_STRING_ALLOC(username); client->password = UA_STRING_ALLOC(password); return UA_Client_connect(client, endpointUrl); } UA_StatusCode UA_Client_connect(UA_Client *client, const char *endpointUrl) { if(client->state == UA_CLIENTSTATE_CONNECTED) return UA_STATUSCODE_GOOD; if(client->state == UA_CLIENTSTATE_ERRORED) { UA_Client_reset(client); } UA_StatusCode retval = UA_STATUSCODE_GOOD; client->connection = client->config.connectionFunc(UA_ConnectionConfig_standard, endpointUrl, client->config.logger); if(client->connection.state != UA_CONNECTION_OPENING) { retval = UA_STATUSCODE_BADCONNECTIONCLOSED; goto cleanup; } client->endpointUrl = UA_STRING_ALLOC(endpointUrl); if(!client->endpointUrl.data) { retval = UA_STATUSCODE_BADOUTOFMEMORY; goto cleanup; } client->connection.localConf = client->config.localConnectionConfig; retval = HelAckHandshake(client); if(retval == UA_STATUSCODE_GOOD) retval = SecureChannelHandshake(client, false); if(retval == UA_STATUSCODE_GOOD) retval = EndpointsHandshake(client); if(retval == UA_STATUSCODE_GOOD) retval = SessionHandshake(client); if(retval == UA_STATUSCODE_GOOD) retval = ActivateSession(client); if(retval == UA_STATUSCODE_GOOD) { client->connection.state = UA_CONNECTION_ESTABLISHED; client->state = UA_CLIENTSTATE_CONNECTED; } else { goto cleanup; } return retval; cleanup: UA_Client_reset(client); return retval; } UA_StatusCode UA_Client_disconnect(UA_Client *client) { if(client->state == UA_CLIENTSTATE_READY) return UA_STATUSCODE_BADNOTCONNECTED; UA_StatusCode retval = UA_STATUSCODE_GOOD; /* Is a session established? */ if(client->connection.state == UA_CONNECTION_ESTABLISHED && !UA_NodeId_equal(&client->authenticationToken, &UA_NODEID_NULL)) retval = CloseSession(client); /* Is a secure channel established? */ if(client->connection.state == UA_CONNECTION_ESTABLISHED) retval |= CloseSecureChannel(client); return retval; } UA_StatusCode UA_Client_manuallyRenewSecureChannel(UA_Client *client) { UA_StatusCode retval = SecureChannelHandshake(client, true); if(retval == UA_STATUSCODE_GOOD) client->state = UA_CLIENTSTATE_CONNECTED; return retval; } /****************/ /* Raw Services */ /****************/ struct ResponseDescription { UA_Client *client; UA_Boolean processed; UA_UInt32 requestId; void *response; const UA_DataType *responseType; }; static void processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel, UA_MessageType messageType, UA_UInt32 requestId, UA_ByteString *message) { const UA_NodeId expectedNodeId = UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId); const UA_NodeId serviceFaultNodeId = UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_SERVICEFAULT].binaryEncodingId); UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_ResponseHeader *respHeader = (UA_ResponseHeader*)rd->response; rd->processed = true; if(messageType != UA_MESSAGETYPE_MSG) { UA_LOG_ERROR(rd->client->config.logger, UA_LOGCATEGORY_CLIENT, "Server replied with the wrong message type"); retval = UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; goto finish; } /* Check that the request id matches */ /* Todo: we need to demux async responses since a publish responses may come at any time */ if(requestId != rd->requestId) { UA_LOG_ERROR(rd->client->config.logger, UA_LOGCATEGORY_CLIENT, "Reply answers the wrong requestId. " "Async services are not yet implemented."); retval = UA_STATUSCODE_BADINTERNALERROR; goto finish; } /* Check that the response type matches */ size_t offset = 0; UA_NodeId responseId; retval = UA_NodeId_decodeBinary(message, &offset, &responseId); if(retval != UA_STATUSCODE_GOOD) goto finish; if(!UA_NodeId_equal(&responseId, &expectedNodeId)) { if(UA_NodeId_equal(&responseId, &serviceFaultNodeId)) { /* Take the statuscode from the servicefault */ retval = UA_decodeBinary(message, &offset, rd->response, &UA_TYPES[UA_TYPES_SERVICEFAULT]); } else { UA_LOG_ERROR(rd->client->config.logger, UA_LOGCATEGORY_CLIENT, "Reply answers the wrong request. Expected ns=%i,i=%i." "But retrieved ns=%i,i=%i", expectedNodeId.namespaceIndex, expectedNodeId.identifier.numeric, responseId.namespaceIndex, responseId.identifier.numeric); UA_NodeId_deleteMembers(&responseId); retval = UA_STATUSCODE_BADINTERNALERROR; } goto finish; } /* Decode the response */ retval = UA_decodeBinary(message, &offset, rd->response, rd->responseType); finish: if(retval == UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(rd->client->config.logger, UA_LOGCATEGORY_CLIENT, "Received a response of type %i", responseId.identifier.numeric); } else { if(retval == UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED) retval = UA_STATUSCODE_BADRESPONSETOOLARGE; UA_LOG_INFO(rd->client->config.logger, UA_LOGCATEGORY_CLIENT, "Error receiving the response"); respHeader->serviceResult = retval; } } void __UA_Client_Service(UA_Client *client, const void *request, const UA_DataType *requestType, void *response, const UA_DataType *responseType) { UA_init(response, responseType); UA_ResponseHeader *respHeader = (UA_ResponseHeader*)response; /* Make sure we have a valid session */ UA_StatusCode retval = UA_Client_manuallyRenewSecureChannel(client); if(retval != UA_STATUSCODE_GOOD) { respHeader->serviceResult = retval; client->state = UA_CLIENTSTATE_ERRORED; return; } /* Adjusting the request header. The const attribute is violated, but we * only touch the following members: */ UA_RequestHeader *rr = (UA_RequestHeader*)(uintptr_t)request; rr->authenticationToken = client->authenticationToken; /* cleaned up at the end */ rr->timestamp = UA_DateTime_now(); rr->requestHandle = ++client->requestHandle; /* Send the request */ UA_UInt32 requestId = ++client->requestId; UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT, "Sending a request of type %i", requestType->typeId.identifier.numeric); retval = UA_SecureChannel_sendBinaryMessage(&client->channel, requestId, rr, requestType); if(retval != UA_STATUSCODE_GOOD) { if(retval == UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED) respHeader->serviceResult = UA_STATUSCODE_BADREQUESTTOOLARGE; else respHeader->serviceResult = retval; client->state = UA_CLIENTSTATE_FAULTED; UA_NodeId_init(&rr->authenticationToken); return; } /* Prepare the response and the structure we give into processServiceResponse */ UA_init(response, responseType); struct ResponseDescription rd = {client, false, requestId, response, responseType}; /* Retrieve the response */ UA_DateTime maxDate = UA_DateTime_nowMonotonic() + (client->config.timeout * UA_MSEC_TO_DATETIME); do { /* Retrieve complete chunks */ UA_ByteString reply = UA_BYTESTRING_NULL; UA_Boolean realloced = false; UA_DateTime now = UA_DateTime_nowMonotonic(); if(now < maxDate) { UA_UInt32 timeout = (UA_UInt32)((maxDate - now) / UA_MSEC_TO_DATETIME); retval = Connection_receiveChunk(&client->connection, &reply, &realloced, timeout); } else { retval = UA_STATUSCODE_GOODNONCRITICALTIMEOUT; } if(retval != UA_STATUSCODE_GOOD) { respHeader->serviceResult = retval; break; } /* ProcessChunks and call processServiceResponse for complete messages */ UA_SecureChannel_processChunks(&client->channel, &reply, (UA_ProcessMessageCallback*)processServiceResponse, &rd); /* Free the received buffer */ if(!realloced) client->connection.releaseRecvBuffer(&client->connection, &reply); else UA_ByteString_deleteMembers(&reply); } while(!rd.processed); /* Clean up the authentication token */ UA_NodeId_init(&rr->authenticationToken); }