Parcourir la source

Merge branch '0.2'

Julius Pfrommer il y a 8 ans
Parent
commit
6135e96aeb

+ 12 - 0
doc/CMakeLists.txt

@@ -1,5 +1,8 @@
 find_package(Sphinx REQUIRED)
+find_package(LATEX)
 
+
+set(DOC_LATEX_DIR ${PROJECT_BINARY_DIR}/doc_latex)
 make_directory(${PROJECT_BINARY_DIR}/doc_src)
 file(GLOB DOC_SRC "${PROJECT_SOURCE_DIR}/doc/*")
 file(COPY ${DOC_SRC} DESTINATION ${PROJECT_BINARY_DIR}/doc_src)
@@ -37,6 +40,15 @@ add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
   COMMENT "Building LaTeX sources for documentation with Sphinx")
 add_dependencies(doc_latex open62541)
 
+
+add_custom_target(doc_pdf ${PDFLATEX_COMPILER} -q "open62541.tex"
+  WORKING_DIRECTORY ${DOC_LATEX_DIR}
+  # compile it twice so that the contents pages are correct
+  COMMAND ${PDFLATEX_COMPILER} -q "open62541.tex"
+  DEPENDS ${PDFLATEX_COMPILER}
+  COMMENT "Generating PDF documentation from LaTeX sources")
+add_dependencies(doc_pdf doc_latex)
+
 add_custom_target(doc ${SPHINX_EXECUTABLE}
   -b html -c "${PROJECT_BINARY_DIR}/doc_src" "${PROJECT_BINARY_DIR}/doc_src" "${PROJECT_BINARY_DIR}/doc"
   COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_BINARY_DIR}/doc_src/open62541_html.png" "${PROJECT_BINARY_DIR}/doc/_static/"

+ 91 - 0
examples/server.c

@@ -81,6 +81,22 @@ helloWorld(void *methodHandle, const UA_NodeId objectId,
     UA_String_deleteMembers(&greet);
     return UA_STATUSCODE_GOOD;
 }
+
+static UA_StatusCode
+noargMethod (void *methodHandle, const UA_NodeId objectId,
+           size_t inputSize, const UA_Variant *input,
+           size_t outputSize, UA_Variant *output) {
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+outargMethod (void *methodHandle, const UA_NodeId objectId,
+           size_t inputSize, const UA_Variant *input,
+           size_t outputSize, UA_Variant *output) {
+    UA_Int32 out = 42;
+    UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_INT32]);
+    return UA_STATUSCODE_GOOD;
+}
 #endif
 
 int main(int argc, char** argv) {
@@ -123,6 +139,7 @@ int main(int argc, char** argv) {
 
     /* Add HelloWorld method to the server */
 #ifdef UA_ENABLE_METHODCALLS
+    /* Method with IO Arguments */
     UA_Argument inputArguments;
     UA_Argument_init(&inputArguments);
     inputArguments.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
@@ -275,6 +292,80 @@ int main(int argc, char** argv) {
     UA_LocalizedText objectsName = UA_LOCALIZEDTEXT("en_US", "Objects");
     UA_Server_writeDisplayName(server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), objectsName);
 
+#define NOARGID     60000
+#define INARGID     60001
+#define OUTARGID    60002
+#define INOUTARGID  60003
+#ifdef UA_ENABLE_METHODCALLS
+    /* adding some more method nodes to pass CTT */
+    /* Method without arguments */
+    UA_MethodAttributes_init(&addmethodattributes);
+    addmethodattributes.displayName = UA_LOCALIZEDTEXT("en_US", "noarg");
+    addmethodattributes.executable = true;
+    addmethodattributes.userExecutable = true;
+    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, NOARGID),
+        UA_NODEID_NUMERIC(1, DEMOID),
+        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+        UA_QUALIFIEDNAME(1, "noarg"), addmethodattributes,
+        &noargMethod, /* callback of the method node */
+        NULL, /* handle passed with the callback */
+        0, NULL, 0, NULL, NULL);
+
+    /* Method with in arguments */
+    UA_MethodAttributes_init(&addmethodattributes);
+    addmethodattributes.displayName = UA_LOCALIZEDTEXT("en_US", "inarg");
+    addmethodattributes.executable = true;
+    addmethodattributes.userExecutable = true;
+
+    UA_Argument_init(&inputArguments);
+    inputArguments.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    inputArguments.description = UA_LOCALIZEDTEXT("en_US", "Input");
+    inputArguments.name = UA_STRING("Input");
+    inputArguments.valueRank = -1; //uaexpert will crash if set to 0 ;)
+
+    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, INARGID),
+        UA_NODEID_NUMERIC(1, DEMOID),
+        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+        UA_QUALIFIEDNAME(1, "noarg"), addmethodattributes,
+        &noargMethod, /* callback of the method node */
+        NULL, /* handle passed with the callback */
+        1, &inputArguments, 0, NULL, NULL);
+
+    /* Method with out arguments */
+    UA_MethodAttributes_init(&addmethodattributes);
+    addmethodattributes.displayName = UA_LOCALIZEDTEXT("en_US", "outarg");
+    addmethodattributes.executable = true;
+    addmethodattributes.userExecutable = true;
+
+    UA_Argument_init(&outputArguments);
+    outputArguments.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    outputArguments.description = UA_LOCALIZEDTEXT("en_US", "Output");
+    outputArguments.name = UA_STRING("Output");
+    outputArguments.valueRank = -1;
+
+    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, OUTARGID),
+        UA_NODEID_NUMERIC(1, DEMOID),
+        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+        UA_QUALIFIEDNAME(1, "outarg"), addmethodattributes,
+        &outargMethod, /* callback of the method node */
+        NULL, /* handle passed with the callback */
+        0, NULL, 1, &outputArguments, NULL);
+
+    /* Method with inout arguments */
+    UA_MethodAttributes_init(&addmethodattributes);
+    addmethodattributes.displayName = UA_LOCALIZEDTEXT("en_US", "inoutarg");
+    addmethodattributes.executable = true;
+    addmethodattributes.userExecutable = true;
+
+    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, INOUTARGID),
+        UA_NODEID_NUMERIC(1, DEMOID),
+        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+        UA_QUALIFIEDNAME(1, "inoutarg"), addmethodattributes,
+        &outargMethod, /* callback of the method node */
+        NULL, /* handle passed with the callback */
+        1, &inputArguments, 1, &outputArguments, NULL);
+#endif
+
     /* run server */
     UA_StatusCode retval = UA_Server_run(server, &running); /* run until ctrl-c is received */
     UA_Server_delete(server);

