Forráskód Böngészése

Merge branch '0.2'

Julius Pfrommer 7 éve
szülő
commit
03ad243612

+ 7 - 7
FEATURES.md

@@ -5,10 +5,10 @@ open62541 Supported Features
 |:----------------------------|:--------------------------------|:--------------------:|:---------------------|
 | Discovery Service Set       |                                 |                      |                      |
 |                             | FindServers()                   |  :white_check_mark:  |                      |
-|                             | FindServersOnNetwork()          |     :full_moon:      | Branch: master       |
+|                             | FindServersOnNetwork()          |     :full_moon:      | Branch: master, Release 0.3  |
 |                             | GetEndpoints()                  |  :white_check_mark:  |                      |
 |                             | RegisterServer()                |  :white_check_mark:  |                      |
-|                             | RegisterServer2()               |     :full_moon:      | Branch: master       |
+|                             | RegisterServer2()               |     :full_moon:      | Branch: master, Release 0.3  |
 | Secure Channel Service Set  |                                 |                      |                      |
 |                             | OpenSecureChannel()             |  :white_check_mark:  |                      |
 |                             | CloseSecureChannel()            |  :white_check_mark:  |                      |
@@ -63,9 +63,9 @@ open62541 Supported Features
 | SOAP-HTTP WS-SC UA XML-UA Binary        |      :new_moon:      |                      |
 | **Encryption**                          |                      |                      |
 | None                                    |  :white_check_mark:  |                      |
