Browse Source

Merge branch '1.0' into master

Stefan Profanter 5 years ago
parent
commit
6752ff9518

+ 84 - 9
examples/server_ctt.c

@@ -9,9 +9,10 @@
 #define _CRT_SECURE_NO_WARNINGS /* disable fopen deprication warning in msvs */
 #endif
 
-#include <open62541/plugin/log_stdout.h>
 #include <open62541/server.h>
+#include <open62541/plugin/log_stdout.h>
 #include <open62541/server_config_default.h>
+#include <open62541/plugin/pki_default.h>
 
 #include <signal.h>
 #include <stdlib.h>
@@ -828,7 +829,9 @@ disableOutdatedSecurityPolicy(UA_ServerConfig *config) {
     for(size_t i = 0; i < config->endpointsSize; i++) {
         UA_EndpointDescription *ep = &config->endpoints[i];
         UA_ByteString basic128uri = UA_BYTESTRING("http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15");
-        if(!UA_String_equal(&ep->securityPolicyUri, &basic128uri))
+        UA_ByteString basic256uri = UA_BYTESTRING("http://opcfoundation.org/UA/SecurityPolicy#Basic256");
+        if(!UA_String_equal(&ep->securityPolicyUri, &basic128uri) &&
+           !UA_String_equal(&ep->securityPolicyUri, &basic256uri))
             continue;
 
         UA_EndpointDescription_clear(ep);
@@ -934,9 +937,15 @@ usage(void) {
                    "server_ctt [<server-certificate.der>]\n"
 #else
                    "server_ctt <server-certificate.der> <private-key.der>\n"
+#ifndef __linux__
                    "\t[--trustlist <tl1.ctl> <tl2.ctl> ... ]\n"
                    "\t[--issuerlist <il1.der> <il2.der> ... ]\n"
                    "\t[--revocationlist <rv1.crl> <rv2.crl> ...]\n"
+#else
+                   "\t[--trustlistFolder <folder>]\n"
+                   "\t[--issuerlistFolder <folder>]\n"
+                   "\t[--revocationlistFolder <folder>]\n"
+#endif
                    "\t[--enableUnencrypted]\n"
                    "\t[--enableOutdatedSecurityPolicy]\n"
                    "\t[--enableTimestampCheck]\n"
@@ -988,12 +997,6 @@ int main(int argc, char **argv) {
         pos++;
     }
 
-    UA_ByteString trustList[100];
-    size_t trustListSize = 0;
-    UA_ByteString issuerList[100];
-    size_t issuerListSize = 0;
-    UA_ByteString revocationList[100];
-    size_t revocationListSize = 0;
     char filetype = ' '; /* t==trustlist, l == issuerList, r==revocationlist */
     UA_Boolean enableUnencr = false;
     UA_Boolean enableSec = false;
@@ -1002,6 +1005,19 @@ int main(int argc, char **argv) {
     UA_Boolean disableBasic256 = false;
     UA_Boolean disableBasic256Sha256 = false;
 
+#ifndef __linux__
+    UA_ByteString trustList[100];
+    size_t trustListSize = 0;
+    UA_ByteString issuerList[100];
+    size_t issuerListSize = 0;
+    UA_ByteString revocationList[100];
+    size_t revocationListSize = 0;
+#else
+    const char *trustlistFolder = NULL;
+    const char *issuerlistFolder = NULL;
+    const char *revocationlistFolder = NULL;
+#endif
+
 #endif
 
     UA_Boolean enableAnon = false;
@@ -1045,6 +1061,22 @@ int main(int argc, char **argv) {
             continue;
         }
 
+        if(strcmp(argv[pos], "--disableBasic128") == 0) {
+            disableBasic128 = true;
+            continue;
+        }
+
+        if(strcmp(argv[pos], "--disableBasic256") == 0) {
+            disableBasic256 = true;
+            continue;
+        }
+
+        if(strcmp(argv[pos], "--disableBasic256Sha256") == 0) {
+            disableBasic256Sha256 = true;
+            continue;
+        }
+
+#ifndef __linux__
         if(strcmp(argv[pos], "--trustlist") == 0) {
             filetype = 't';
             continue;
@@ -1107,6 +1139,38 @@ int main(int argc, char **argv) {
             revocationListSize++;
             continue;
         }
+#else
+        if(strcmp(argv[pos], "--trustlistFolder") == 0) {
+            filetype = 't';
+            continue;
+        }
+
+        if(strcmp(argv[pos], "--issuerlistFolder") == 0) {
+            filetype = 'l';
+            continue;
+        }
+
+        if(strcmp(argv[pos], "--revocationlistFolder") == 0) {
+            filetype = 'r';
+            continue;
+        }
+
+        if(filetype == 't') {
+            trustlistFolder = argv[pos];
+            continue;
+        }
+
+        if(filetype == 'l') {
+            issuerlistFolder = argv[pos];
+            continue;
+        }
+
+        if(filetype == 'r') {
+            revocationlistFolder = argv[pos];
+            continue;
+        }
+#endif
+
 #endif
 
         usage();
@@ -1114,11 +1178,22 @@ int main(int argc, char **argv) {
     }
 
 #ifdef UA_ENABLE_ENCRYPTION
+#ifndef __linux__
     UA_ServerConfig_setDefaultWithSecurityPolicies(&config, 4840,
                                                    &certificate, &privateKey,
                                                    trustList, trustListSize,
                                                    issuerList, issuerListSize,
                                                    revocationList, revocationListSize);
+#else
+    UA_ServerConfig_setDefaultWithSecurityPolicies(&config, 4840,
+                                                   &certificate, &privateKey,
+                                                   NULL, 0, NULL, 0, NULL, 0);
+    config.certificateVerification.deleteMembers(&config.certificateVerification);
+    UA_CertificateVerification_CertFolders(&config.certificateVerification,
+                                           trustlistFolder, issuerlistFolder,
+                                           revocationlistFolder);
+#endif
+
     if(!enableUnencr)
         disableUnencrypted(&config);
     if(!enableSec)
@@ -1156,7 +1231,7 @@ int main(int argc, char **argv) {
 
     /* Clean up temp values */
     UA_ByteString_clear(&certificate);
-#ifdef UA_ENABLE_ENCRYPTION
+#if defined(UA_ENABLE_ENCRYPTION) && !defined(__linux__)
     UA_ByteString_clear(&privateKey);
     for(size_t i = 0; i < trustListSize; i++)
         UA_ByteString_clear(&trustList[i]);

+ 1 - 1
include/open62541/server.h

@@ -467,7 +467,7 @@ UA_Server_browseNext(UA_Server *server, UA_Boolean releaseContinuationPoint,
  * by adding every target node at most once to the results array. */
 UA_StatusCode UA_EXPORT
 UA_Server_browseRecursive(UA_Server *server, const UA_BrowseDescription *bd,
-                          size_t *resultsSize, UA_ExpandedNodeId *results);
+                          size_t *resultsSize, UA_ExpandedNodeId **results);
 
 UA_BrowsePathResult UA_EXPORT
 UA_Server_translateBrowsePathToNodeIds(UA_Server *server,

+ 8 - 0
plugins/include/open62541/plugin/pki_default.h

@@ -29,6 +29,14 @@ UA_CertificateVerification_Trustlist(UA_CertificateVerification *cv,
                                      const UA_ByteString *certificateRevocationList,
                                      size_t certificateRevocationListSize);
 
+#if __linux__ /* Linux only so far */
+UA_EXPORT UA_StatusCode
+UA_CertificateVerification_CertFolders(UA_CertificateVerification *cv,
+                                       const char *trustListFolder,
+                                       const char *issuerListFolder,
+                                       const char *revocationListFolder);
+#endif
+
 #endif
 
 _UA_END_DECLS

+ 9 - 9
plugins/ua_config_default.c

@@ -232,19 +232,19 @@ addDefaultNetworkLayers(UA_ServerConfig *conf, UA_UInt16 portNumber,
 
 static UA_StatusCode
 addDiscoveryUrl(UA_ServerConfig *config, UA_UInt16 portNumber) {
-       config->applicationDescription.discoveryUrlsSize = 1;
+    config->applicationDescription.discoveryUrlsSize = 1;
     UA_String *discurl = (UA_String *) UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
     char discoveryUrlBuffer[220];
     if (config->customHostname.length) {
         UA_snprintf(discoveryUrlBuffer, 220, "opc.tcp://%.*s:%d/",
-                                                 (int)config->customHostname.length,
-                                                 config->customHostname.data,
-                                                 portNumber);
+                    (int)config->customHostname.length,
+                    config->customHostname.data,
+                    portNumber);
     } else {
-    char hostnameBuffer[200];
-       if(UA_gethostname(hostnameBuffer, 200) == 0) {
-               UA_snprintf(discoveryUrlBuffer, 220, "opc.tcp://%s:%d/", hostnameBuffer, portNumber);
-       } else {
+        char hostnameBuffer[200];
+        if(UA_gethostname(hostnameBuffer, 200) == 0) {
+            UA_snprintf(discoveryUrlBuffer, 220, "opc.tcp://%s:%d/", hostnameBuffer, portNumber);
+        } else {
             UA_LOG_ERROR(&config->logger, UA_LOGCATEGORY_NETWORK, "Could not get the hostname");
         }
     }
@@ -423,7 +423,7 @@ UA_ServerConfig_setMinimalCustomBuffer(UA_ServerConfig *config, UA_UInt16 portNu
         UA_ServerConfig_clean(config);
         return retval;
     }
-    
+
     retval = addDiscoveryUrl(config, portNumber);
     if (retval != UA_STATUSCODE_GOOD) {
         UA_ServerConfig_clean(config);

+ 184 - 0
plugins/ua_pki_default.c

@@ -3,13 +3,17 @@
  *
  *    Copyright 2018 (c) Mark Giraud, Fraunhofer IOSB
  *    Copyright 2019 (c) Kalycito Infotech Private Limited
+ *    Copyright 2019 (c) Julius Pfrommer, Fraunhofer IOSB
  */
 
+#include <open62541/server_config.h>
 #include <open62541/plugin/pki_default.h>
+#include <open62541/plugin/log_stdout.h>
 
 #ifdef UA_ENABLE_ENCRYPTION
 #include <mbedtls/x509.h>
 #include <mbedtls/x509_crt.h>
+#include <mbedtls/error.h>
 #endif
 
 #define REMOTECERTIFICATETRUSTED 1
@@ -48,11 +52,143 @@ void UA_CertificateVerification_AcceptAll(UA_CertificateVerification *cv) {
 #ifdef UA_ENABLE_ENCRYPTION
 
 typedef struct {
+    /* If the folders are defined, we use them to reload the certificates during
+     * runtime */
+    UA_String trustListFolder;
+    UA_String issuerListFolder;
+    UA_String revocationListFolder;
+
     mbedtls_x509_crt certificateTrustList;
     mbedtls_x509_crt certificateIssuerList;
     mbedtls_x509_crl certificateRevocationList;
 } CertInfo;
 
+#if __linux__ /* Linux only so far */
+
+#include <dirent.h>
+#include <limits.h>
+
+static UA_StatusCode
+fileNamesFromFolder(const UA_String *folder, size_t *pathsSize, UA_String **paths) {
+    char buf[PATH_MAX + 1];
+    if(folder->length > PATH_MAX)
+        return UA_STATUSCODE_BADINTERNALERROR;
+
+    memcpy(buf, folder->data, folder->length);
+    buf[folder->length] = 0;
+    
+    DIR *dir = opendir(buf);
+    if(!dir)
+        return UA_STATUSCODE_BADINTERNALERROR;
+
+    *paths = (UA_String*)UA_Array_new(256, &UA_TYPES[UA_TYPES_STRING]);
+    if(*paths == NULL) {
+        closedir(dir);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+
+    struct dirent *ent;
+    char buf2[PATH_MAX + 1];
+    realpath(buf, buf2);
+    size_t pathlen = strlen(buf2);
+    *pathsSize = 0;
+    while((ent = readdir (dir)) != NULL && *pathsSize < 256) {
+        if(ent->d_type != DT_REG)
+            continue;
+        buf2[pathlen] = '/';
+        buf2[pathlen+1] = 0;
+        strcat(buf2, ent->d_name);
+        (*paths)[*pathsSize] = UA_STRING_ALLOC(buf2);
+        *pathsSize += 1;
+    }
+    closedir(dir);
+
+    if(*pathsSize == 0) {
+        UA_free(*paths);
+        *paths = NULL;
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+reloadCertificates(CertInfo *ci) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    int err = 0;
+
+    /* Load the trustlists */
+    if(ci->trustListFolder.length > 0) {
+        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Reloading the trust-list");
+        mbedtls_x509_crt_free(&ci->certificateTrustList);
+        mbedtls_x509_crt_init(&ci->certificateTrustList);
+
+        char f[PATH_MAX];
+        memcpy(f, ci->trustListFolder.data, ci->trustListFolder.length);
+        f[ci->trustListFolder.length] = 0;
+        err = mbedtls_x509_crt_parse_path(&ci->certificateTrustList, f);
+        if(err == 0) {
+            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                        "Loaded certificate from %s", f);
+        } else {
+            char errBuff[300];
+            mbedtls_strerror(err, errBuff, 300);
+            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                        "Failed to load certificate from %s", f);
+        }
+    }
+
+    /* Load the revocationlists */
+    if(ci->revocationListFolder.length > 0) {
+        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Reloading the revocation-list");
+        size_t pathsSize = 0;
+        UA_String *paths = NULL;
+        retval = fileNamesFromFolder(&ci->revocationListFolder, &pathsSize, &paths);
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+        mbedtls_x509_crl_free(&ci->certificateRevocationList);
+        mbedtls_x509_crl_init(&ci->certificateRevocationList);
+        for(size_t i = 0; i < pathsSize; i++) {
+            char f[PATH_MAX];
+            memcpy(f, paths[i].data, paths[i].length);
+            f[paths[i].length] = 0;
+            err = mbedtls_x509_crl_parse_file(&ci->certificateRevocationList, f);
+            if(err == 0) {
+                UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                            "Loaded certificate from %.*s",
+                            (int)paths[i].length, paths[i].data);
+            } else {
+                UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                            "Failed to load certificate from %.*s",
+                            (int)paths[i].length, paths[i].data);
+            }
+        }
+        UA_Array_delete(paths, pathsSize, &UA_TYPES[UA_TYPES_STRING]);
+        paths = NULL;
+        pathsSize = 0;
+    }
+
+    /* Load the issuerlists */
+    if(ci->issuerListFolder.length > 0) {
+        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Reloading the issuer-list");
+        mbedtls_x509_crt_free(&ci->certificateIssuerList);
+        mbedtls_x509_crt_init(&ci->certificateIssuerList);
+        char f[PATH_MAX];
+        memcpy(f, ci->issuerListFolder.data, ci->issuerListFolder.length);
+        f[ci->issuerListFolder.length] = 0;
+        err = mbedtls_x509_crt_parse_path(&ci->certificateIssuerList, f);
+        if(err == 0) {
+            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                        "Loaded certificate from %s", f);
+        } else {
+            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                        "Failed to load certificate from %s", f);
+        }
+    }
+
+    return retval;
+}
+
+#endif
+
 static UA_StatusCode
 certificateVerification_verify(void *verificationContext,
                                const UA_ByteString *certificate) {
@@ -60,6 +196,16 @@ certificateVerification_verify(void *verificationContext,
     if(!ci)
         return UA_STATUSCODE_BADINTERNALERROR;
 
+#if __linux__ /* Reload certificates if folder paths are specified */
+    reloadCertificates(ci);
+#endif
+
+    /* if(ci->certificateTrustList.raw.len == 0) { */
+    /*     UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, */
+    /*                    "No Trustlist loaded. Accepting the certificate."); */
+    /*     return UA_STATUSCODE_GOOD; */
+    /* } */
+
     /* Parse the certificate */
     mbedtls_x509_crt remoteCertificate;
 
@@ -346,6 +492,10 @@ certificateVerification_deleteMembers(UA_CertificateVerification *cv) {
         return;
     mbedtls_x509_crt_free(&ci->certificateTrustList);
     mbedtls_x509_crl_free(&ci->certificateRevocationList);
+    mbedtls_x509_crt_free(&ci->certificateIssuerList);
+    UA_String_clear(&ci->trustListFolder);
+    UA_String_clear(&ci->issuerListFolder);
+    UA_String_clear(&ci->revocationListFolder);
     UA_free(ci);
     cv->context = NULL;
 }
@@ -361,6 +511,7 @@ UA_CertificateVerification_Trustlist(UA_CertificateVerification *cv,
     CertInfo *ci = (CertInfo*)UA_malloc(sizeof(CertInfo));
     if(!ci)
         return UA_STATUSCODE_BADOUTOFMEMORY;
+    memset(ci, 0, sizeof(CertInfo));
     mbedtls_x509_crt_init(&ci->certificateTrustList);
     mbedtls_x509_crl_init(&ci->certificateRevocationList);
     mbedtls_x509_crt_init(&ci->certificateIssuerList);
@@ -402,4 +553,37 @@ error:
     return UA_STATUSCODE_BADINTERNALERROR;
 }
 
+#if __linux__ /* Linux only so far */
+
+UA_StatusCode
+UA_CertificateVerification_CertFolders(UA_CertificateVerification *cv,
+                                       const char *trustListFolder,
+                                       const char *issuerListFolder,
+                                       const char *revocationListFolder) {
+    CertInfo *ci = (CertInfo*)UA_malloc(sizeof(CertInfo));
+    if(!ci)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    memset(ci, 0, sizeof(CertInfo));
+    mbedtls_x509_crt_init(&ci->certificateTrustList);
+    mbedtls_x509_crl_init(&ci->certificateRevocationList);
+    mbedtls_x509_crt_init(&ci->certificateIssuerList);
+
+    /* Only set the folder paths. They will be reloaded during runtime.
+     * TODO: Add a more efficient reloading of only the changes */
+    ci->trustListFolder = UA_STRING_ALLOC(trustListFolder);
+    ci->issuerListFolder = UA_STRING_ALLOC(issuerListFolder);
+    ci->revocationListFolder = UA_STRING_ALLOC(revocationListFolder);
+
+    reloadCertificates(ci);
+
+    cv->context = (void*)ci;
+    cv->verifyCertificate = certificateVerification_verify;
+    cv->deleteMembers = certificateVerification_deleteMembers;
+    cv->verifyApplicationURI = certificateVerification_verifyApplicationURI;
+
+    return UA_STATUSCODE_GOOD;
+}
+
+#endif
+
 #endif

+ 2 - 19
src/server/ua_nodes.c

@@ -318,26 +318,9 @@ copyCommonVariableAttributes(UA_VariableNode *node,
     node->valueRank = attr->valueRank;
 
     /* Copy the value */
-    node->valueSource = UA_VALUESOURCE_DATA;
-    UA_NodeId extensionObject = UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE);
-    /* If we have an extension object which is still encoded (e.g. from the
-     * nodeset compiler) return an error.
-     * This was used in the old version of the nodeset compiler and is not
-     * needed anymore. */
-    if(attr->value.type != NULL && UA_NodeId_equal(&attr->value.type->typeId, &extensionObject)) {
-        /* Do nothing since we got an empty array of extension objects */
-        if(attr->value.data == UA_EMPTY_ARRAY_SENTINEL)
-            return UA_STATUSCODE_GOOD;
-
-        const UA_ExtensionObject *obj = (const UA_ExtensionObject *)attr->value.data;
-        if(obj && obj->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING) {
-            return UA_STATUSCODE_BADNOTSUPPORTED;
-        }
-    }
-
     retval = UA_Variant_copy(&attr->value, &node->value.data.value.value);
-
-    node->value.data.value.hasValue = node->value.data.value.value.type != NULL;
+    node->valueSource = UA_VALUESOURCE_DATA;
+    node->value.data.value.hasValue = (node->value.data.value.value.type != NULL);
 
     return retval;
 }

+ 4 - 2
src/server/ua_server_ns0.c

@@ -899,11 +899,13 @@ UA_Server_initNS0(UA_Server *server) {
                                &maxBrowseContinuationPoints, &UA_TYPES[UA_TYPES_UINT16]);
 
     /* ServerProfileArray */
-    UA_String profileArray[2];
+    UA_String profileArray[3];
     UA_UInt16 profileArraySize = 0;
 #define ADDPROFILEARRAY(x) profileArray[profileArraySize++] = UA_STRING(x)
     ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/MicroEmbeddedDevice");
-
+#ifdef UA_ENABLE_NODEMANAGEMENT
+    ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/NodeManagement");
+#endif
 #ifdef UA_ENABLE_METHODCALLS
     ADDPROFILEARRAY("http://opcfoundation.org/UA-Profile/Server/Methods");
 #endif

+ 8 - 0
src/server/ua_services_nodemanagement.c

@@ -923,6 +923,14 @@ recursiveTypeCheckAddChildren(UA_Server *server, UA_Session *session,
             return retval;
         }
 
+        /* Check NodeClass for 'hasSubtype'. UA_NODECLASS_VARIABLE not allowed to have subtype */
+        if((node->nodeClass == UA_NODECLASS_VARIABLE) && (UA_NodeId_equal(
+                &node->references->referenceTypeId, &hasSubtype))) {
+            UA_LOG_INFO_SESSION(&server->config.logger, session,
+                                            "AddNodes: VariableType not allowed to have HasSubType");
+            return UA_STATUSCODE_BADREFERENCENOTALLOWED;
+        }
+
         /* Check if all attributes hold the constraints of the type now. The initial
          * attributes must type-check. The constructor might change the attributes
          * again. Then, the changes are type-checked by the normal write service. */

+ 2 - 2
src/server/ua_services_view.c

@@ -287,7 +287,7 @@ referenceSubtypes(UA_Server *server, const UA_NodeId *refType,
 
 UA_StatusCode
 UA_Server_browseRecursive(UA_Server *server, const UA_BrowseDescription *bd,
-                          size_t *resultsSize, UA_ExpandedNodeId *results) {
+                          size_t *resultsSize, UA_ExpandedNodeId **results) {
     /* Set the list of relevant reference types */
     UA_NodeId *refTypes = NULL;
     size_t refTypesSize = 0;
@@ -306,7 +306,7 @@ UA_Server_browseRecursive(UA_Server *server, const UA_BrowseDescription *bd,
 
     /* Browse */
     retval = browseRecursive(server, 1, &bd->nodeId, refTypesSize, refTypes,
-                             bd->browseDirection, false, resultsSize, &results);
+                             bd->browseDirection, false, resultsSize, results);
 
     /* Clean up */
     if(refTypes && bd->includeSubtypes)

+ 28 - 0
tests/server/check_services_nodemanagement.c

@@ -85,6 +85,33 @@ START_TEST(AddVariableNode_Matrix) {
     ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
 } END_TEST
 
+START_TEST(AddVariableNode_ExtensionObject) {
+        /* Add a variable node to the address space */
+        UA_VariableAttributes attr = UA_VariableAttributes_default;
+        attr.displayName = UA_LOCALIZEDTEXT("en-US","the extensionobject");
+
+        /* Set an ExtensionObject with an unknown binary encoding */
+        UA_ExtensionObject myExtensionObject;
+        UA_ExtensionObject_init(&myExtensionObject);
+        myExtensionObject.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
+        myExtensionObject.content.encoded.typeId = UA_NODEID_NUMERIC(5, 1234);
+        UA_ByteString byteString = UA_BYTESTRING("String Payload as a ByteString extension");
+        myExtensionObject.content.encoded.body = byteString;
+        UA_Variant_setScalar(&attr.value, &myExtensionObject, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
+
+        UA_NodeId myEONodeId = UA_NODEID_STRING(1, "the.extensionobject");
+        UA_QualifiedName myEOName = UA_QUALIFIEDNAME(1, "the extensionobject");
+        UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+        UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+        UA_StatusCode res =
+            UA_Server_addVariableNode(server, myEONodeId, parentNodeId,
+                                      parentReferenceNodeId, myEOName,
+                                      UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                      attr, NULL, NULL);
+        ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
+    } END_TEST
+
+
 static UA_NodeId pointTypeId;
 
 static void
@@ -594,6 +621,7 @@ int main(void) {
     tcase_add_checked_fixture(tc_addnodes, setup, teardown);
     tcase_add_test(tc_addnodes, AddVariableNode);
     tcase_add_test(tc_addnodes, AddVariableNode_Matrix);
+    tcase_add_test(tc_addnodes, AddVariableNode_ExtensionObject);
     tcase_add_test(tc_addnodes, InstantiateVariableTypeNode);
     tcase_add_test(tc_addnodes, InstantiateVariableTypeNodeWrongDims);
     tcase_add_test(tc_addnodes, InstantiateVariableTypeNodeLessDims);

+ 33 - 0
tests/server/check_services_view.c

@@ -129,6 +129,38 @@ START_TEST(Service_Browse_WithBrowseName) {
 }
 END_TEST
 
+START_TEST(Service_Browse_Recursive) {
+    UA_Server *server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
+
+    size_t resultSize = 0;
+    UA_ExpandedNodeId *result = NULL;
+
+    UA_BrowseDescription bd;
+    UA_BrowseDescription_init(&bd);
+    bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_NAMESPACEARRAY);
+    bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
+    bd.includeSubtypes = true;
+    bd.browseDirection = UA_BROWSEDIRECTION_INVERSE;
+    UA_StatusCode retval = UA_Server_browseRecursive(server, &bd, &resultSize, &result);
+
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(resultSize, 3);
+
+    UA_NodeId expected[3];
+    expected[0] = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
+    expected[1] = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    expected[2] = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
+
+    for(size_t i = 0; i < resultSize; i++) {
+        ck_assert(UA_NodeId_equal(&expected[i], &result[i].nodeId));
+    }
+
+    UA_Array_delete(result, resultSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
+    UA_Server_delete(server);
+}
+END_TEST
+
 START_TEST(Service_TranslateBrowsePathsToNodeIds) {
     UA_Client *client = UA_Client_new();
     UA_ClientConfig_setDefault(UA_Client_getConfig(client));
@@ -196,6 +228,7 @@ static Suite *testSuite_Service_TranslateBrowsePathsToNodeIds(void) {
     TCase *tc_browse = tcase_create("Browse Service");
     tcase_add_test(tc_browse, Service_Browse_WithBrowseName);
     tcase_add_test(tc_browse, Service_Browse_WithMaxResults);
+    tcase_add_test(tc_browse, Service_Browse_Recursive);
     suite_add_tcase(s, tc_browse);
 
     TCase *tc_translate = tcase_create("TranslateBrowsePathsToNodeIds");