+ 33 - 26
src/server/ua_securechannel_manager.c

@@ -35,15 +35,15 @@ static void removeSecureChannel(UA_SecureChannelManager *cm, channel_list_entry
     UA_Server_delayedFree(cm->server, entry);
 #endif
 }
+
 /* remove channels that were not renewed or who have no connection attached */
-void UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm, UA_DateTime now) {
+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 timeout = entry->channel.securityToken.createdAt +
             (UA_DateTime)(entry->channel.securityToken.revisedLifetime * UA_MSEC_TO_DATETIME);
-        if(timeout < now || !entry->channel.connection) {
-            UA_LOG_DEBUG_CHANNEL(cm->server->config.logger, (&entry->channel), "SecureChannel has timed out");
+        if(timeout < nowMonotonic || !entry->channel.connection) {
+            UA_LOG_DEBUG_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);
@@ -56,7 +56,8 @@ static UA_Boolean purgeFirstChannelWithoutSession(UA_SecureChannelManager *cm) {
     channel_list_entry *entry;
     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");
+            UA_LOG_DEBUG_CHANNEL(cm->server->config.logger, &entry->channel,
+                                 "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;
@@ -71,46 +72,47 @@ UA_SecureChannelManager_open(UA_SecureChannelManager *cm, UA_Connection *conn,
                              UA_OpenSecureChannelResponse *response) {
     if(request->securityMode != UA_MESSAGESECURITYMODE_NONE)
         return UA_STATUSCODE_BADSECURITYMODEREJECTED;
+
     //check if there exists a free SC, otherwise try to purge one SC without a session
     //the purge has been introduced to pass CTT, it is not clear what strategy is expected here
     if(cm->currentChannelCount >= cm->server->config.maxSecureChannels && !purgeFirstChannelWithoutSession(cm)){
         return UA_STATUSCODE_BADOUTOFMEMORY;
     }
+
+    /* Set up the channel */
     channel_list_entry *entry = UA_malloc(sizeof(channel_list_entry));
     if(!entry)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-#ifndef UA_ENABLE_MULTITHREADING
-    cm->currentChannelCount++;
-#else
-    cm->currentChannelCount = uatomic_add_return(&cm->currentChannelCount, 1);
-#endif
-
     UA_SecureChannel_init(&entry->channel);
-    response->responseHeader.stringTableSize = 0;
-    response->responseHeader.timestamp = UA_DateTime_now();
-    response->serverProtocolVersion = 0;
-
     entry->channel.securityToken.channelId = cm->lastChannelId++;
     entry->channel.securityToken.tokenId = cm->lastTokenId++;
     entry->channel.securityToken.createdAt = UA_DateTime_now();
     entry->channel.securityToken.revisedLifetime =
         (request->requestedLifetime > cm->server->config.maxSecurityTokenLifetime) ?
         cm->server->config.maxSecurityTokenLifetime : request->requestedLifetime;
-    /* lifetime 0 -> set the maximum possible */
-    if(entry->channel.securityToken.revisedLifetime == 0)
+    if(entry->channel.securityToken.revisedLifetime == 0) /* lifetime 0 -> set the maximum possible */
         entry->channel.securityToken.revisedLifetime = cm->server->config.maxSecurityTokenLifetime;
-
     UA_ByteString_copy(&request->clientNonce, &entry->channel.clientNonce);
-    entry->channel.serverAsymAlgSettings.securityPolicyUri = UA_STRING_ALLOC(
-            "http://opcfoundation.org/UA/SecurityPolicy#None");
-
+    entry->channel.serverAsymAlgSettings.securityPolicyUri =
+        UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#None");
     UA_SecureChannel_generateNonce(&entry->channel.serverNonce);
+
+    /* Set the response */
     UA_ByteString_copy(&entry->channel.serverNonce, &response->serverNonce);
-    UA_ChannelSecurityToken_copy(&entry->channel.securityToken,
-            &response->securityToken);
+    UA_ChannelSecurityToken_copy(&entry->channel.securityToken, &response->securityToken);
+    response->responseHeader.timestamp = UA_DateTime_now();
 
+    /* Now overwrite the creation date with the internal monotonic clock */
+    entry->channel.securityToken.createdAt = UA_DateTime_nowMonotonic();
+
+    /* Set all the pointers internally */
     UA_Connection_attachSecureChannel(conn, &entry->channel);
     LIST_INSERT_HEAD(&cm->channels, entry, pointers);
+#ifndef UA_ENABLE_MULTITHREADING
+    cm->currentChannelCount++;
+#else
+    cm->currentChannelCount = uatomic_add_return(&cm->currentChannelCount, 1);
+#endif
     return UA_STATUSCODE_GOOD;
 }
 
@@ -130,17 +132,22 @@ UA_SecureChannelManager_renew(UA_SecureChannelManager *cm, UA_Connection *conn,
         channel->nextSecurityToken.revisedLifetime =
             (request->requestedLifetime > cm->server->config.maxSecurityTokenLifetime) ?
             cm->server->config.maxSecurityTokenLifetime : request->requestedLifetime;
-        /* lifetime 0 -> return the max lifetime */
-        if(channel->nextSecurityToken.revisedLifetime == 0)
+        if(channel->nextSecurityToken.revisedLifetime == 0) /* lifetime 0 -> return the max lifetime */
             channel->nextSecurityToken.revisedLifetime = cm->server->config.maxSecurityTokenLifetime;
     }
 
+    /* invalidate the old nonce */
     if(channel->clientNonce.data)
         UA_ByteString_deleteMembers(&channel->clientNonce);
 
+    /* set the response */
     UA_ByteString_copy(&request->clientNonce, &channel->clientNonce);
     UA_ByteString_copy(&channel->serverNonce, &response->serverNonce);
     UA_ChannelSecurityToken_copy(&channel->nextSecurityToken, &response->securityToken);
+
+    /* reset the creation date to the monotonic clock */
+    channel->nextSecurityToken.createdAt = UA_DateTime_nowMonotonic();
+
     return UA_STATUSCODE_GOOD;
 }
 

+ 1 - 1
src/server/ua_securechannel_manager.h

@@ -24,7 +24,7 @@ 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 now);
+void UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm, UA_DateTime nowMonotonic);
 
 UA_StatusCode
 UA_SecureChannelManager_open(UA_SecureChannelManager *cm, UA_Connection *conn,

+ 4 - 4
src/server/ua_server.c

@@ -288,11 +288,11 @@ void UA_Server_delete(UA_Server *server) {
 
 /* Recurring cleanup. Removing unused and timed-out channels and sessions */
 static void UA_Server_cleanup(UA_Server *server, void *_) {
-    UA_DateTime now = UA_DateTime_now();
-    UA_SessionManager_cleanupTimedOut(&server->sessionManager, now);
-    UA_SecureChannelManager_cleanupTimedOut(&server->secureChannelManager, now);
+    UA_DateTime nowMonotonic = UA_DateTime_nowMonotonic();
+    UA_SessionManager_cleanupTimedOut(&server->sessionManager, nowMonotonic);
+    UA_SecureChannelManager_cleanupTimedOut(&server->secureChannelManager, nowMonotonic);
 #ifdef UA_ENABLE_DISCOVERY
-    UA_Discovery_cleanupTimedOut(server, now);
+    UA_Discovery_cleanupTimedOut(server, nowMonotonic);
 #endif
 }
 

+ 1 - 1
src/server/ua_server_binary.c

@@ -672,7 +672,7 @@ void UA_Server_processBinaryMessage(UA_Server *server, UA_Connection *connection
             return;
 
         default:
-            UA_LOG_TRACE(server->config.logger, UA_LOGCATEGORY_NETWORK, "Connection %i | Unknown request type", connection->sockfd);
+            UA_LOG_TRACE(server->config.logger, UA_LOGCATEGORY_NETWORK, "Connection %i | Unknown chunk type", connection->sockfd);
         }
 
         /* Loop to process the next message in the stream */

+ 3 - 0
src/server/ua_server_internal.h

@@ -117,4 +117,7 @@ isNodeInTree(UA_NodeStore *ns, const UA_NodeId *rootNode, const UA_NodeId *nodeT
              const UA_NodeId *referenceTypeIds, size_t referenceTypeIdsSize,
              size_t maxDepth, UA_Boolean *found);
 
+/* Periodic task to clean up the discovery registry */
+void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now);
+
 #endif /* UA_SERVER_INTERNAL_H_ */

+ 2 - 4
src/server/ua_services.h

@@ -41,10 +41,8 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session,
 #ifdef UA_ENABLE_DISCOVERY
 /* Registers a remote server in the local discovery service. */
 void Service_RegisterServer(UA_Server *server, UA_Session *session,
-							const UA_RegisterServerRequest *request,
-							UA_RegisterServerResponse *response);
-
-void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now);
+                            const UA_RegisterServerRequest *request,
+                            UA_RegisterServerResponse *response);
 #endif
 
 /**

+ 20 - 16
src/server/ua_services_call.c

@@ -171,28 +171,32 @@ Service_Call_single(UA_Server *server, UA_Session *session, const UA_CallMethodR
         return;
 
     /* Verify Input Argument count, types and sizes */
