|
@@ -7,31 +7,36 @@
|
|
|
#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_securitypolicy_none.h"
|
|
|
|
|
|
-/*********************/
|
|
|
-/* Create and Delete */
|
|
|
-/*********************/
|
|
|
+ /********************/
|
|
|
+ /* Client Lifecycle */
|
|
|
+ /********************/
|
|
|
|
|
|
-static void UA_Client_init(UA_Client* client, UA_ClientConfig config) {
|
|
|
+static void
|
|
|
+UA_Client_init(UA_Client* client, UA_ClientConfig config) {
|
|
|
memset(client, 0, sizeof(UA_Client));
|
|
|
- client->channel.connection = &client->connection;
|
|
|
+ /* TODO: Select policy according to the endpoint */
|
|
|
+ UA_SecurityPolicy_None(&client->securityPolicy, UA_BYTESTRING_NULL, config.logger);
|
|
|
+ client->channel.securityPolicy = &client->securityPolicy;
|
|
|
+ client->channel.securityMode = UA_MESSAGESECURITYMODE_NONE;
|
|
|
client->config = config;
|
|
|
}
|
|
|
|
|
|
-UA_Client * UA_Client_new(UA_ClientConfig config) {
|
|
|
- UA_Client *client = (UA_Client*)UA_calloc(1, sizeof(UA_Client));
|
|
|
+UA_Client *
|
|
|
+UA_Client_new(UA_ClientConfig config) {
|
|
|
+ UA_Client *client = (UA_Client*)UA_malloc(sizeof(UA_Client));
|
|
|
if(!client)
|
|
|
return NULL;
|
|
|
UA_Client_init(client, config);
|
|
|
return client;
|
|
|
}
|
|
|
|
|
|
-static void UA_Client_deleteMembers(UA_Client* client) {
|
|
|
+static void
|
|
|
+UA_Client_deleteMembers(UA_Client* client) {
|
|
|
UA_Client_disconnect(client);
|
|
|
+ client->securityPolicy.deleteMembers(&client->securityPolicy);
|
|
|
UA_SecureChannel_deleteMembersCleanup(&client->channel);
|
|
|
UA_Connection_deleteMembers(&client->connection);
|
|
|
if(client->endpointUrl.data)
|
|
@@ -63,616 +68,42 @@ static void UA_Client_deleteMembers(UA_Client* client) {
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
-void UA_Client_reset(UA_Client* client){
|
|
|
+void
|
|
|
+UA_Client_reset(UA_Client* client) {
|
|
|
UA_Client_deleteMembers(client);
|
|
|
UA_Client_init(client, client->config);
|
|
|
}
|
|
|
|
|
|
-void UA_Client_delete(UA_Client* client){
|
|
|
+void
|
|
|
+UA_Client_delete(UA_Client* client) {
|
|
|
UA_Client_deleteMembers(client);
|
|
|
UA_free(client);
|
|
|
}
|
|
|
|
|
|
-UA_ClientState UA_Client_getState(UA_Client *client) {
|
|
|
- if(!client)
|
|
|
- return UA_CLIENTSTATE_ERRORED;
|
|
|
+UA_ClientState
|
|
|
+UA_Client_getState(UA_Client *client) {
|
|
|
return client->state;
|
|
|
}
|
|
|
|
|
|
-/*************************/
|
|
|
-/* Manage the Connection */
|
|
|
-/*************************/
|
|
|
-
|
|
|
-#define UA_MINMESSAGESIZE 8192
|
|
|
-
|
|
|
-static void
|
|
|
-processHelResponse(void *application, UA_Connection *connection,
|
|
|
- const UA_ByteString *chunk) {
|
|
|
- UA_Client *client = (UA_Client*)application;
|
|
|
-
|
|
|
- /* Decode the message */
|
|
|
- size_t offset = 0;
|
|
|
- UA_StatusCode retval;
|
|
|
- UA_TcpMessageHeader messageHeader;
|
|
|
- UA_TcpAcknowledgeMessage ackMessage;
|
|
|
- retval = UA_TcpMessageHeader_decodeBinary(chunk, &offset, &messageHeader);
|
|
|
- retval |= UA_TcpAcknowledgeMessage_decodeBinary(chunk, &offset, &ackMessage);
|
|
|
-
|
|
|
- /* 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");
|
|
|
- connection->remoteConf.maxChunkCount = ackMessage.maxChunkCount; /* may be zero -> unlimited */
|
|
|
- connection->remoteConf.maxMessageSize = ackMessage.maxMessageSize; /* may be zero -> unlimited */
|
|
|
- connection->remoteConf.protocolVersion = ackMessage.protocolVersion;
|
|
|
- connection->remoteConf.sendBufferSize = ackMessage.sendBufferSize;
|
|
|
- connection->remoteConf.recvBufferSize = ackMessage.receiveBufferSize;
|
|
|
- if(connection->remoteConf.recvBufferSize < connection->localConf.sendBufferSize)
|
|
|
- connection->localConf.sendBufferSize = connection->remoteConf.recvBufferSize;
|
|
|
- if(connection->remoteConf.sendBufferSize < connection->localConf.recvBufferSize)
|
|
|
- connection->localConf.recvBufferSize = connection->remoteConf.sendBufferSize;
|
|
|
- connection->state = UA_CONNECTION_ESTABLISHED;
|
|
|
- } else {
|
|
|
- UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK, "Decoding ACK message failed");
|
|
|
- }
|
|
|
- UA_TcpAcknowledgeMessage_deleteMembers(&ackMessage);
|
|
|
-}
|
|
|
-
|
|
|
-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;
|
|
|
-
|
|
|
- UA_Byte *bufPos = &message.data[8]; /* skip the header */
|
|
|
- const UA_Byte *bufEnd = &message.data[message.length];
|
|
|
- retval = 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_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 */
|
|
|
- retval = UA_Connection_receiveChunksBlocking(conn, client, processHelResponse,
|
|
|
- client->config.timeout);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK,
|
|
|
- "Receiving ACK message failed");
|
|
|
- return retval;
|
|
|
-}
|
|
|
-
|
|
|
-struct OPNResponseData {
|
|
|
- UA_Client *client;
|
|
|
- UA_Boolean renew;
|
|
|
-};
|
|
|
-
|
|
|
-static void
|
|
|
-processOPNResponse(void *data, UA_Connection *connection,
|
|
|
- const UA_ByteString *chunk) {
|
|
|
- struct OPNResponseData *rd = (struct OPNResponseData*)data;
|
|
|
- UA_Client *client = rd->client;
|
|
|
-
|
|
|
- /* Decode the header */
|
|
|
- size_t offset = 0;
|
|
|
- UA_StatusCode retval;
|
|
|
- UA_SecureConversationMessageHeader messageHeader;
|
|
|
- UA_AsymmetricAlgorithmSecurityHeader asymHeader;
|
|
|
- UA_SequenceHeader seqHeader;
|
|
|
- UA_NodeId requestType;
|
|
|
- retval = UA_SecureConversationMessageHeader_decodeBinary(chunk, &offset, &messageHeader);
|
|
|
- retval |= UA_AsymmetricAlgorithmSecurityHeader_decodeBinary(chunk, &offset, &asymHeader);
|
|
|
- retval |= UA_SequenceHeader_decodeBinary(chunk, &offset, &seqHeader);
|
|
|
- retval |= UA_NodeId_decodeBinary(chunk, &offset, &requestType);
|
|
|
- UA_NodeId expectedRequest =
|
|
|
- UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE].binaryEncodingId);
|
|
|
- if(retval != UA_STATUSCODE_GOOD || !UA_NodeId_equal(&requestType, &expectedRequest)) {
|
|
|
- UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader);
|
|
|
- UA_NodeId_deleteMembers(&requestType);
|
|
|
- UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
|
|
|
- "Chunk answers the wrong request. Expected OpenSecureChannelResponse.");
|
|
|
- client->state = UA_CLIENTSTATE_ERRORED;
|
|
|
- }
|
|
|
-
|
|
|
- /* Save the sequence number from server */
|
|
|
- client->channel.receiveSequenceNumber = seqHeader.sequenceNumber;
|
|
|
-
|
|
|
- /* Decode the response */
|
|
|
- UA_OpenSecureChannelResponse response;
|
|
|
- retval = UA_OpenSecureChannelResponse_decodeBinary(chunk, &offset, &response);
|
|
|
-
|
|
|
- /* 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(rd->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(rd->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_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader);
|
|
|
- UA_OpenSecureChannelResponse_deleteMembers(&response);
|
|
|
-}
|
|
|
-
|
|
|
-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_ByteString message;
|
|
|
- UA_StatusCode retval = conn->getSendBuffer(conn, conn->remoteConf.recvBufferSize, &message);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- return retval;
|
|
|
-
|
|
|
- /* Jump over the messageHeader that will be encoded last */
|
|
|
- UA_Byte *bufPos = &message.data[12];
|
|
|
- const UA_Byte *bufEnd = &message.data[message.length];
|
|
|
-
|
|
|
- /* Encode the Asymmetric Security Header */
|
|
|
- UA_AsymmetricAlgorithmSecurityHeader asymHeader;
|
|
|
- UA_AsymmetricAlgorithmSecurityHeader_init(&asymHeader);
|
|
|
- asymHeader.securityPolicyUri = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None");
|
|
|
- retval = UA_AsymmetricAlgorithmSecurityHeader_encodeBinary(&asymHeader, &bufPos, &bufEnd);
|
|
|
-
|
|
|
- /* Encode the sequence header */
|
|
|
- UA_SequenceHeader seqHeader;
|
|
|
- seqHeader.sequenceNumber = ++client->channel.sendSequenceNumber;
|
|
|
- seqHeader.requestId = ++client->requestId;
|
|
|
- retval |= UA_SequenceHeader_encodeBinary(&seqHeader, &bufPos, &bufEnd);
|
|
|
-
|
|
|
- /* Encode the NodeId of the OpenSecureChannel Service */
|
|
|
- UA_NodeId requestType =
|
|
|
- UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST].binaryEncodingId);
|
|
|
- retval |= UA_NodeId_encodeBinary(&requestType, &bufPos, &bufEnd);
|
|
|
-
|
|
|
- /* Encode 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 = UA_MESSAGESECURITYMODE_NONE;
|
|
|
- opnSecRq.clientNonce = client->channel.clientNonce;
|
|
|
- opnSecRq.requestedLifetime = client->config.secureChannelLifeTime;
|
|
|
- retval |= UA_OpenSecureChannelRequest_encodeBinary(&opnSecRq, &bufPos, &bufEnd);
|
|
|
-
|
|
|
- /* Encode the message header at the beginning */
|
|
|
- size_t length = (uintptr_t)(bufPos - message.data);
|
|
|
- bufPos = message.data;
|
|
|
- UA_SecureConversationMessageHeader messageHeader;
|
|
|
- messageHeader.messageHeader.messageTypeAndChunkType = UA_MESSAGETYPE_OPN + UA_CHUNKTYPE_FINAL;
|
|
|
- messageHeader.messageHeader.messageSize = (UA_UInt32)length;
|
|
|
- if(renew)
|
|
|
- messageHeader.secureChannelId = client->channel.securityToken.channelId;
|
|
|
- else
|
|
|
- messageHeader.secureChannelId = 0;
|
|
|
- retval |= UA_SecureConversationMessageHeader_encodeBinary(&messageHeader, &bufPos, &bufEnd);
|
|
|
-
|
|
|
- /* Clean up and return if encoding the message failed */
|
|
|
- if(retval != UA_STATUSCODE_GOOD) {
|
|
|
- client->connection.releaseSendBuffer(&client->connection, &message);
|
|
|
- return retval;
|
|
|
- }
|
|
|
-
|
|
|
- /* Send the message */
|
|
|
- message.length = length;
|
|
|
- retval = conn->send(conn, &message);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- return retval;
|
|
|
-
|
|
|
- /* Receive the response */
|
|
|
- struct OPNResponseData rd;
|
|
|
- rd.client = client;
|
|
|
- rd.renew = renew;
|
|
|
- retval = UA_Connection_receiveChunksBlocking(conn, &rd, processOPNResponse,
|
|
|
- client->config.timeout);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
|
|
|
- "Receiving OpenSecureChannelResponse failed");
|
|
|
- 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 error code %s",
|
|
|
- UA_StatusCode_name(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 */
|
|
|
-UA_StatusCode
|
|
|
-__UA_Client_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 error code %s",
|
|
|
- UA_StatusCode_name(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 = __UA_Client_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;
|
|
|
- }
|
|
|
-
|
|
|
- /* Jump over the header */
|
|
|
- UA_Byte *bufPos = &message.data[12];
|
|
|
- const UA_Byte *bufEnd = &message.data[message.length];
|
|
|
-
|
|
|
- /* Encode some more headers and the CloseSecureChannelRequest */
|
|
|
- retval |= UA_SymmetricAlgorithmSecurityHeader_encodeBinary(&symHeader, &bufPos, &bufEnd);
|
|
|
- retval |= UA_SequenceHeader_encodeBinary(&seqHeader, &bufPos, &bufEnd);
|
|
|
- retval |= UA_NodeId_encodeBinary(&typeId, &bufPos, &bufEnd);
|
|
|
- retval |= UA_encodeBinary(&request, &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST],
|
|
|
- &bufPos, &bufEnd, NULL, NULL);
|
|
|
-
|
|
|
- /* Encode the header */
|
|
|
- msgHeader.messageHeader.messageSize = (UA_UInt32)((uintptr_t)bufPos - (uintptr_t)message.data);
|
|
|
- bufPos = message.data;
|
|
|
- retval |= UA_SecureConversationMessageHeader_encodeBinary(&msgHeader, &bufPos, &bufEnd);
|
|
|
-
|
|
|
- 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_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,
|
|
|
- UA_Boolean endpointsHandshake, UA_Boolean createSession) {
|
|
|
- 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(client->config.localConnectionConfig, endpointUrl, client->config.timeout);
|
|
|
- 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(endpointsHandshake && retval == UA_STATUSCODE_GOOD)
|
|
|
- retval = EndpointsHandshake(client);
|
|
|
- if(endpointsHandshake && createSession && retval == UA_STATUSCODE_GOOD)
|
|
|
- retval = SessionHandshake(client);
|
|
|
- if(endpointsHandshake && createSession && 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_connect(UA_Client *client, const char *endpointUrl) {
|
|
|
- return __UA_Client_connect(client, endpointUrl, UA_TRUE, UA_TRUE);
|
|
|
-}
|
|
|
-
|
|
|
-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 */
|
|
|
/****************/
|
|
|
|
|
|
+/* For synchronous service calls. Execute async responses with a callback. When
|
|
|
+ * the response with the correct requestId turns up, return it via the
|
|
|
+ * SyncResponseDescription pointer. */
|
|
|
+typedef struct {
|
|
|
+ UA_Client *client;
|
|
|
+ UA_Boolean received;
|
|
|
+ UA_UInt32 requestId;
|
|
|
+ void *response;
|
|
|
+ const UA_DataType *responseType;
|
|
|
+} SyncResponseDescription;
|
|
|
+
|
|
|
/* For both synchronous and asynchronous service calls */
|
|
|
static UA_StatusCode
|
|
|
-sendServiceRequest(UA_Client *client, const void *request,
|
|
|
- const UA_DataType *requestType, UA_UInt32 *requestId) {
|
|
|
+sendSymmetricServiceRequest(UA_Client *client, const void *request,
|
|
|
+ const UA_DataType *requestType, UA_UInt32 *requestId) {
|
|
|
/* Make sure we have a valid session */
|
|
|
UA_StatusCode retval = UA_Client_manuallyRenewSecureChannel(client);
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
@@ -689,8 +120,9 @@ sendServiceRequest(UA_Client *client, const void *request,
|
|
|
UA_UInt32 rqId = ++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, rqId, rr, requestType);
|
|
|
- UA_NodeId_init(&rr->authenticationToken);
|
|
|
+ retval = UA_SecureChannel_sendSymmetricMessage(&client->channel, rqId, UA_MESSAGETYPE_MSG,
|
|
|
+ rr, requestType);
|
|
|
+ UA_NodeId_init(&rr->authenticationToken); /* Do not return the token to the user */
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
|
return retval;
|
|
|
|
|
@@ -700,9 +132,8 @@ sendServiceRequest(UA_Client *client, const void *request,
|
|
|
|
|
|
/* Look for the async callback in the linked list, execute and delete it */
|
|
|
static UA_StatusCode
|
|
|
-processAsyncResponse(UA_Client *client, UA_UInt32 requestId,
|
|
|
- UA_NodeId *responseTypeId, UA_ByteString *responseMessage,
|
|
|
- size_t *offset) {
|
|
|
+processAsyncResponse(UA_Client *client, UA_UInt32 requestId, UA_NodeId *responseTypeId,
|
|
|
+ const UA_ByteString *responseMessage, size_t *offset) {
|
|
|
/* Find the callback */
|
|
|
AsyncServiceCall *ac;
|
|
|
LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) {
|
|
@@ -732,57 +163,32 @@ processAsyncResponse(UA_Client *client, UA_UInt32 requestId,
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
-/* For synchronous service calls. Execute async responses until the response
|
|
|
- * with the correct requestId turns up. Then, the response is decoded into the
|
|
|
- * "response" pointer. If the responseType is NULL, just process responses as
|
|
|
- * async. */
|
|
|
-typedef struct {
|
|
|
- UA_Client *client;
|
|
|
- UA_Boolean received;
|
|
|
- UA_UInt32 requestId;
|
|
|
- void *response;
|
|
|
- const UA_DataType *responseType;
|
|
|
-} SyncResponseDescription;
|
|
|
-
|
|
|
-static void
|
|
|
-processServiceResponse(SyncResponseDescription *rd, UA_SecureChannel *channel,
|
|
|
+/* Processes the received service response. Either with an async callback or by
|
|
|
+ * decoding the message and returning it "upwards" in the
|
|
|
+ * SyncResponseDescription. */
|
|
|
+static UA_StatusCode
|
|
|
+processServiceResponse(void *application, UA_SecureChannel *channel,
|
|
|
UA_MessageType messageType, UA_UInt32 requestId,
|
|
|
- UA_ByteString *message) {
|
|
|
- UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
+ const UA_ByteString *message) {
|
|
|
+ SyncResponseDescription *rd = (SyncResponseDescription*)application;
|
|
|
+
|
|
|
+ /* Must be OPN or MSG */
|
|
|
+ if(messageType != UA_MESSAGETYPE_OPN &&
|
|
|
+ messageType != UA_MESSAGETYPE_MSG) {
|
|
|
+ UA_LOG_TRACE_CHANNEL(rd->client->config.logger, channel,
|
|
|
+ "Invalid message type");
|
|
|
+ return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Forward declaration for the goto */
|
|
|
UA_NodeId expectedNodeId;
|
|
|
const UA_NodeId serviceFaultNodeId =
|
|
|
UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_SERVICEFAULT].binaryEncodingId);
|
|
|
|
|
|
- UA_ResponseHeader *respHeader = (UA_ResponseHeader*)rd->response;
|
|
|
-
|
|
|
- /* Forward declaration for the goto */
|
|
|
+ /* Decode the data type identifier of the response */
|
|
|
size_t offset = 0;
|
|
|
UA_NodeId responseId;
|
|
|
- UA_NodeId_init(&responseId);
|
|
|
-
|
|
|
- /* Got an error.
|
|
|
- * TODO: Return as part of the response header */
|
|
|
- if(messageType == UA_MESSAGETYPE_ERR) {
|
|
|
- UA_TcpErrorMessage *msg = (UA_TcpErrorMessage*)message;
|
|
|
- UA_LOG_ERROR(rd->client->config.logger, UA_LOGCATEGORY_CLIENT,
|
|
|
- "Server replied with an error message: %s %.*s",
|
|
|
- UA_StatusCode_name(msg->error), (int)msg->reason.length,
|
|
|
- msg->reason.data);
|
|
|
- retval = msg->error;
|
|
|
- goto finish;
|
|
|
- }
|
|
|
-
|
|
|
- /* Unexpected response type.
|
|
|
- * TODO: How to process valid OPN responses? */
|
|
|
- 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;
|
|
|
- }
|
|
|
-
|
|
|
- /* Decode the data type identifier of the response */
|
|
|
- retval = UA_NodeId_decodeBinary(message, &offset, &responseId);
|
|
|
+ UA_StatusCode retval = UA_NodeId_decodeBinary(message, &offset, &responseId);
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
|
goto finish;
|
|
|
|
|
@@ -794,33 +200,32 @@ processServiceResponse(SyncResponseDescription *rd, UA_SecureChannel *channel,
|
|
|
}
|
|
|
|
|
|
/* Got the synchronous response */
|
|
|
- rd->received= true;
|
|
|
+ rd->received = true;
|
|
|
|
|
|
/* Check that the response type matches */
|
|
|
expectedNodeId = UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
|
|
|
- if(!UA_NodeId_equal(&responseId, &expectedNodeId)) {
|
|
|
+ if(UA_NodeId_equal(&responseId, &expectedNodeId)) {
|
|
|
+ /* Decode the response */
|
|
|
+ retval = UA_decodeBinary(message, &offset, rd->response, rd->responseType,
|
|
|
+ rd->client->config.customDataTypesSize,
|
|
|
+ rd->client->config.customDataTypes);
|
|
|
+ } else {
|
|
|
+ UA_LOG_ERROR(rd->client->config.logger, UA_LOGCATEGORY_CLIENT,
|
|
|
+ "Reply contains the wrong service response");
|
|
|
if(UA_NodeId_equal(&responseId, &serviceFaultNodeId)) {
|
|
|
- /* Take the statuscode from the servicefault */
|
|
|
+ /* Decode only the message header with the servicefault */
|
|
|
retval = UA_decodeBinary(message, &offset, rd->response,
|
|
|
&UA_TYPES[UA_TYPES_SERVICEFAULT], 0, NULL);
|
|
|
} 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);
|
|
|
- retval = UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+ /* Close the connection */
|
|
|
+ retval = UA_STATUSCODE_BADCOMMUNICATIONERROR;
|
|
|
}
|
|
|
- goto finish;
|
|
|
}
|
|
|
|
|
|
- /* Decode the response */
|
|
|
- retval = UA_decodeBinary(message, &offset, rd->response, rd->responseType,
|
|
|
- rd->client->config.customDataTypesSize,
|
|
|
- rd->client->config.customDataTypes);
|
|
|
|
|
|
- finish:
|
|
|
+finish:
|
|
|
UA_NodeId_deleteMembers(&responseId);
|
|
|
+
|
|
|
if(retval == UA_STATUSCODE_GOOD) {
|
|
|
UA_LOG_DEBUG(rd->client->config.logger, UA_LOGCATEGORY_CLIENT,
|
|
|
"Received a response of type %i", responseId.identifier.numeric);
|
|
@@ -828,49 +233,53 @@ processServiceResponse(SyncResponseDescription *rd, UA_SecureChannel *channel,
|
|
|
if(retval == UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED)
|
|
|
retval = UA_STATUSCODE_BADRESPONSETOOLARGE;
|
|
|
UA_LOG_INFO(rd->client->config.logger, UA_LOGCATEGORY_CLIENT,
|
|
|
- "Error receiving the response");
|
|
|
+ "Error receiving the response with status code %s",
|
|
|
+ UA_StatusCode_name(retval));
|
|
|
+ UA_ResponseHeader *respHeader = (UA_ResponseHeader*)rd->response;
|
|
|
respHeader->serviceResult = retval;
|
|
|
}
|
|
|
+ return retval;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-processChunk(void *application, UA_Connection *connection,
|
|
|
- const UA_ByteString *chunk) {
|
|
|
+/* Forward complete chunks directly to the securechannel */
|
|
|
+static UA_StatusCode
|
|
|
+client_processChunk(void *application, UA_Connection *connection, UA_ByteString *chunk) {
|
|
|
SyncResponseDescription *rd = (SyncResponseDescription*)application;
|
|
|
- UA_SecureChannel_processChunk(&rd->client->channel, chunk,
|
|
|
- (UA_ProcessMessageCallback*)processServiceResponse, rd);
|
|
|
+ return UA_SecureChannel_processChunk(&rd->client->channel, chunk,
|
|
|
+ processServiceResponse,
|
|
|
+ rd);
|
|
|
}
|
|
|
|
|
|
+/* Receive and process messages until a synchronous message arrives or the
|
|
|
+ * timout finishes */
|
|
|
static UA_StatusCode
|
|
|
receiveServiceResponse(UA_Client *client, void *response, const UA_DataType *responseType,
|
|
|
UA_DateTime maxDate, UA_UInt32 *synchronousRequestId) {
|
|
|
/* Prepare the response and the structure we give into processServiceResponse */
|
|
|
- SyncResponseDescription rd = {client, false, 0, response, responseType};
|
|
|
+ SyncResponseDescription rd = { client, false, 0, response, responseType };
|
|
|
|
|
|
/* Return upon receiving the synchronized response. All other responses are
|
|
|
* processed with a callback "in the background". */
|
|
|
if(synchronousRequestId)
|
|
|
rd.requestId = *synchronousRequestId;
|
|
|
|
|
|
+ UA_StatusCode retval;
|
|
|
do {
|
|
|
- /* Retrieve complete chunks */
|
|
|
UA_DateTime now = UA_DateTime_nowMonotonic();
|
|
|
if(now > maxDate)
|
|
|
return UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
|
|
|
UA_UInt32 timeout = (UA_UInt32)((maxDate - now) / UA_MSEC_TO_DATETIME);
|
|
|
- UA_StatusCode retval =
|
|
|
- UA_Connection_receiveChunksBlocking(&client->connection, &rd,
|
|
|
- processChunk, timeout);
|
|
|
+ retval = UA_Connection_receiveChunksBlocking(&client->connection, &rd,
|
|
|
+ client_processChunk, timeout);
|
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
|
- if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED) {
|
|
|
- /* set client to error state so that call to connect will force
|
|
|
- * a new connection attempt */
|
|
|
- client->state = UA_CLIENTSTATE_ERRORED;
|
|
|
- }
|
|
|
- return retval;
|
|
|
+ if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED)
|
|
|
+ client->state = UA_CLIENTSTATE_DISCONNECTED;
|
|
|
+ else
|
|
|
+ UA_Client_disconnect(client);
|
|
|
+ break;
|
|
|
}
|
|
|
} while(!rd.received);
|
|
|
- return UA_STATUSCODE_GOOD;
|
|
|
+ return retval;
|
|
|
}
|
|
|
|
|
|
void
|
|
@@ -882,13 +291,13 @@ __UA_Client_Service(UA_Client *client, const void *request,
|
|
|
|
|
|
/* Send the request */
|
|
|
UA_UInt32 requestId;
|
|
|
- UA_StatusCode retval = sendServiceRequest(client, request, requestType, &requestId);
|
|
|
+ UA_StatusCode retval = sendSymmetricServiceRequest(client, request, requestType, &requestId);
|
|
|
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_Client_disconnect(client);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -915,7 +324,7 @@ __UA_Client_AsyncService(UA_Client *client, const void *request,
|
|
|
ac->userdata = userdata;
|
|
|
|
|
|
/* Call the service and set the requestId */
|
|
|
- UA_StatusCode retval = sendServiceRequest(client, request, requestType, &ac->requestId);
|
|
|
+ UA_StatusCode retval = sendSymmetricServiceRequest(client, request, requestType, &ac->requestId);
|
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
|
UA_free(ac);
|
|
|
return retval;
|