|
@@ -42,6 +42,15 @@ static void UA_Client_deleteMembers(UA_Client* client) {
|
|
|
UA_String_deleteMembers(&client->username);
|
|
|
if(client->password.data)
|
|
|
UA_String_deleteMembers(&client->password);
|
|
|
+
|
|
|
+ /* Delete the async service calls */
|
|
|
+ AsyncServiceCall *ac, *ac_tmp;
|
|
|
+ LIST_FOREACH_SAFE(ac, &client->asyncServiceCalls, pointers, ac_tmp) {
|
|
|
+ LIST_REMOVE(ac, pointers);
|
|
|
+ UA_free(ac);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Delete the subscriptions */
|
|
|
#ifdef UA_ENABLE_SUBSCRIPTIONS
|
|
|
UA_Client_NotificationsAckNumber *n, *tmp;
|
|
|
LIST_FOREACH_SAFE(n, &client->pendingNotificationsAcks, listEntry, tmp) {
|
|
@@ -654,60 +663,135 @@ UA_StatusCode UA_Client_manuallyRenewSecureChannel(UA_Client *client) {
|
|
|
/* Raw Services */
|
|
|
/****************/
|
|
|
|
|
|
-struct ResponseDescription {
|
|
|
+/* For both synchronous and asynchronous service calls */
|
|
|
+static UA_StatusCode
|
|
|
+sendServiceRequest(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)
|
|
|
+ return retval;
|
|
|
+
|
|
|
+ /* 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 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);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ return retval;
|
|
|
+
|
|
|
+ *requestId = rqId;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+/* 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) {
|
|
|
+ /* Find the callback */
|
|
|
+ AsyncServiceCall *ac;
|
|
|
+ LIST_FOREACH(ac, &client->asyncServiceCalls, pointers) {
|
|
|
+ if(ac->requestId == requestId)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if(!ac)
|
|
|
+ return UA_STATUSCODE_BADREQUESTHEADERINVALID;
|
|
|
+
|
|
|
+ /* Decode the response */
|
|
|
+ void *response = UA_alloca(ac->responseType->memSize);
|
|
|
+ UA_StatusCode retval = UA_decodeBinary(responseMessage, offset, response,
|
|
|
+ ac->responseType, 0, NULL);
|
|
|
+
|
|
|
+ /* Call the callback */
|
|
|
+ if(retval == UA_STATUSCODE_GOOD) {
|
|
|
+ ac->callback(client, ac->userdata, requestId, response);
|
|
|
+ UA_deleteMembers(response, ac->responseType);
|
|
|
+ } else {
|
|
|
+ UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
|
|
|
+ "Could not decodee the response with Id %u", requestId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Remove the callback */
|
|
|
+ LIST_REMOVE(ac, pointers);
|
|
|
+ UA_free(ac);
|
|
|
+ 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 processed;
|
|
|
+ UA_Boolean received;
|
|
|
UA_UInt32 requestId;
|
|
|
void *response;
|
|
|
const UA_DataType *responseType;
|
|
|
-};
|
|
|
+} SyncResponseDescription;
|
|
|
|
|
|
static void
|
|
|
-processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel,
|
|
|
+processServiceResponse(SyncResponseDescription *rd, UA_SecureChannel *channel,
|
|
|
UA_MessageType messageType, UA_UInt32 requestId,
|
|
|
UA_ByteString *message) {
|
|
|
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
- const UA_NodeId expectedNodeId =
|
|
|
- UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
|
|
|
+ UA_NodeId expectedNodeId;
|
|
|
const UA_NodeId serviceFaultNodeId =
|
|
|
UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_SERVICEFAULT].binaryEncodingId);
|
|
|
|
|
|
UA_ResponseHeader *respHeader = (UA_ResponseHeader*)rd->response;
|
|
|
- rd->processed = true;
|
|
|
|
|
|
/* Forward declaration for the goto */
|
|
|
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), msg->reason.length, msg->reason.data);
|
|
|
+ UA_StatusCode_name(msg->error), msg->reason.length,
|
|
|
+ msg->reason.data);
|
|
|
retval = msg->error;
|
|
|
goto finish;
|
|
|
- } else if(messageType != UA_MESSAGETYPE_MSG) {
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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;
|
|
|
}
|
|
|
|
|
|
- /* 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;
|
|
|
+ /* Decode the data type identifier of the response */
|
|
|
+ retval = UA_NodeId_decodeBinary(message, &offset, &responseId);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ goto finish;
|
|
|
+
|
|
|
+ /* Got an asynchronous response. Don't expected a synchronous response
|
|
|
+ * (responseType NULL) or the id does not match. */
|
|
|
+ if(!rd->responseType || requestId != rd->requestId) {
|
|
|
+ retval = processAsyncResponse(rd->client, requestId, &responseId, message, &offset);
|
|
|
goto finish;
|
|
|
}
|
|
|
|
|
|
+ /* Got the synchronous response */
|
|
|
+ rd->received= true;
|
|
|
+
|
|
|
/* Check that the response type matches */
|
|
|
- retval = UA_NodeId_decodeBinary(message, &offset, &responseId);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- goto finish;
|
|
|
+ expectedNodeId = UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
|
|
|
if(!UA_NodeId_equal(&responseId, &expectedNodeId)) {
|
|
|
if(UA_NodeId_equal(&responseId, &serviceFaultNodeId)) {
|
|
|
/* Take the statuscode from the servicefault */
|
|
@@ -719,7 +803,6 @@ processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel
|
|
|
"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;
|
|
@@ -731,6 +814,7 @@ processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel
|
|
|
rd->client->config.customDataTypes);
|
|
|
|
|
|
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);
|
|
@@ -743,63 +827,32 @@ processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-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;
|
|
|
- }
|
|
|
-
|
|
|
+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 */
|
|
|
- UA_init(response, responseType);
|
|
|
- struct ResponseDescription rd = {client, false, requestId, 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;
|
|
|
|
|
|
- /* 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 = UA_Connection_receiveChunksBlocking(&client->connection, &reply, &realloced, timeout);
|
|
|
- } else {
|
|
|
- retval = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
|
|
|
- }
|
|
|
- if(retval != UA_STATUSCODE_GOOD) {
|
|
|
- respHeader->serviceResult = retval;
|
|
|
- break;
|
|
|
- }
|
|
|
+ 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, &reply,
|
|
|
+ &realloced, timeout);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ return retval;
|
|
|
+
|
|
|
/* ProcessChunks and call processServiceResponse for complete messages */
|
|
|
UA_SecureChannel_processChunks(&client->channel, &reply,
|
|
|
(UA_ProcessMessageCallback*)processServiceResponse, &rd);
|
|
@@ -808,8 +861,73 @@ __UA_Client_Service(UA_Client *client, const void *request, const UA_DataType *r
|
|
|
client->connection.releaseRecvBuffer(&client->connection, &reply);
|
|
|
else
|
|
|
UA_ByteString_deleteMembers(&reply);
|
|
|
- } while(!rd.processed);
|
|
|
+ } while(!rd.received);
|
|
|
|
|
|
- /* Clean up the authentication token */
|
|
|
- UA_NodeId_init(&rr->authenticationToken);
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+
|
|
|
+ /* Send the request */
|
|
|
+ UA_UInt32 requestId;
|
|
|
+ UA_StatusCode retval = sendServiceRequest(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;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Retrieve the response */
|
|
|
+ UA_DateTime maxDate = UA_DateTime_nowMonotonic() +
|
|
|
+ (client->config.timeout * UA_MSEC_TO_DATETIME);
|
|
|
+ retval = receiveServiceResponse(client, response, responseType, maxDate, &requestId);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ respHeader->serviceResult = retval;
|
|
|
+}
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+__UA_Client_AsyncService(UA_Client *client, const void *request,
|
|
|
+ const UA_DataType *requestType,
|
|
|
+ UA_ClientAsyncServiceCallback callback,
|
|
|
+ const UA_DataType *responseType,
|
|
|
+ void *userdata, UA_UInt32 *requestId) {
|
|
|
+ /* Prepare the entry for the linked list */
|
|
|
+ AsyncServiceCall *ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall));
|
|
|
+ if(!ac)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ ac->callback = callback;
|
|
|
+ ac->responseType = responseType;
|
|
|
+ ac->userdata = userdata;
|
|
|
+
|
|
|
+ /* Call the service and set the requestId */
|
|
|
+ UA_StatusCode retval = sendServiceRequest(client, request, requestType, &ac->requestId);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD) {
|
|
|
+ UA_free(ac);
|
|
|
+ return retval;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Store the entry for async processing */
|
|
|
+ LIST_INSERT_HEAD(&client->asyncServiceCalls, ac, pointers);
|
|
|
+ if(requestId)
|
|
|
+ *requestId = ac->requestId;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+UA_Client_runAsync(UA_Client *client, UA_UInt16 timeout) {
|
|
|
+ /* TODO: Call repeated jobs that are scheduled */
|
|
|
+ UA_DateTime maxDate = UA_DateTime_nowMonotonic() +
|
|
|
+ (timeout * UA_MSEC_TO_DATETIME);
|
|
|
+ UA_StatusCode retval = receiveServiceResponse(client, NULL, NULL, maxDate, NULL);
|
|
|
+ if(retval == UA_STATUSCODE_GOODNONCRITICALTIMEOUT)
|
|
|
+ retval = UA_STATUSCODE_GOOD;
|
|
|
+ return retval;
|
|
|
}
|