-    const UA_VariableNode *inputArguments = getArgumentsVariableNode(server, methodCalled, UA_STRING("InputArguments"));
-    if(!inputArguments) {
-        result->statusCode = UA_STATUSCODE_BADINVALIDARGUMENT;
-        return;
+    //check inputAgruments only if there are any
+    if(request->inputArgumentsSize > 0){
+        const UA_VariableNode *inputArguments = getArgumentsVariableNode(server, methodCalled, UA_STRING("InputArguments"));
+
+        if(!inputArguments) {
+            result->statusCode = UA_STATUSCODE_BADINVALIDARGUMENT;
+            return;
+        }
+            result->statusCode = argConformsToDefinition(server, inputArguments, request->inputArgumentsSize,
+                                                     request->inputArguments);
+        if(result->statusCode != UA_STATUSCODE_GOOD)
+            return;
     }
-    result->statusCode = argConformsToDefinition(server, inputArguments, request->inputArgumentsSize,
-                                                 request->inputArguments);
-    if(result->statusCode != UA_STATUSCODE_GOOD)
-        return;
 
     /* Allocate the output arguments */
     const UA_VariableNode *outputArguments = getArgumentsVariableNode(server, methodCalled, UA_STRING("OutputArguments"));
     if(!outputArguments) {
-        result->statusCode = UA_STATUSCODE_BADINTERNALERROR;
-        return;
-    }
-    result->outputArguments = UA_Array_new(outputArguments->value.variant.value.arrayLength, &UA_TYPES[UA_TYPES_VARIANT]);
-    if(!result->outputArguments) {
-        result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
-        return;
+        result->outputArgumentsSize=0;
+    }else{
+        result->outputArguments = UA_Array_new(outputArguments->value.variant.value.arrayLength, &UA_TYPES[UA_TYPES_VARIANT]);
+        if(!result->outputArguments) {
+            result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
+            return;
+        }
+        result->outputArgumentsSize = outputArguments->value.variant.value.arrayLength;
     }
-    result->outputArgumentsSize = outputArguments->value.variant.value.arrayLength;
 
 #if defined(UA_ENABLE_METHODCALLS) && defined(UA_ENABLE_SUBSCRIPTIONS)
     methodCallSession = session;

+ 5 - 9
src/server/ua_services_discovery.c

@@ -5,9 +5,10 @@
 #ifdef UA_ENABLE_DISCOVERY
     #ifdef _MSC_VER
     # include <io.h> //access
+    # define access _access
     #else
     # include <unistd.h> //access
-	#endif
+    #endif
 #endif
 
 #ifdef UA_ENABLE_DISCOVERY
@@ -347,7 +348,7 @@ void Service_RegisterServer(UA_Server *server, UA_Session *session,
 
     // copy the data from the request into the list
     UA_RegisteredServer_copy(&request->server, &registeredServer_entry->registeredServer);
-    registeredServer_entry->lastSeen = UA_DateTime_now();
+    registeredServer_entry->lastSeen = UA_DateTime_nowMonotonic();
 
     response->responseHeader.serviceResult = retval;
 }
@@ -358,9 +359,9 @@ void Service_RegisterServer(UA_Server *server, UA_Session *session,
  * When it is deleted, the registration is removed.
  * If there is no semaphore file, then the registration will be removed if it is older than 60 minutes.
  */
-void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now) {
+void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime nowMonotonic) {
 
-    UA_DateTime timedOut = now;
+    UA_DateTime timedOut = nowMonotonic;
     // registration is timed out if lastSeen is older than 60 minutes.
     timedOut -= 60*60*UA_SEC_TO_DATETIME;
 
@@ -373,12 +374,7 @@ void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now) {
             char* filePath = malloc(sizeof(char)*current->registeredServer.semaphoreFilePath.length+1);
             memcpy( filePath, current->registeredServer.semaphoreFilePath.data, current->registeredServer.semaphoreFilePath.length );
             filePath[current->registeredServer.semaphoreFilePath.length] = '\0';
-
-#ifdef _MSC_VER
-            semaphoreDeleted = _access( filePath, 0 ) == -1;
-#else
             semaphoreDeleted = access( filePath, 0 ) == -1;
-#endif
             free(filePath);
         }
 

+ 49 - 44
src/server/ua_services_nodemanagement.c

@@ -873,52 +873,57 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
     parent.nodeId = result.addedNodeId;
 
     const UA_NodeId hasproperty = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY);
