Procházet zdrojové kódy

add the BrowseNext service

Julius Pfrommer před 10 roky
rodič
revize
aabd4b22fc

+ 1 - 0
include/ua_client.h

@@ -49,6 +49,7 @@ UA_WriteResponse UA_EXPORT UA_Client_write(UA_Client *client, UA_WriteRequest *r
 
 /* View Service Set */    
 UA_BrowseResponse UA_EXPORT UA_Client_browse(UA_Client *client, UA_BrowseRequest *request);
+UA_BrowseNextResponse UA_EXPORT UA_Client_browseNext(UA_Client *client, UA_BrowseNextRequest *request);
 UA_TranslateBrowsePathsToNodeIdsResponse UA_EXPORT
     UA_Client_translateTranslateBrowsePathsToNodeIds(UA_Client *client,
                                                      UA_TranslateBrowsePathsToNodeIdsRequest *request);

+ 7 - 0
src/client/ua_client.c

@@ -462,6 +462,13 @@ UA_BrowseResponse UA_Client_browse(UA_Client *client, UA_BrowseRequest *request)
     return response;
 }
 
+UA_BrowseNextResponse UA_Client_browseNext(UA_Client *client, UA_BrowseNextRequest *request) {
+    UA_BrowseNextResponse response;
+    synchronousRequest(request, &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], &response,
+                       &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE], client);
+    return response;
+}
+
 UA_TranslateBrowsePathsToNodeIdsResponse
     UA_Client_translateTranslateBrowsePathsToNodeIds(UA_Client *client,
                                                      UA_TranslateBrowsePathsToNodeIdsRequest *request) {

+ 20 - 21
src/server/ua_server_binary.c

@@ -148,9 +148,16 @@ static void init_response_header(const UA_RequestHeader *p, UA_ResponseHeader *r
         UA_##TYPE##Response r;                                          \
         if(UA_##TYPE##Request_decodeBinary(msg, pos, &p))               \
             return;                                                     \
+        if(clientChannel->session &&                                    \
+           UA_NodeId_equal(&clientChannel->session->authenticationToken, \
+                           &p.requestHeader.authenticationToken))       \
+            clientSession = clientChannel->session;                     \
         UA_##TYPE##Response_init(&r);                                   \
         init_response_header(&p.requestHeader, &r.responseHeader);      \
-        Service_##TYPE(server, clientSession, &p, &r);                  \
+        if(!clientSession)                                              \
+            r.responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID; \
+        else                                                            \
+            Service_##TYPE(server, clientSession, &p, &r);              \
         ALLOC_MESSAGE(message, UA_##TYPE##Response_calcSizeBinary(&r)); \
         UA_##TYPE##Response_encodeBinary(&r, message, &sendOffset);     \
         UA_##TYPE##Request_deleteMembers(&p);                           \
@@ -172,12 +179,6 @@ static void processMSG(UA_Connection *connection, UA_Server *server, const UA_By
         clientChannel = &anonymousChannel;
     }
 
-    UA_Session *clientSession = clientChannel->session;
-#ifdef EXTENSION_STATELESS
-    if(secureChannelId == 0)
-        clientSession = &anonymousSession;
-#endif
-
     // 2) Read the security header
     UA_UInt32 tokenId;
     UA_SequenceHeader sequenceHeader;
@@ -191,6 +192,12 @@ static void processMSG(UA_Connection *connection, UA_Server *server, const UA_By
     clientChannel->sequenceNumber = sequenceHeader.sequenceNumber;
     clientChannel->requestId = sequenceHeader.requestId;
 
+    UA_Session *clientSession = UA_NULL;
+#ifdef EXTENSION_STATELESS
+    if(clientChannel->session == UA_NULL && secureChannelId == 0)
+        clientSession = &anonymousSession;
+#endif
+
     // 3) Read the nodeid of the request
     UA_NodeId requestType;
     if(UA_NodeId_decodeBinary(msg, pos, &requestType))
@@ -287,21 +294,9 @@ static void processMSG(UA_Connection *connection, UA_Server *server, const UA_By
         break;
     }
 
-    case UA_NS0ID_CLOSESESSIONREQUEST: {
-        UA_CloseSessionRequest  p;
-        UA_CloseSessionResponse r;
-        if(UA_CloseSessionRequest_decodeBinary(msg, pos, &p))
-            return;
-        UA_CloseSessionResponse_init(&r);
-        init_response_header(&p.requestHeader, &r.responseHeader);
-        Service_CloseSession(server, &p, &r);
-        ALLOC_MESSAGE(message, UA_CloseSessionResponse_calcSizeBinary(&r));
-        UA_CloseSessionResponse_encodeBinary(&r, message, &sendOffset);
-        UA_CloseSessionRequest_deleteMembers(&p);
-        UA_CloseSessionResponse_deleteMembers(&r);
-        responseType = requestType.identifier.numeric + 3;
+    case UA_NS0ID_CLOSESESSIONREQUEST:
+        INVOKE_SERVICE(CloseSession);
         break;
-    }
 
     case UA_NS0ID_READREQUEST:
         INVOKE_SERVICE(Read);
@@ -315,6 +310,10 @@ static void processMSG(UA_Connection *connection, UA_Server *server, const UA_By
         INVOKE_SERVICE(Browse);
         break;
 
+    case UA_NS0ID_BROWSENEXTREQUEST:
+        INVOKE_SERVICE(BrowseNext);
+        break;
+
     case UA_NS0ID_ADDREFERENCESREQUEST:
         INVOKE_SERVICE(AddReferences);
         break;

+ 13 - 4
src/server/ua_services.h

@@ -89,7 +89,7 @@ void Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel,
                              const UA_ActivateSessionRequest *request, UA_ActivateSessionResponse *response);
 
 /** Used to terminate a Session. */
-void Service_CloseSession(UA_Server *server, const UA_CloseSessionRequest *request,
+void Service_CloseSession(UA_Server *server, UA_Session *session, const UA_CloseSessionRequest *request,
                           UA_CloseSessionResponse *response);
 // Service_Cancel
 /** @} */
@@ -136,14 +136,23 @@ void Service_DeleteReferences(UA_Server *server, UA_Session *session, const UA_D
  * further limited by the use of a View. This Browse Service also supports a
  * primitive filtering capability.
  */
-void Service_Browse(UA_Server *server, UA_Session *session,
-                    const UA_BrowseRequest *request, UA_BrowseResponse *response);
+void Service_Browse(UA_Server *server, UA_Session *session, const UA_BrowseRequest *request,
+                    UA_BrowseResponse *response);
+
+/**
+ * Used to request the next set of Browse or BrowseNext response information
+ * that is too large to be sent in a single response. “Too large” in this
+ * context means that the Server is not able to return a larger response or that
+ * the number of results to return exceeds the maximum number of results to
+ * return that was specified by the Client in the original Browse request.
+ */
+void Service_BrowseNext(UA_Server *server, UA_Session *session, const UA_BrowseNextRequest *request,
+                        UA_BrowseNextResponse *response);
 
 /** Used to translate textual node paths to their respective ids. */
 void Service_TranslateBrowsePathsToNodeIds(UA_Server *server, UA_Session *session,
                                            const UA_TranslateBrowsePathsToNodeIdsRequest *request,
                                            UA_TranslateBrowsePathsToNodeIdsResponse *response);
-// Service_BrowseNext
 
 // Service_RegisterNodes
 void Service_RegisterNodes(UA_Server *server, UA_Session *session, const UA_RegisterNodesRequest *request,

+ 4 - 6
src/server/ua_services_session.c

@@ -53,8 +53,8 @@ void Service_ActivateSession(UA_Server *server,UA_SecureChannel *channel,
         channel->session = foundSession;
 }
 
-void Service_CloseSession(UA_Server *server, const UA_CloseSessionRequest *request,
-                              UA_CloseSessionResponse *response) {
+void Service_CloseSession(UA_Server *server, UA_Session *session, const UA_CloseSessionRequest *request,
+                          UA_CloseSessionResponse *response) {
 	UA_Session *foundSession;
 	UA_SessionManager_getSessionByToken(&server->sessionManager,
 			(const UA_NodeId*)&request->requestHeader.authenticationToken, &foundSession);
@@ -64,8 +64,6 @@ void Service_CloseSession(UA_Server *server, const UA_CloseSessionRequest *reque
 		return;
 	}
 
-	if(UA_SessionManager_removeSession(&server->sessionManager, &foundSession->sessionId) == UA_STATUSCODE_GOOD)
-		response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
-	else
-		response->responseHeader.serviceResult = UA_STATUSCODE_BADSECURECHANNELIDINVALID;
+	response->responseHeader.serviceResult =
+        UA_SessionManager_removeSession(&server->sessionManager, &session->sessionId);
 }

+ 159 - 56
src/server/ua_services_view.c

@@ -4,9 +4,8 @@
 #include "ua_nodestore.h"
 #include "ua_util.h"
 
-static UA_StatusCode
-fillrefdescr(UA_NodeStore *ns, const UA_Node *curr, UA_ReferenceNode *ref, UA_UInt32 mask,
-             UA_ReferenceDescription *descr)
+static UA_StatusCode fillrefdescr(UA_NodeStore *ns, const UA_Node *curr, UA_ReferenceNode *ref,
+                                  UA_UInt32 mask, UA_ReferenceDescription *descr)
 {
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     UA_ReferenceDescription_init(descr);
@@ -25,13 +24,14 @@ fillrefdescr(UA_NodeStore *ns, const UA_Node *curr, UA_ReferenceNode *ref, UA_UI
         retval |= UA_QualifiedName_copy(&curr->browseName, &descr->browseName);
     if(mask & UA_BROWSERESULTMASK_DISPLAYNAME)
         retval |= UA_LocalizedText_copy(&curr->displayName, &descr->displayName);
-    if(mask & UA_BROWSERESULTMASK_TYPEDEFINITION ) {
-        for(UA_Int32 i = 0;i < curr->referencesSize;i++) {
+    if(mask & UA_BROWSERESULTMASK_TYPEDEFINITION &&
+       (curr->nodeClass == UA_NODECLASS_OBJECT || curr->nodeClass == UA_NODECLASS_VARIABLE)) {
+        for(UA_Int32 i = 0; i < curr->referencesSize; i++) {
             UA_ReferenceNode *refnode = &curr->references[i];
-            if(refnode->referenceTypeId.identifier.numeric == UA_NS0ID_HASTYPEDEFINITION) {
-                retval |= UA_ExpandedNodeId_copy(&refnode->targetId, &descr->typeDefinition);
-                break;
-            }
+            if(refnode->referenceTypeId.identifier.numeric != UA_NS0ID_HASTYPEDEFINITION)
+                continue;
+            retval |= UA_ExpandedNodeId_copy(&refnode->targetId, &descr->typeDefinition);
+            break;
         }
     }
 
@@ -42,9 +42,9 @@ fillrefdescr(UA_NodeStore *ns, const UA_Node *curr, UA_ReferenceNode *ref, UA_UI
 
 /* Tests if the node is relevant to the browse request and shall be returned. If
    so, it is retrieved from the Nodestore. If not, null is returned. */
-static const UA_Node
-*relevant_node(UA_NodeStore *ns, const UA_BrowseDescription *descr, UA_Boolean return_all,
-               UA_ReferenceNode *reference, UA_NodeId *relevant, size_t relevant_count)
+static const UA_Node *relevant_node(UA_NodeStore *ns, const UA_BrowseDescription *descr,
+                                    UA_Boolean return_all, UA_ReferenceNode *reference,
+                                    UA_NodeId *relevant, size_t relevant_count)
 {
     if(reference->isInverse == UA_TRUE && descr->browseDirection == UA_BROWSEDIRECTION_FORWARD)
         return UA_NULL;
@@ -73,14 +73,21 @@ is_relevant: ;
  * the array and increase the size. Since the hierarchy is not cyclic, we can safely progress in the
  * array to process the newly found referencetype nodeids (emulated recursion).
  */
-static UA_StatusCode
-findsubtypes(UA_NodeStore *ns, const UA_NodeId *root, UA_NodeId **reftypes, size_t *reftypes_count)
+static UA_StatusCode findsubtypes(UA_NodeStore *ns, const UA_NodeId *root, UA_NodeId **reftypes,
+                                  size_t *reftypes_count)
 {
     size_t results_size = 20; // probably too big, but saves mallocs
     UA_NodeId *results = UA_malloc(sizeof(UA_NodeId) * results_size);
     if(!results)
         return UA_STATUSCODE_BADOUTOFMEMORY;
 
+    const UA_Node *node = UA_NodeStore_get(ns, root);
+    if(!node || node->nodeClass != UA_NODECLASS_REFERENCETYPE)  {
+        UA_NodeStore_release(node);
+        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
+    }
+    UA_NodeStore_release(node);
+
     UA_StatusCode retval = UA_NodeId_copy(root, &results[0]);
     if(retval != UA_STATUSCODE_GOOD) {
         UA_free(results);
@@ -90,7 +97,7 @@ findsubtypes(UA_NodeStore *ns, const UA_NodeId *root, UA_NodeId **reftypes, size
     size_t index = 0; // where are we currently in the array?
     size_t last = 0; // where is the last element in the array?
     do {
-        const UA_Node *node = UA_NodeStore_get(ns, &results[index]);
+        node = UA_NodeStore_get(ns, &results[index]);
         if(!node || node->nodeClass != UA_NODECLASS_REFERENCETYPE)
             continue;
         for(UA_Int32 i = 0; i < node->referencesSize; i++) {
@@ -127,19 +134,36 @@ findsubtypes(UA_NodeStore *ns, const UA_NodeId *root, UA_NodeId **reftypes, size
     return UA_STATUSCODE_GOOD;
 }
 
-/* Results for a single browsedescription. */
-static void
-browse(UA_NodeStore *ns, const UA_BrowseDescription *descr, UA_UInt32 maxrefs, UA_BrowseResult *result)
+/* Results for a single browsedescription. Call this either with an existing continuationpoint from
+   which we take the entire context for the search. Or, call with a browsedescription and maxrefs
+   value. If we need to create a new continuationpoint, it will be alloced and the new pointer
+   stored in *cp.
+ */
+static void browse(UA_NodeStore *ns, struct ContinuationPointEntry **cp, const UA_BrowseDescription *descr, 
+                   UA_UInt32 maxrefs, UA_BrowseResult *result)
 {
+    UA_UInt32 continuationIndex = 0;
+    if(*cp) {
+        descr = &(*cp)->browseDescription;
+        maxrefs = (*cp)->maxReferences;
+        continuationIndex = (*cp)->continuationIndex;
+    }
+
+    if(descr->browseDirection != UA_BROWSEDIRECTION_BOTH &&
+       descr->browseDirection != UA_BROWSEDIRECTION_FORWARD &&
+       descr->browseDirection != UA_BROWSEDIRECTION_INVERSE) {
+        result->statusCode = UA_STATUSCODE_BADBROWSEDIRECTIONINVALID;
+        return;
+    }
+    
     size_t relevant_refs_size = 0;
     UA_NodeId *relevant_refs = UA_NULL;
-
     // what are the relevant references?
     UA_Boolean all_refs = UA_NodeId_isNull(&descr->referenceTypeId);
     if(!all_refs) {
         if(descr->includeSubtypes) {
-            result->statusCode = findsubtypes(ns, &descr->referenceTypeId,
-                                              &relevant_refs, &relevant_refs_size);
+            result->statusCode = findsubtypes(ns, &descr->referenceTypeId, &relevant_refs,
+                                              &relevant_refs_size);
             if(result->statusCode != UA_STATUSCODE_GOOD)
                 return;
         } else {
@@ -162,35 +186,64 @@ browse(UA_NodeStore *ns, const UA_BrowseDescription *descr, UA_UInt32 maxrefs, U
         goto cleanup;
     }
 
-    // allocate memory for the results
-    maxrefs = node->referencesSize; // allocate enough space for all of them
-    result->references = UA_malloc(sizeof(UA_ReferenceDescription) * maxrefs);
+    UA_UInt32 real_maxrefs = maxrefs;
+    if(real_maxrefs == 0)
+        real_maxrefs = node->referencesSize;
+    result->references = UA_malloc(sizeof(UA_ReferenceDescription) * real_maxrefs);
     if(!result->references) {
         result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
         goto cleanup;
     }
 
     size_t count = 0;
-    for(UA_Int32 i = 0; i < node->referencesSize && count < maxrefs; i++) {
-        const UA_Node *current;
-        current = relevant_node(ns, descr, all_refs, &node->references[i], relevant_refs, relevant_refs_size);
+    size_t skipped = 0;
+    for(UA_Int32 i = 0; i < node->referencesSize && count < real_maxrefs; i++) {
+        const UA_Node *current = relevant_node(ns, descr, all_refs, &node->references[i],
+                                               relevant_refs, relevant_refs_size);
         if(!current)
             continue;
-
-        UA_StatusCode retval = fillrefdescr(ns, current, &node->references[i],
-                                            descr->resultMask, &result->references[count]);
+        if(skipped < continuationIndex) {
+            UA_NodeStore_release(current);
+            skipped++;
+            continue;
+        }
+        UA_StatusCode retval = fillrefdescr(ns, current, &node->references[i], descr->resultMask,
+                                            &result->references[count]);
         UA_NodeStore_release(current);
-
-        if(retval) {
+        if(retval != UA_STATUSCODE_GOOD) {
             UA_Array_delete(result->references, &UA_TYPES[UA_TYPES_REFERENCEDESCRIPTION], count);
-            count = 0;
             result->references = UA_NULL;
+            count = 0;
             result->statusCode = UA_STATUSCODE_UNCERTAINNOTALLNODESAVAILABLE;
-            break;
+            goto cleanup;
         }
         count++;
     }
-    if(count != 0)
+
+    if(*cp) {
+        (*cp)->continuationIndex += count;
+        if((*cp)->continuationIndex == node->referencesSize) {
+            /* remove a finished continuationPoint */
+            UA_ByteString_deleteMembers(&(*cp)->identifier);
+            UA_BrowseDescription_deleteMembers(&(*cp)->browseDescription);
+            LIST_REMOVE(*cp, pointers);
+            UA_free(*cp);
+            *cp = UA_NULL;
+        }
+    } else if(maxrefs != 0 && count >= maxrefs) {
+        /* create a continuationPoint */
+        *cp = UA_malloc(sizeof(struct ContinuationPointEntry));
+        UA_BrowseDescription_copy(descr,&(*cp)->browseDescription);
+        (*cp)->maxReferences = maxrefs;
+        (*cp)->continuationIndex = count;
+        UA_Guid *ident = UA_Guid_new();
+        UA_UInt32 seed = (uintptr_t)*cp;
+        *ident = UA_Guid_random(&seed);
+        (*cp)->identifier.data = (UA_Byte*)ident;
+        (*cp)->identifier.length = sizeof(UA_Guid);
+    }
+    
+    if(count > 0)
         result->referencesSize = count;
     else {
         UA_free(result->references);
@@ -205,6 +258,11 @@ cleanup:
 
 void Service_Browse(UA_Server *server, UA_Session *session, const UA_BrowseRequest *request,
                     UA_BrowseResponse *response) {
+    if(!UA_NodeId_isNull(&request->view.viewId)) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADVIEWIDUNKNOWN;
+        return;
+    }
+    
    if(request->nodesToBrowseSize <= 0) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
         return;
@@ -240,13 +298,63 @@ void Service_Browse(UA_Server *server, UA_Session *session, const UA_BrowseReque
     /* ### End External Namespaces */
 
     response->resultsSize = size;
-    for(size_t i = 0;i < size;i++){
-        if(!isExternal[i])
-            browse(server->nodestore, &request->nodesToBrowse[i],
+    for(size_t i = 0; i < size; i++) {
+        if(!isExternal[i]) {
+            struct ContinuationPointEntry *cp = UA_NULL;
+            browse(server->nodestore, &cp, &request->nodesToBrowse[i],
                    request->requestedMaxReferencesPerNode, &response->results[i]);
+            if(cp) {
+                LIST_INSERT_HEAD(&session->continuationPoints, cp, pointers);
+                UA_ByteString_copy(&cp->identifier, &response->results[i].continuationPoint);
+            }
+        }
     }
 }
 
+void Service_BrowseNext(UA_Server *server, UA_Session *session, const UA_BrowseNextRequest *request,
+                        UA_BrowseNextResponse *response) {
+   if(request->continuationPointsSize <= 0) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
+        return;
+   }
+   size_t size = request->continuationPointsSize;
+   if(!request->releaseContinuationPoints) {
+       response->results = UA_Array_new(&UA_TYPES[UA_TYPES_BROWSERESULT], size);
+       if(!response->results) {
+           response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+           return;
+       }
+       response->resultsSize = size;
+       for(size_t i = 0; i < size; i++) {
+           struct ContinuationPointEntry *cp = UA_NULL;
+           struct ContinuationPointEntry *search_cp;
+           LIST_FOREACH(search_cp, &session->continuationPoints, pointers) {
+               if(UA_ByteString_equal(&search_cp->identifier, &request->continuationPoints[i])) {
+                   cp = search_cp;
+                   break;
+               }
+           }
+           if(!cp)
+               response->results[i].statusCode = UA_STATUSCODE_BADCONTINUATIONPOINTINVALID;
+           else
+               browse(server->nodestore, &cp, UA_NULL, 0, &response->results[i]);
+       }
+   } else {
+       for(size_t i = 0; i < size; i++) {
+           struct ContinuationPointEntry *cp = UA_NULL;
+           LIST_FOREACH(cp, &session->continuationPoints, pointers) {
+               if(UA_ByteString_equal(&cp->identifier, &request->continuationPoints[i])) {
+                   UA_ByteString_deleteMembers(&cp->identifier);
+                   UA_BrowseDescription_deleteMembers(&cp->browseDescription);
+                   LIST_REMOVE(cp, pointers);
+                   UA_free(cp);
+                   break;
+               }
+           }
+       }
+   }
+}
+
 /***********************/
 /* TranslateBrowsePath */
 /***********************/
@@ -375,6 +483,7 @@ void Service_TranslateBrowsePathsToNodeIds(UA_Server *server, UA_Session *sessio
         response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
     }
+
     /* ### Begin External Namespaces */
     UA_Boolean *isExternal = UA_alloca(sizeof(UA_Boolean) * size);
     UA_memset(isExternal, UA_FALSE, sizeof(UA_Boolean) * size);
@@ -396,40 +505,34 @@ void Service_TranslateBrowsePathsToNodeIds(UA_Server *server, UA_Session *sessio
     			indices, indexSize, response->results, response->diagnosticInfos);
     }
     /* ### End External Namespaces */
+
     response->resultsSize = size;
-    for(size_t i = 0;i < size;i++){
+    for(size_t i = 0; i < size; i++){
     	if(!isExternal[i])
     		translateBrowsePath(server, session, &request->browsePaths[i], &response->results[i]);
     }
 }
 
-void Service_RegisterNodes(UA_Server *server, UA_Session *session,
-                                           const UA_RegisterNodesRequest *request,
-                                           UA_RegisterNodesResponse *response) {
-
+void Service_RegisterNodes(UA_Server *server, UA_Session *session, const UA_RegisterNodesRequest *request,
+                           UA_RegisterNodesResponse *response) {
 	//TODO: hang the nodeids to the session if really needed
 	response->responseHeader.timestamp = UA_DateTime_now();
 	response->registeredNodeIdsSize = request->nodesToRegisterSize;
 	response->registeredNodeIds = request->nodesToRegister;
-	if(request->nodesToRegisterSize==0){
+	if(request->nodesToRegisterSize==0)
 		response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
-	}
-	if(UA_NodeId_equal(&request->requestHeader.authenticationToken, &UA_NODEID_NULL) || !UA_NodeId_equal(&request->requestHeader.authenticationToken, &session->authenticationToken) ){
+	if(UA_NodeId_equal(&request->requestHeader.authenticationToken, &UA_NODEID_NULL) ||
+       !UA_NodeId_equal(&request->requestHeader.authenticationToken, &session->authenticationToken))
 		response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID;
-	}
-
 }
 
-void Service_UnregisterNodes(UA_Server *server, UA_Session *session,
-                                           const UA_UnregisterNodesRequest *request,
-                                           UA_UnregisterNodesResponse *response) {
-
+void Service_UnregisterNodes(UA_Server *server, UA_Session *session, const UA_UnregisterNodesRequest *request,
+                             UA_UnregisterNodesResponse *response) {
 	//TODO: remove the nodeids from the session if really needed
 	response->responseHeader.timestamp = UA_DateTime_now();
-	if(request->nodesToUnregisterSize==0){
+	if(request->nodesToUnregisterSize==0)
 		response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
-	}
-	if(UA_NodeId_equal(&request->requestHeader.authenticationToken, &UA_NODEID_NULL) || !UA_NodeId_equal(&request->requestHeader.authenticationToken, &session->authenticationToken) ){
+	if(UA_NodeId_equal(&request->requestHeader.authenticationToken, &UA_NODEID_NULL) ||
+       !UA_NodeId_equal(&request->requestHeader.authenticationToken, &session->authenticationToken))
 		response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID;
-	}
 }

+ 24 - 24
src/ua_session.c

@@ -3,40 +3,32 @@
 #include "ua_statuscodes.h"
 
 UA_Session anonymousSession = {
-    .clientDescription =  {.applicationUri = {-1, UA_NULL},
-                           .productUri = {-1, UA_NULL},
+    .clientDescription =  {.applicationUri = {-1, UA_NULL}, .productUri = {-1, UA_NULL},
                            .applicationName = {.locale = {-1, UA_NULL}, .text = {-1, UA_NULL}},
                            .applicationType = UA_APPLICATIONTYPE_CLIENT,
-                           .gatewayServerUri = {-1, UA_NULL},
-                           .discoveryProfileUri = {-1, UA_NULL},
-                           .discoveryUrlsSize = -1,
-                           .discoveryUrls = UA_NULL},
+                           .gatewayServerUri = {-1, UA_NULL}, .discoveryProfileUri = {-1, UA_NULL},
+                           .discoveryUrlsSize = -1, .discoveryUrls = UA_NULL},
     .sessionName = {sizeof("Anonymous Session")-1, (UA_Byte*)"Anonymous Session"},
-    .authenticationToken = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = 0}, // is never used, as this session is not stored in the sessionmanager
+    .authenticationToken = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC,
+                            .identifier.numeric = 0}, 
     .sessionId = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = 0},
-    .maxRequestMessageSize = UA_UINT32_MAX,
-    .maxResponseMessageSize = UA_UINT32_MAX,
-    .timeout = UA_INT64_MAX,
-    .validTill = UA_INT64_MAX,
-    .channel = UA_NULL};
+    .maxRequestMessageSize = UA_UINT32_MAX, .maxResponseMessageSize = UA_UINT32_MAX,
+    .timeout = UA_INT64_MAX, .validTill = UA_INT64_MAX, .channel = UA_NULL,
+    .continuationPoints = {UA_NULL}};
 
 UA_Session adminSession = {
-    .clientDescription =  {.applicationUri = {-1, UA_NULL},
-                           .productUri = {-1, UA_NULL},
+    .clientDescription =  {.applicationUri = {-1, UA_NULL}, .productUri = {-1, UA_NULL},
                            .applicationName = {.locale = {-1, UA_NULL}, .text = {-1, UA_NULL}},
                            .applicationType = UA_APPLICATIONTYPE_CLIENT,
-                           .gatewayServerUri = {-1, UA_NULL},
-                           .discoveryProfileUri = {-1, UA_NULL},
-                           .discoveryUrlsSize = -1,
-                           .discoveryUrls = UA_NULL},
+                           .gatewayServerUri = {-1, UA_NULL}, .discoveryProfileUri = {-1, UA_NULL},
+                           .discoveryUrlsSize = -1, .discoveryUrls = UA_NULL},
     .sessionName = {sizeof("Administrator Session")-1, (UA_Byte*)"Administrator Session"},
-    .authenticationToken = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = 1}, // is never used, as this session is not stored in the sessionmanager
+    .authenticationToken = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC,
+                            .identifier.numeric = 1},
     .sessionId = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = 1},
-    .maxRequestMessageSize = UA_UINT32_MAX,
-    .maxResponseMessageSize = UA_UINT32_MAX,
-    .timeout = UA_INT64_MAX,
-    .validTill = UA_INT64_MAX,
-    .channel = UA_NULL};
+    .maxRequestMessageSize = UA_UINT32_MAX, .maxResponseMessageSize = UA_UINT32_MAX,
+    .timeout = UA_INT64_MAX, .validTill = UA_INT64_MAX, .channel = UA_NULL,
+    .continuationPoints = {UA_NULL}};
 
 UA_Session * UA_Session_new(void) {
     UA_Session *s = UA_malloc(sizeof(UA_Session));
@@ -63,6 +55,7 @@ void UA_Session_init(UA_Session *session) {
     session->timeout = 0;
     UA_DateTime_init(&session->validTill);
     session->channel = UA_NULL;
+    session->continuationPoints = (struct ContinuationPointList){UA_NULL};
 }
 
 void UA_Session_deleteMembers(UA_Session *session) {
@@ -71,6 +64,13 @@ void UA_Session_deleteMembers(UA_Session *session) {
     UA_NodeId_deleteMembers(&session->sessionId);
     UA_String_deleteMembers(&session->sessionName);
     session->channel = UA_NULL;
+    struct ContinuationPointEntry *cp;
+    while((cp = LIST_FIRST(&session->continuationPoints))) {
+        UA_ByteString_deleteMembers(&cp->identifier);
+        UA_BrowseDescription_deleteMembers(&cp->browseDescription);
+        LIST_REMOVE(cp, pointers);
+        UA_free(cp);
+    }
 }
 
 void UA_Session_delete(UA_Session *session) {

+ 10 - 0
src/ua_session.h

@@ -3,6 +3,7 @@
 
 #include "ua_types.h"
 #include "ua_securechannel.h"
+#include "queue.h"
 
 /**
  *  @ingroup communication
@@ -10,6 +11,14 @@
  * @{
  */
 
+struct ContinuationPointEntry {
+    LIST_ENTRY(ContinuationPointEntry) pointers;
+    UA_ByteString        identifier;
+    UA_BrowseDescription browseDescription;
+    UA_Int32            continuationIndex;
+    UA_UInt32            maxReferences;
+};
+
 struct UA_Session {
     UA_ApplicationDescription clientDescription;
     UA_String         sessionName;
@@ -20,6 +29,7 @@ struct UA_Session {
     UA_Int64          timeout;
     UA_DateTime       validTill;
     UA_SecureChannel *channel;
+    LIST_HEAD(ContinuationPointList, ContinuationPointEntry) continuationPoints;
 };
 
 extern UA_Session anonymousSession; ///< If anonymous access is allowed, this session is used internally (Session ID: 0)

+ 1 - 0
tools/generate_datatypes.py

@@ -48,6 +48,7 @@ minimal_types = ["InvalidType", "Node", "NodeClass", "ReferenceNode", "Applicati
                  "TranslateBrowsePathsToNodeIdsResponse", "BrowsePath", "BrowsePathResult", "RelativePath",
                  "BrowsePathTarget", "RelativePathElement", "CreateSubscriptionRequest", "CreateSubscriptionResponse",
                  "BrowseResponse", "BrowseResult", "ReferenceDescription", "BrowseRequest", "ViewDescription",
+                 "BrowseNextRequest", "BrowseNextResponse",
                  "BrowseDescription", "BrowseDirection", "CloseSessionRequest", "AddNodesRequest", "AddNodesResponse",
                  "AddNodesItem", "AddNodesResult", "DeleteNodesItem","AddReferencesRequest", "AddReferencesResponse",
                  "AddReferencesItem","DeleteReferencesItem", "VariableNode", "MethodNode", "VariableTypeNode",