-| Basic128Rsa15                           |      :new_moon:      |                      |
-| Basic256                                |      :new_moon:      |                      |
-| Basic256Sha256                          |      :new_moon:      |                      |
+| Basic128Rsa15                           |      :waning_crescent_moon:      | [WIP](https://github.com/open62541/open62541/pull/1003), Release 0.3     |
+| Basic256                                |      :waning_crescent_moon:      | [WIP](https://github.com/open62541/open62541/pull/1003), Release 0.3     |
+| Basic256Sha256                          |      :waning_crescent_moon:      | [WIP](https://github.com/open62541/open62541/pull/1003), Release 0.3     |
 | **Authentication**                      |                      |                      |
 | Anonymous                               |  :white_check_mark:  |                      |
 | User Name Password                      |  :white_check_mark:  |                      |
@@ -92,6 +92,6 @@ open62541 Supported Features
 | Method call                             |  :white_check_mark:  |                      |
 | Advanced Type                           |  :white_check_mark:  |                      |
 | **Discovery**                           |                      | See Discovery Service Set |
-| Local Disovery Server                   |     :full_moon:      | Branch: master       |
-| Local Discovery Server Multicast Ext.   |     :full_moon:      | Branch: master       |
+| Local Disovery Server                   |     :full_moon:      | Branch: master, Release 0.3  |
+| Local Discovery Server Multicast Ext.   |     :full_moon:      | Branch: master, Release 0.3  |
 | Global Discovery Server                 |      :new_moon:      |                      |

+ 11 - 2
README.md

@@ -40,6 +40,7 @@ Features still missing in the 0.2 release are:
 - Event-loop (background tasks) and asynchronous service requests in the client
 
 ### Using open62541
+
 A general introduction to OPC UA and the open62541 documentation can be found at http://open62541.org/doc/current.
 Past releases of the library can be downloaded at https://github.com/open62541/open62541/releases.
 To use the latest improvements, download a nightly build of the *single-file distribution* (the entire library merged into a single source and header file) from http://open62541.org/releases. Nightly builds of MSVC binaries of the library are available [here](https://ci.appveyor.com/project/open62541/open62541/build/artifacts).
@@ -49,8 +50,16 @@ For discussion and help, you can use
 - our [IRC channel](http://webchat.freenode.net/?channels=%23open62541)
 - the [bugtracker](https://github.com/open62541/open62541/issues)
 
-### Contribute to open62541
-As an open source project, we invite new contributors to help improve open62541. Issue reports, bugfixes and new features are very welcome. Note that there are ways to begin contributing without deep knowledge of the OPC UA standard:
+### Development
+
+Besides the general open62541 community, a group of core maintainers jointly steers the long-term development. The current core maintainers are (as of Mai 2017, in alphabetical order):
+
+- Chris-Paul Iatrou (Dresden University of Technology, Chair for Process Control Systems Engineering)
+- Florian Palm (RWTH Aachen University, Chair of Process Control Engineering)
+- Julius Pfrommer (Fraunhofer IOSB, Karlsruhe)
+- Stefan Profanter (fortiss, Munich)
+
+As an open source project, we encourage new contributors to help improve open62541. There are ways to begin contributing without deep knowledge of the OPC UA standard:
 - [Report bugs](https://github.com/open62541/open62541/issues)
 - Improve the [documentation](http://open62541.org/doc/current)
 - Work on issues marked as "[easy hacks](https://github.com/open62541/open62541/labels/easy%20hack)"

+ 2 - 2
include/ua_client_highlevel.h

@@ -152,7 +152,7 @@ UA_Client_readValueRankAttribute(UA_Client *client, const UA_NodeId nodeId,
 
 UA_StatusCode UA_EXPORT
 UA_Client_readArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                       UA_Int32 **outArrayDimensions,
+                                       UA_UInt32 **outArrayDimensions,
                                        size_t *outArrayDimensionsSize);
 
 static UA_INLINE UA_StatusCode
@@ -333,7 +333,7 @@ UA_Client_writeValueRankAttribute(UA_Client *client, const UA_NodeId nodeId,
 
 UA_StatusCode UA_EXPORT
 UA_Client_writeArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                        const UA_Int32 *newArrayDimensions,
+                                        const UA_UInt32 *newArrayDimensions,
                                         size_t newArrayDimensionsSize);
 
 static UA_INLINE UA_StatusCode

+ 1 - 1
include/ua_log.h

@@ -121,7 +121,7 @@ UA_LOG_FATAL(UA_Logger logger, UA_LogCategory category, const char *msg, ...) {
 /**
  * Convenience macros for complex types
  * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
-#define UA_PRINTF_GUID_FORMAT "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"
+#define UA_PRINTF_GUID_FORMAT "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"
 #define UA_PRINTF_GUID_DATA(GUID) (GUID).data1, (GUID).data2, (GUID).data3, \
         (GUID).data4[0], (GUID).data4[1], (GUID).data4[2], (GUID).data4[3], \
         (GUID).data4[4], (GUID).data4[5], (GUID).data4[6], (GUID).data4[7]

+ 30 - 10
src/client/ua_client_highlevel.c

@@ -294,14 +294,22 @@ __UA_Client_writeAttribute(UA_Client *client, const UA_NodeId *nodeId,
     wReq.nodesToWriteSize = 1;
 
     UA_WriteResponse wResp = UA_Client_Service_write(client, wReq);
+
     UA_StatusCode retval = wResp.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD) {
+        if(wResp.resultsSize == 1)
+            retval = wResp.results[0];
+        else
+            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
+
     UA_WriteResponse_deleteMembers(&wResp);
     return retval;
 }
 
 UA_StatusCode
 UA_Client_writeArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                        const UA_Int32 *newArrayDimensions,
+                                        const UA_UInt32 *newArrayDimensions,
                                         size_t newArrayDimensionsSize) {
     if(!newArrayDimensions)
       return UA_STATUSCODE_BADTYPEMISMATCH;
@@ -311,7 +319,7 @@ UA_Client_writeArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeI
     wValue.nodeId = nodeId;
     wValue.attributeId = UA_ATTRIBUTEID_ARRAYDIMENSIONS;
     UA_Variant_setArray(&wValue.value.value, (void*)(uintptr_t)newArrayDimensions,
-                        newArrayDimensionsSize, &UA_TYPES[UA_TYPES_INT32]);
+                        newArrayDimensionsSize, &UA_TYPES[UA_TYPES_UINT32]);
     wValue.value.hasValue = true;
     UA_WriteRequest wReq;
     UA_WriteRequest_init(&wReq);
@@ -319,7 +327,14 @@ UA_Client_writeArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeI
     wReq.nodesToWriteSize = 1;
 
     UA_WriteResponse wResp = UA_Client_Service_write(client, wReq);
+
     UA_StatusCode retval = wResp.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD) {
+        if(wResp.resultsSize == 1)
+            retval = wResp.results[0];
+        else
+            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
     UA_WriteResponse_deleteMembers(&wResp);
     return retval;
 }
@@ -342,8 +357,12 @@ __UA_Client_readAttribute(UA_Client *client, const UA_NodeId *nodeId,
     request.nodesToReadSize = 1;
     UA_ReadResponse response = UA_Client_Service_read(client, request);
     UA_StatusCode retval = response.responseHeader.serviceResult;
-    if(retval == UA_STATUSCODE_GOOD && response.resultsSize != 1)
-        retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+    if(retval == UA_STATUSCODE_GOOD) {
+        if(response.resultsSize == 1)
+            retval = response.results[0].status;
+        else
+            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
     if(retval != UA_STATUSCODE_GOOD) {
         UA_ReadResponse_deleteMembers(&response);
         return retval;
@@ -381,7 +400,7 @@ __UA_Client_readAttribute(UA_Client *client, const UA_NodeId *nodeId,
 
 static UA_StatusCode
 processReadArrayDimensionsResult(UA_ReadResponse *response,
-                                 UA_Int32 **outArrayDimensions,
+                                 UA_UInt32 **outArrayDimensions,
                                  size_t *outArrayDimensionsSize) {
     UA_StatusCode retval = response->responseHeader.serviceResult;
     if(retval != UA_STATUSCODE_GOOD)
@@ -390,17 +409,18 @@ processReadArrayDimensionsResult(UA_ReadResponse *response,
     if(response->resultsSize != 1)
         return UA_STATUSCODE_BADUNEXPECTEDERROR;
 
-    UA_DataValue *res = response->results;
-    if(res->hasStatus != UA_STATUSCODE_GOOD)
-        return res->hasStatus;
+    retval = response->results[0].status;
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
 
+    UA_DataValue *res = &response->results[0];
     if(!res->hasValue ||
        UA_Variant_isScalar(&res->value) ||
        res->value.type != &UA_TYPES[UA_TYPES_INT32])
         return UA_STATUSCODE_BADUNEXPECTEDERROR;
 
     /* Move results */
-    *outArrayDimensions = (UA_Int32 *)res->value.data;
+    *outArrayDimensions = (UA_UInt32*)res->value.data;
     *outArrayDimensionsSize = res->value.arrayLength;
     res->value.data = NULL;
     res->value.arrayLength = 0;
@@ -409,7 +429,7 @@ processReadArrayDimensionsResult(UA_ReadResponse *response,
 
 UA_StatusCode
 UA_Client_readArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                       UA_Int32 **outArrayDimensions,
+                                       UA_UInt32 **outArrayDimensions,
                                        size_t *outArrayDimensionsSize) {
     UA_ReadValueId item;
     UA_ReadValueId_init(&item);

+ 28 - 12
src/server/ua_securechannel_manager.c

@@ -28,25 +28,41 @@ void UA_SecureChannelManager_deleteMembers(UA_SecureChannelManager *cm) {
     }
 }
 
-static void removeSecureChannel(UA_SecureChannelManager *cm, channel_list_entry *entry){
+static void
+removeSecureChannelCallback(UA_Server *server, void *entry) {
+    channel_list_entry *centry = (channel_list_entry*)entry;
+    UA_SecureChannel_deleteMembersCleanup(&centry->channel);
+    UA_free(entry);
+}
+
+static UA_StatusCode
+removeSecureChannel(UA_SecureChannelManager *cm, channel_list_entry *entry){
+    /* Add a delayed callback to remove the channel when the currently
+     * scheduled jobs have completed */
+    UA_StatusCode retval = UA_Server_delayedCallback(cm->server, removeSecureChannelCallback, entry);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING(cm->server->config.logger, UA_LOGCATEGORY_SESSION,
+                       "Could not remove the secure channel with error code %s",
+                       UA_StatusCode_name(retval));
+        return retval; /* Try again next time */
+    }
+
+    /* Detach the channel and make the capacity available */
     LIST_REMOVE(entry, pointers);
     UA_atomic_add(&cm->currentChannelCount, (UA_UInt32)-1);
-    UA_SecureChannel_deleteMembersCleanup(&entry->channel);
-#ifndef UA_ENABLE_MULTITHREADING
-    UA_free(entry);
-#else
-    UA_Server_delayedFree(cm->server, entry);
-#endif
+    return UA_STATUSCODE_GOOD;
 }
 
 /* remove channels that were not renewed or who have no connection attached */
-void UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm, UA_DateTime nowMonotonic) {
+void
+UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm, UA_DateTime nowMonotonic) {
     channel_list_entry *entry, *temp;
     LIST_FOREACH_SAFE(entry, &cm->channels, pointers, temp) {
         UA_DateTime timeout = entry->channel.securityToken.createdAt +
             (UA_DateTime)(entry->channel.securityToken.revisedLifetime * UA_MSEC_TO_DATETIME);
         if(timeout < nowMonotonic || !entry->channel.connection) {
-            UA_LOG_DEBUG_CHANNEL(cm->server->config.logger, &entry->channel, "SecureChannel has timed out");
+            UA_LOG_INFO_CHANNEL(cm->server->config.logger, &entry->channel,
+                                "SecureChannel has timed out");
             removeSecureChannel(cm, entry);
         } else if(entry->channel.nextSecurityToken.tokenId > 0) {
             UA_SecureChannel_revolveTokens(&entry->channel);
@@ -60,7 +76,8 @@ static UA_Boolean purgeFirstChannelWithoutSession(UA_SecureChannelManager *cm) {
     LIST_FOREACH(entry, &cm->channels, pointers) {
         if(LIST_EMPTY(&(entry->channel.sessions))){
             UA_LOG_DEBUG_CHANNEL(cm->server->config.logger, &entry->channel,
-                                 "Channel was purged since maxSecureChannels was reached and channel had no session attached");
+                                 "Channel was purged since maxSecureChannels was "
+                                 "reached and channel had no session attached");
             removeSecureChannel(cm, entry);
             UA_assert(entry != LIST_FIRST(&cm->channels));
             return true;
@@ -169,6 +186,5 @@ UA_SecureChannelManager_close(UA_SecureChannelManager *cm, UA_UInt32 channelId)
     }
     if(!entry)
         return UA_STATUSCODE_BADINTERNALERROR;
-    removeSecureChannel(cm, entry);
-    return UA_STATUSCODE_GOOD;
+    return removeSecureChannel(cm, entry);
 }

+ 9 - 3
src/server/ua_securechannel_manager.h

@@ -30,9 +30,15 @@ typedef struct UA_SecureChannelManager {
 UA_StatusCode
 UA_SecureChannelManager_init(UA_SecureChannelManager *cm, UA_Server *server);
 
-void UA_SecureChannelManager_deleteMembers(UA_SecureChannelManager *cm);
-
-void UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm, UA_DateTime nowMonotonic);
+/* Remove a all securechannels */
+void
+UA_SecureChannelManager_deleteMembers(UA_SecureChannelManager *cm);
+
+/* Remove timed out securechannels with a delayed callback. So all currently
+ * scheduled jobs with a pointer to a securechannel can finish first. */
+void
+UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm,
+                                        UA_DateTime nowMonotonic);
 
 UA_StatusCode
 UA_SecureChannelManager_open(UA_SecureChannelManager *cm, UA_Connection *conn,

+ 11 - 10
src/server/ua_server_worker.c

@@ -151,6 +151,15 @@ emptyDispatchQueue(UA_Server *server) {
 /* Delayed Jobs */
 /****************/
 
+static void
+delayed_free(UA_Server *server, void *data) {
+    UA_free(data);
+}
+
+UA_StatusCode UA_Server_delayedFree(UA_Server *server, void *data) {
+    return UA_Server_delayedCallback(server, delayed_free, data);
+}
+
 #ifndef UA_ENABLE_MULTITHREADING
 
 typedef struct UA_DelayedJob {
@@ -202,7 +211,8 @@ static void getCounters(UA_Server *server, struct DelayedJobs *delayed) {
 /* Call from the main thread only. This is the only function that modifies */
 /* server->delayedWork. processDelayedWorkQueue modifies the "next" (after the */
 /* head). */
-static void addDelayedJob(UA_Server *server, UA_Job *job) {
+static void
+addDelayedJob(UA_Server *server, UA_Job *job) {
     struct DelayedJobs *dj = server->delayedJobs;
     if(!dj || dj->jobsCount >= DELAYEDJOBSSIZE) {
         /* create a new DelayedJobs and add it to the linked list */
@@ -229,15 +239,6 @@ static void addDelayedJob(UA_Server *server, UA_Job *job) {
     ++dj->jobsCount;
 }
 
-static void
-delayed_free(UA_Server *server, void *data) {
-    UA_free(data);
-}
-
-UA_StatusCode UA_Server_delayedFree(UA_Server *server, void *data) {
-    return UA_Server_delayedCallback(server, delayed_free, data);
-}
-
 static void
 addDelayedJobAsync(UA_Server *server, UA_Job *job) {
     addDelayedJob(server, job);

+ 1 - 1
src/server/ua_services_attribute.c

@@ -284,7 +284,7 @@ typeCheckValue(UA_Server *server, const UA_NodeId *targetDataTypeId,
 static UA_StatusCode
 readArrayDimensionsAttribute(const UA_VariableNode *vn, UA_DataValue *v) {
     UA_Variant_setArray(&v->value, vn->arrayDimensions,
-                        vn->arrayDimensionsSize, &UA_TYPES[UA_TYPES_INT32]);
+                        vn->arrayDimensionsSize, &UA_TYPES[UA_TYPES_UINT32]);
     v->value.storageType = UA_VARIANT_DATA_NODELETE;
     v->hasValue = true;
     return UA_STATUSCODE_GOOD;

+ 51 - 27
src/server/ua_session_manager.c

@@ -22,27 +22,46 @@ void UA_SessionManager_deleteMembers(UA_SessionManager *sm) {
     }
 }
 
+/* Delayed callback to free the session memory */
 static void
-removeSessionEntry(UA_SessionManager *sm, session_list_entry *sentry) {
+removeSessionCallback(UA_Server *server, void *entry) {
+    session_list_entry *sentry = (session_list_entry*)entry;
+    UA_Session_deleteMembersCleanup(&sentry->session, server);
+    UA_free(sentry);
+}
+
+static UA_StatusCode
+removeSession(UA_SessionManager *sm, session_list_entry *sentry) {
+    /* Deactivate the session */
+    sentry->session.activated = false;
+
+    /* Add a delayed callback to remove the session when the currently
+     * scheduled jobs have completed */
+    UA_StatusCode retval = UA_Server_delayedCallback(sm->server, removeSessionCallback, sentry);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING_SESSION(sm->server->config.logger, &sentry->session,
+                       "Could not remove session with error code %s",
+                       UA_StatusCode_name(retval));
+        return retval; /* Try again next time */
+    }
+
+    /* Detach the session and make the capacity available */
     LIST_REMOVE(sentry, pointers);
     UA_atomic_add(&sm->currentSessionCount, (UA_UInt32)-1);
-    UA_Session_deleteMembersCleanup(&sentry->session, sm->server);
-#ifndef UA_ENABLE_MULTITHREADING
-    UA_free(sentry);
-#else
-    UA_Server_delayedFree(sm->server, sentry);
-#endif
+    return UA_STATUSCODE_GOOD;
 }
 
-void UA_SessionManager_cleanupTimedOut(UA_SessionManager *sm, UA_DateTime nowMonotonic) {
+void
+UA_SessionManager_cleanupTimedOut(UA_SessionManager *sm,
+                                  UA_DateTime nowMonotonic) {
     session_list_entry *sentry, *temp;
     LIST_FOREACH_SAFE(sentry, &sm->sessions, pointers, temp) {
-        if(sentry->session.validTill < nowMonotonic) {
-            UA_LOG_DEBUG(sm->server->config.logger, UA_LOGCATEGORY_SESSION,
-                         "Session with token %i has timed out and is removed",
-                         sentry->session.sessionId.identifier.numeric);
-            removeSessionEntry(sm, sentry);
-        }
+        /* Session has timed out? */
+        if(sentry->session.validTill >= nowMonotonic)
+            continue;
+        UA_LOG_INFO_SESSION(sm->server->config.logger, &sentry->session,
+                            "Session has timed out");
+        removeSession(sm, sentry);
     }
 }
 
@@ -50,19 +69,25 @@ UA_Session *
 UA_SessionManager_getSession(UA_SessionManager *sm, const UA_NodeId *token) {
     session_list_entry *current = NULL;
     LIST_FOREACH(current, &sm->sessions, pointers) {
-        if(UA_NodeId_equal(&current->session.authenticationToken, token)) {
-            if(UA_DateTime_nowMonotonic() > current->session.validTill) {
-                UA_LOG_DEBUG(sm->server->config.logger, UA_LOGCATEGORY_SESSION,
-                             "Try to use Session with token " UA_PRINTF_GUID_FORMAT ", but has timed out",
-                             UA_PRINTF_GUID_DATA(token->identifier.guid));
-                return NULL;
-            }
-            return &current->session;
+        /* Token does not match */
+        if(!UA_NodeId_equal(&current->session.authenticationToken, token))
+            continue;
+
+        /* Session has timed out */
+        if(UA_DateTime_nowMonotonic() > current->session.validTill) {
+            UA_LOG_INFO_SESSION(sm->server->config.logger, &current->session,
+                                "Client tries to use a session that has timed out");
+            return NULL;
         }
+
+        /* Ok, return */
+        return &current->session;
     }
-    UA_LOG_DEBUG(sm->server->config.logger, UA_LOGCATEGORY_SESSION,
-                 "Try to use Session with token " UA_PRINTF_GUID_FORMAT " but is not found",
-                 UA_PRINTF_GUID_DATA(token->identifier.guid));
+
+    /* Session not found */
+    UA_LOG_INFO(sm->server->config.logger, UA_LOGCATEGORY_SESSION,
+                "Try to use Session with token " UA_PRINTF_GUID_FORMAT " but is not found",
+                UA_PRINTF_GUID_DATA(token->identifier.guid));
     return NULL;
 }
 
@@ -103,6 +128,5 @@ UA_SessionManager_removeSession(UA_SessionManager *sm, const UA_NodeId *token) {
     }
     if(!current)
         return UA_STATUSCODE_BADSESSIONIDINVALID;
-    removeSessionEntry(sm, current);
-    return UA_STATUSCODE_GOOD;
+    return removeSession(sm, current);
 }

+ 6 - 1
src/server/ua_session_manager.h

@@ -28,9 +28,14 @@ typedef struct UA_SessionManager {
 UA_StatusCode
 UA_SessionManager_init(UA_SessionManager *sm, UA_Server *server);
 
+/* Deletes all sessions */
 void UA_SessionManager_deleteMembers(UA_SessionManager *sm);
 
-void UA_SessionManager_cleanupTimedOut(UA_SessionManager *sm, UA_DateTime nowMonotonic);
+/* Deletes all sessions that have timed out. Deletion is implemented via a
+ * delayed callback. So all currently scheduled jobs with a pointer to the
+ * session can complete. */
+void UA_SessionManager_cleanupTimedOut(UA_SessionManager *sm,
+                                       UA_DateTime nowMonotonic);
 
 UA_StatusCode
 UA_SessionManager_createSession(UA_SessionManager *sm, UA_SecureChannel *channel,

+ 18 - 18
src/ua_session.h

@@ -86,44 +86,44 @@ UA_Session_getUniqueSubscriptionID(UA_Session *session);
 
 #define UA_LOG_TRACE_SESSION(LOGGER, SESSION, MSG, ...)                 \
     UA_LOG_TRACE(LOGGER, UA_LOGCATEGORY_SESSION, "Connection %i | SecureChannel %i | Session " UA_PRINTF_GUID_FORMAT " | " MSG, \
-                 (SESSION->channel ? (SESSION->channel->connection ? SESSION->channel->connection->sockfd : 0) : 0), \
-                 (SESSION->channel ? SESSION->channel->securityToken.channelId : 0), \
-                 UA_PRINTF_GUID_DATA(SESSION->sessionId.identifier.guid), \
+                 ((SESSION)->channel ? ((SESSION)->channel->connection ? (SESSION)->channel->connection->sockfd : 0) : 0), \
+                 ((SESSION)->channel ? (SESSION)->channel->securityToken.channelId : 0), \
+                 UA_PRINTF_GUID_DATA((SESSION)->sessionId.identifier.guid), \
                  ##__VA_ARGS__);
 
 #define UA_LOG_DEBUG_SESSION(LOGGER, SESSION, MSG, ...)                 \
     UA_LOG_DEBUG(LOGGER, UA_LOGCATEGORY_SESSION, "Connection %i | SecureChannel %i | Session " UA_PRINTF_GUID_FORMAT " | " MSG, \
-                 (SESSION->channel ? (SESSION->channel->connection ? SESSION->channel->connection->sockfd : 0) : 0), \
-                 (SESSION->channel ? SESSION->channel->securityToken.channelId : 0), \
-                 UA_PRINTF_GUID_DATA(SESSION->sessionId.identifier.guid), \
+                 ((SESSION)->channel ? ((SESSION)->channel->connection ? (SESSION)->channel->connection->sockfd : 0) : 0), \
+                 ((SESSION)->channel ? (SESSION)->channel->securityToken.channelId : 0), \
+                 UA_PRINTF_GUID_DATA((SESSION)->sessionId.identifier.guid), \
                  ##__VA_ARGS__);
 
 #define UA_LOG_INFO_SESSION(LOGGER, SESSION, MSG, ...)                  \
     UA_LOG_INFO(LOGGER, UA_LOGCATEGORY_SESSION, "Connection %i | SecureChannel %i | Session " UA_PRINTF_GUID_FORMAT " | " MSG, \
-                 (SESSION->channel ? (SESSION->channel->connection ? SESSION->channel->connection->sockfd : 0) : 0), \
-                 (SESSION->channel ? SESSION->channel->securityToken.channelId : 0), \
-                 UA_PRINTF_GUID_DATA(SESSION->sessionId.identifier.guid), \
+                 ((SESSION)->channel ? ((SESSION)->channel->connection ? (SESSION)->channel->connection->sockfd : 0) : 0), \
+                 ((SESSION)->channel ? (SESSION)->channel->securityToken.channelId : 0), \
+                 UA_PRINTF_GUID_DATA((SESSION)->sessionId.identifier.guid), \
                  ##__VA_ARGS__);
 
 #define UA_LOG_WARNING_SESSION(LOGGER, SESSION, MSG, ...)               \
     UA_LOG_WARNING(LOGGER, UA_LOGCATEGORY_SESSION, "Connection %i | SecureChannel %i | Session " UA_PRINTF_GUID_FORMAT " | " MSG, \
-                   (SESSION->channel ? (SESSION->channel->connection ? SESSION->channel->connection->sockfd : 0) : 0), \
-                   (SESSION->channel ? SESSION->channel->securityToken.channelId : 0), \
-                   UA_PRINTF_GUID_DATA(SESSION->sessionId.identifier.guid), \
+                   ((SESSION)->channel ? ((SESSION)->channel->connection ? (SESSION)->channel->connection->sockfd : 0) : 0), \
+                   ((SESSION)->channel ? (SESSION)->channel->securityToken.channelId : 0), \
+                   UA_PRINTF_GUID_DATA((SESSION)->sessionId.identifier.guid), \
                    ##__VA_ARGS__);
 
 #define UA_LOG_ERROR_SESSION(LOGGER, SESSION, MSG, ...)                 \
     UA_LOG_ERROR(LOGGER, UA_LOGCATEGORY_SESSION, "Connection %i | SecureChannel %i | Session " UA_PRINTF_GUID_FORMAT " | " MSG, \
-                 (SESSION->channel ? (SESSION->channel->connection ? SESSION->channel->connection->sockfd : 0) : 0), \
-                 (SESSION->channel ? SESSION->channel->securityToken.channelId : 0), \
-                 UA_PRINTF_GUID_DATA(SESSION->sessionId.identifier.guid), \
+                 ((SESSION)->channel ? ((SESSION)->channel->connection ? (SESSION)->channel->connection->sockfd : 0) : 0), \
+                 ((SESSION)->channel ? (SESSION)->channel->securityToken.channelId : 0), \
+                 UA_PRINTF_GUID_DATA((SESSION)->sessionId.identifier.guid), \
                  ##__VA_ARGS__);
 
 #define UA_LOG_FATAL_SESSION(LOGGER, SESSION, MSG, ...)                 \
     UA_LOG_FATAL(LOGGER, UA_LOGCATEGORY_SESSION, "Connection %i | SecureChannel %i | Session " UA_PRINTF_GUID_FORMAT " | " MSG, \
-                 (SESSION->channel ? (SESSION->channel->connection ? SESSION->channel->connection->sockfd : 0) : 0), \
-                 (SESSION->channel ? SESSION->channel->securityToken.channelId : 0), \
-                 UA_PRINTF_GUID_DATA(SESSION->sessionId.identifier.guid), \
+                 ((SESSION)->channel ? ((SESSION)->channel->connection ? (SESSION)->channel->connection->sockfd : 0) : 0), \
+                 ((SESSION)->channel ? (SESSION)->channel->securityToken.channelId : 0), \
+                 UA_PRINTF_GUID_DATA((SESSION)->sessionId.identifier.guid), \
                  ##__VA_ARGS__);
 
 #ifdef __cplusplus

+ 151 - 3
tests/check_client_highlevel.c

@@ -196,9 +196,7 @@ START_TEST(Node_Add)
             attr.description = UA_LOCALIZEDTEXT("en_US", "Top Coordinate");
             attr.displayName = UA_LOCALIZEDTEXT("en_US", "Top");
 
-            UA_Int32 values[2];
-            values[1] = 10;
-            values[2] = 20;
+            UA_Int32 values[2] = {10, 20};
 
             UA_Variant_setArray(&attr.value, values, 2, &UA_TYPES[UA_TYPES_INT32]);
             attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
@@ -282,6 +280,155 @@ START_TEST(Node_Add)
     }
 END_TEST
 
+unsigned int iteratedNodeCount = 0;
+UA_NodeId iteratedNodes[2];
+
+static UA_StatusCode
+nodeIter(UA_NodeId childId, UA_Boolean isInverse, UA_NodeId referenceTypeId, void *handle) {
+    if (isInverse ||
+            (referenceTypeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
+                    referenceTypeId.identifier.numeric == UA_NS0ID_HASTYPEDEFINITION)
+            )
+        return UA_STATUSCODE_GOOD;
+
+    if (iteratedNodeCount >= 2)
+        return UA_STATUSCODE_BADINDEXRANGEINVALID;
+
+    UA_NodeId_copy(&childId, &iteratedNodes[iteratedNodeCount]);
+
+    iteratedNodeCount++;
+
+    return UA_STATUSCODE_GOOD;
+}
+
+START_TEST(Node_ReadWrite)
+    {
+        UA_StatusCode retval;
+        // Create a folder with two variables for testing
+
+        UA_NodeId unitTestNodeId;
+
+        UA_NodeId nodeArrayId;
+        UA_NodeId nodeIntId;
+
+        // create Coordinates Object within ObjectsFolder
+        {
+            UA_ObjectAttributes attr;
+            UA_ObjectAttributes_init(&attr);
+            attr.description = UA_LOCALIZEDTEXT("en_US", "UnitTest");
+            attr.displayName = UA_LOCALIZEDTEXT("en_US", "UnitTest");
+
+            retval = UA_Client_addObjectNode(client, UA_NODEID_NULL,
+                                             UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                             UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                             UA_QUALIFIEDNAME(1, "UnitTest"),
+                                             UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE), attr, &unitTestNodeId);
+            ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        }
+
+        // create Variable 'Top' within UnitTest Object
+        {
+            UA_VariableAttributes attr;
+            UA_VariableAttributes_init(&attr);
+            attr.description = UA_LOCALIZEDTEXT("en_US", "Array");
+            attr.displayName = UA_LOCALIZEDTEXT("en_US", "Array");
+
+            /*UA_Int32 values[2];
+            values[1] = 10;
+            values[2] = 20;
+
+            UA_Variant_setArray(&attr.value, values, 2, &UA_TYPES[UA_TYPES_INT32]);*/
+            attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+            attr.valueRank = 1; /* array with one dimension */
+            UA_UInt32 arrayDims[1] = {2};
+            attr.arrayDimensions = arrayDims;
+            attr.arrayDimensionsSize = 1;
+
+            retval = UA_Client_addVariableNode(client, UA_NODEID_NULL,
+                                               unitTestNodeId,
+                                               UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                               UA_QUALIFIEDNAME(1, "Array"),
+                                               UA_NODEID_NULL, attr, &nodeArrayId);
+            ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        }
+
+        // create Variable 'Bottom' within UnitTest Object
+        {
+            UA_VariableAttributes attr;
+            UA_VariableAttributes_init(&attr);
+            attr.description = UA_LOCALIZEDTEXT("en_US", "Int");
+            attr.displayName = UA_LOCALIZEDTEXT("en_US", "Int");
+
+            UA_Int32 int_value = 5678;
+
+            UA_Variant_setScalar(&attr.value, &int_value, &UA_TYPES[UA_TYPES_INT32]);
+            attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+
+            retval = UA_Client_addVariableNode(client, UA_NODEID_NULL,
+                                               unitTestNodeId,
+                                               UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                               UA_QUALIFIEDNAME(1, "Int"),
+                                               UA_NODEID_NULL, attr, &nodeIntId);
+
+            ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        }
+
+        // iterate over children
+        {
+            retval = UA_Client_forEachChildNodeCall(client, unitTestNodeId,
+                                                    nodeIter, NULL);
+            ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+            ck_assert(UA_NodeId_equal(&nodeArrayId, &iteratedNodes[0]));
+            ck_assert(UA_NodeId_equal(&nodeIntId, &iteratedNodes[1]));
+        }
+
+
+        /* Read attribute */
+        UA_Int32 value = 0;
+        UA_Variant *val = UA_Variant_new();
+        retval = UA_Client_readValueAttribute(client, nodeIntId, val);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        ck_assert(UA_Variant_isScalar(val) && val->type == &UA_TYPES[UA_TYPES_INT32]);
+        value = *(UA_Int32*)val->data;
+        ck_assert_int_eq(value, 5678);
+        UA_Variant_delete(val);
+
+        /* Write attribute */
+        value++;
+        val = UA_Variant_new();
+        UA_Variant_setScalarCopy(val, &value, &UA_TYPES[UA_TYPES_INT32]);
+        retval = UA_Client_writeValueAttribute(client, nodeIntId, val);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        UA_Variant_delete(val);
+
+        /* Read again to check value */
+        val = UA_Variant_new();
+        retval = UA_Client_readValueAttribute(client, nodeIntId, val);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        ck_assert(UA_Variant_isScalar(val) && val->type == &UA_TYPES[UA_TYPES_INT32]);
+        value = *(UA_Int32*)val->data;
+        ck_assert_int_eq(value, 5679);
+        UA_Variant_delete(val);
+
+        UA_UInt32 arrayDimsNew[] = {3};
+        retval = UA_Client_writeArrayDimensionsAttribute(client, nodeArrayId, arrayDimsNew , 1);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+
+        UA_UInt32 *arrayDimsRead;
+        size_t arrayDimsReadSize;
+        retval = UA_Client_readArrayDimensionsAttribute(client, nodeArrayId, &arrayDimsRead , &arrayDimsReadSize);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(arrayDimsReadSize, 1);
+        ck_assert_int_eq(arrayDimsRead[0], 3);
+        UA_Array_delete(arrayDimsRead, arrayDimsReadSize, &UA_TYPES[UA_TYPES_UINT32]);
+
+    }
+END_TEST
+
 START_TEST(Node_Browse)
     {
 
@@ -403,6 +550,7 @@ static Suite *testSuite_Client(void) {
     tcase_add_checked_fixture(tc_nodes, setup, teardown);
     tcase_add_test(tc_nodes, Node_Add);
     tcase_add_test(tc_nodes, Node_Browse);
+    tcase_add_test(tc_nodes, Node_ReadWrite);
     tcase_add_test(tc_nodes, Node_Register);
     suite_add_tcase(s, tc_nodes);
     return s;

+ 1 - 1
tests/check_services_attributes.c

@@ -448,7 +448,7 @@ START_TEST(ReadSingleAttributeArrayDimensionsWithoutTimestamp) {
     UA_DataValue resp = UA_Server_read(server, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
 
     ck_assert_int_eq(0, resp.value.arrayLength);
-    ck_assert_ptr_eq(&UA_TYPES[UA_TYPES_INT32], resp.value.type);
+    ck_assert_ptr_eq(&UA_TYPES[UA_TYPES_UINT32], resp.value.type);
     ck_assert_ptr_eq((UA_Int32*)resp.value.data,0);
     UA_DataValue_deleteMembers(&resp);
     UA_Server_delete(server);