-    UA_VariableNode *inputArgumentsVariableNode = UA_NodeStore_newVariableNode();
-    inputArgumentsVariableNode->nodeId.namespaceIndex = result.addedNodeId.namespaceIndex;
-    inputArgumentsVariableNode->browseName = UA_QUALIFIEDNAME_ALLOC(0, "InputArguments");
-    inputArgumentsVariableNode->displayName = UA_LOCALIZEDTEXT_ALLOC("en_US", "InputArguments");
-    inputArgumentsVariableNode->description = UA_LOCALIZEDTEXT_ALLOC("en_US", "InputArguments");
-    inputArgumentsVariableNode->valueRank = 1;
-    //TODO: 0.3 work item: the addMethodNode API does not have the possibility to set nodeIDs
-    //actually we need to change the signature to pass UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS
-    //and UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS into the function :/
-    if(result.addedNodeId.namespaceIndex == 0 &&
-       result.addedNodeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
-       result.addedNodeId.identifier.numeric == UA_NS0ID_SERVER_GETMONITOREDITEMS){
-        inputArgumentsVariableNode->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS);
+
+    if(inputArgumentsSize > 0){
+        UA_VariableNode *inputArgumentsVariableNode = UA_NodeStore_newVariableNode();
+        inputArgumentsVariableNode->nodeId.namespaceIndex = result.addedNodeId.namespaceIndex;
+        inputArgumentsVariableNode->browseName = UA_QUALIFIEDNAME_ALLOC(0, "InputArguments");
+        inputArgumentsVariableNode->displayName = UA_LOCALIZEDTEXT_ALLOC("en_US", "InputArguments");
+        inputArgumentsVariableNode->description = UA_LOCALIZEDTEXT_ALLOC("en_US", "InputArguments");
+        inputArgumentsVariableNode->valueRank = 1;
+        //TODO: 0.3 work item: the addMethodNode API does not have the possibility to set nodeIDs
+        //actually we need to change the signature to pass UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS
+        //and UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS into the function :/
+        if(result.addedNodeId.namespaceIndex == 0 &&
+           result.addedNodeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
+           result.addedNodeId.identifier.numeric == UA_NS0ID_SERVER_GETMONITOREDITEMS){
+            inputArgumentsVariableNode->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS);
+        }
+        UA_Variant_setArrayCopy(&inputArgumentsVariableNode->value.variant.value, inputArguments,
+                                inputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
+        UA_AddNodesResult inputAddRes;
+        UA_RCU_LOCK();
+        Service_AddNodes_existing(server, &adminSession, (UA_Node*)inputArgumentsVariableNode,
+                                  &parent.nodeId, &hasproperty, &UA_NODEID_NULL, NULL, &inputAddRes);
+        UA_RCU_UNLOCK();
+        // todo: check if adding succeeded
+        UA_AddNodesResult_deleteMembers(&inputAddRes);
     }
-    UA_Variant_setArrayCopy(&inputArgumentsVariableNode->value.variant.value, inputArguments,
-                            inputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
-    UA_AddNodesResult inputAddRes;
-    UA_RCU_LOCK();
-    Service_AddNodes_existing(server, &adminSession, (UA_Node*)inputArgumentsVariableNode,
-                              &parent.nodeId, &hasproperty, &UA_NODEID_NULL, NULL, &inputAddRes);
-    UA_RCU_UNLOCK();
-    // todo: check if adding succeeded
-    UA_AddNodesResult_deleteMembers(&inputAddRes);
-
-    /* create OutputArguments */
-    UA_VariableNode *outputArgumentsVariableNode  = UA_NodeStore_newVariableNode();
-    outputArgumentsVariableNode->nodeId.namespaceIndex = result.addedNodeId.namespaceIndex;
-    outputArgumentsVariableNode->browseName  = UA_QUALIFIEDNAME_ALLOC(0, "OutputArguments");
-    outputArgumentsVariableNode->displayName = UA_LOCALIZEDTEXT_ALLOC("en_US", "OutputArguments");
-    outputArgumentsVariableNode->description = UA_LOCALIZEDTEXT_ALLOC("en_US", "OutputArguments");
-    outputArgumentsVariableNode->valueRank = 1;
-    //FIXME: comment in line 882
-    if(result.addedNodeId.namespaceIndex == 0 &&
-       result.addedNodeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
-       result.addedNodeId.identifier.numeric == UA_NS0ID_SERVER_GETMONITOREDITEMS){
-        outputArgumentsVariableNode->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS);
+
+    if(outputArgumentsSize > 0){
+        /* create OutputArguments */
+        UA_VariableNode *outputArgumentsVariableNode  = UA_NodeStore_newVariableNode();
+        outputArgumentsVariableNode->nodeId.namespaceIndex = result.addedNodeId.namespaceIndex;
+        outputArgumentsVariableNode->browseName  = UA_QUALIFIEDNAME_ALLOC(0, "OutputArguments");
+        outputArgumentsVariableNode->displayName = UA_LOCALIZEDTEXT_ALLOC("en_US", "OutputArguments");
+        outputArgumentsVariableNode->description = UA_LOCALIZEDTEXT_ALLOC("en_US", "OutputArguments");
+        outputArgumentsVariableNode->valueRank = 1;
+        //FIXME: comment in line 882
+        if(result.addedNodeId.namespaceIndex == 0 &&
+           result.addedNodeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
+           result.addedNodeId.identifier.numeric == UA_NS0ID_SERVER_GETMONITOREDITEMS){
+            outputArgumentsVariableNode->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS);
+        }
+        UA_Variant_setArrayCopy(&outputArgumentsVariableNode->value.variant.value, outputArguments,
+                                outputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
+        UA_AddNodesResult outputAddRes;
+        UA_RCU_LOCK();
+        Service_AddNodes_existing(server, &adminSession, (UA_Node*)outputArgumentsVariableNode,
+                                  &parent.nodeId, &hasproperty, &UA_NODEID_NULL, NULL, &outputAddRes);
+        UA_RCU_UNLOCK();
+        // todo: check if adding succeeded
+        UA_AddNodesResult_deleteMembers(&outputAddRes);
     }
-    UA_Variant_setArrayCopy(&outputArgumentsVariableNode->value.variant.value, outputArguments,
-                            outputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
-    UA_AddNodesResult outputAddRes;
-    UA_RCU_LOCK();
-    Service_AddNodes_existing(server, &adminSession, (UA_Node*)outputArgumentsVariableNode,
-                              &parent.nodeId, &hasproperty, &UA_NODEID_NULL, NULL, &outputAddRes);
-    UA_RCU_UNLOCK();
-    // todo: check if adding succeeded
-    UA_AddNodesResult_deleteMembers(&outputAddRes);
 
     if(outNewNodeId)
         *outNewNodeId = result.addedNodeId; // don't deleteMember the result

+ 2 - 3
src/server/ua_services_session.c

@@ -50,7 +50,7 @@ void Service_CreateSession(UA_Server *server, UA_SecureChannel *channel,
 void
 Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel, UA_Session *session,
                         const UA_ActivateSessionRequest *request, UA_ActivateSessionResponse *response) {
-    if(session->validTill < UA_DateTime_now()) {
+    if(session->validTill < UA_DateTime_nowMonotonic()) {
         UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: SecureChannel %i wants "
                             "to activate, but the session has timed out", channel->securityToken.channelId);
         response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID;
@@ -61,8 +61,7 @@ Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel, UA_Session
        (request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] &&
         request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN])) {
         UA_LOG_INFO_SESSION(server->config.logger, session, "ActivateSession: SecureChannel %i wants "
-                            "to activate, but the UserIdentify token is invalid",
-                            channel->securityToken.channelId);
+                            "to activate, but the UserIdentify token is invalid", channel->securityToken.channelId);
         response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID;
         return;
     }

+ 3 - 3
src/server/ua_session_manager.c

@@ -18,10 +18,10 @@ void UA_SessionManager_deleteMembers(UA_SessionManager *sm) {
     }
 }
 
-void UA_SessionManager_cleanupTimedOut(UA_SessionManager *sm, UA_DateTime now) {
+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 < now) {
+        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);
@@ -43,7 +43,7 @@ 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_now() > current->session.validTill) {
+            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)));

+ 1 - 1
src/server/ua_session_manager.h

@@ -22,7 +22,7 @@ UA_SessionManager_init(UA_SessionManager *sm, UA_Server *server);
 
 void UA_SessionManager_deleteMembers(UA_SessionManager *sessionManager);
 
-void UA_SessionManager_cleanupTimedOut(UA_SessionManager *sessionManager, UA_DateTime now);
+void UA_SessionManager_cleanupTimedOut(UA_SessionManager *sessionManager, UA_DateTime nowMonotonic);
 
 UA_StatusCode
 UA_SessionManager_createSession(UA_SessionManager *sessionManager, UA_SecureChannel *channel,

+ 1 - 1
src/ua_session.c

@@ -69,7 +69,7 @@ void UA_Session_deleteMembersCleanup(UA_Session *session, UA_Server* server) {
 }
 
 void UA_Session_updateLifetime(UA_Session *session) {
-    session->validTill = UA_DateTime_now() + (UA_DateTime)(session->timeout * UA_MSEC_TO_DATETIME);
+    session->validTill = UA_DateTime_nowMonotonic() + (UA_DateTime)(session->timeout * UA_MSEC_TO_DATETIME);
 }
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS

+ 1 - 1
src/ua_types_encoding_binary.c

@@ -966,7 +966,7 @@ DataValue_encodeBinary(UA_DataValue const *src, const UA_DataType *_) {
     return retval;
 }
 
-#define MAX_PICO_SECONDS 999
+#define MAX_PICO_SECONDS 9999
 static UA_StatusCode
 DataValue_decodeBinary(UA_DataValue *dst, const UA_DataType *_) {
     UA_Byte encodingMask;

+ 1 - 1
src/ua_util.h

@@ -47,7 +47,7 @@
 /* Thread Local Storage */
 /************************/
 
-#if __STDC_VERSION__ >= 201112L
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
 # define UA_THREAD_LOCAL _Thread_local /* C11 */
 #elif defined(__GNUC__)
 # define UA_THREAD_LOCAL __thread /* GNU extension */