/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ua_types.h" #include "ua_server_internal.h" #include "ua_securechannel_manager.h" #include "ua_session_manager.h" #include "ua_util.h" #include "ua_services.h" #include "ua_nodeids.h" #ifdef UA_ENABLE_GENERATE_NAMESPACE0 #include "ua_namespaceinit_generated.h" #endif #if defined(UA_ENABLE_MULTITHREADING) && !defined(NDEBUG) UA_THREAD_LOCAL bool rcu_locked = false; #endif /**********************/ /* Namespace Handling */ /**********************/ UA_UInt16 addNamespace(UA_Server *server, const UA_String name) { /* Check if the namespace already exists in the server's namespace array */ for(UA_UInt16 i = 0; i < server->namespacesSize; ++i) { if(UA_String_equal(&name, &server->namespaces[i])) return i; } /* Make the array bigger */ UA_String *newNS = (UA_String*)UA_realloc(server->namespaces, sizeof(UA_String) * (server->namespacesSize + 1)); if(!newNS) return 0; server->namespaces = newNS; /* Copy the namespace string */ UA_StatusCode retval = UA_String_copy(&name, &server->namespaces[server->namespacesSize]); if(retval != UA_STATUSCODE_GOOD) return 0; /* Announce the change (otherwise, the array appears unchanged) */ ++server->namespacesSize; return (UA_UInt16)(server->namespacesSize - 1); } UA_UInt16 UA_Server_addNamespace(UA_Server *server, const char* name) { /* Override const attribute to get string (dirty hack) */ UA_String nameString; nameString.length = strlen(name); nameString.data = (UA_Byte*)(uintptr_t)name; return addNamespace(server, nameString); } #ifdef UA_ENABLE_EXTERNAL_NAMESPACES static void UA_ExternalNamespace_init(UA_ExternalNamespace *ens) { ens->index = 0; UA_String_init(&ens->url); } static void UA_ExternalNamespace_deleteMembers(UA_ExternalNamespace *ens) { UA_String_deleteMembers(&ens->url); ens->externalNodeStore.destroy(ens->externalNodeStore.ensHandle); } static void UA_Server_deleteExternalNamespaces(UA_Server *server) { for(UA_UInt32 i = 0; i < server->externalNamespacesSize; ++i) UA_ExternalNamespace_deleteMembers(&server->externalNamespaces[i]); if(server->externalNamespacesSize > 0) { UA_free(server->externalNamespaces); server->externalNamespaces = NULL; server->externalNamespacesSize = 0; } } UA_StatusCode UA_EXPORT UA_Server_addExternalNamespace(UA_Server *server, const UA_String *url, UA_ExternalNodeStore *nodeStore, UA_UInt16 *assignedNamespaceIndex) { if(!nodeStore) return UA_STATUSCODE_BADARGUMENTSMISSING; size_t size = server->externalNamespacesSize; server->externalNamespaces = UA_realloc(server->externalNamespaces, sizeof(UA_ExternalNamespace) * (size + 1)); server->externalNamespaces[size].externalNodeStore = *nodeStore; server->externalNamespaces[size].index = (UA_UInt16)server->namespacesSize; *assignedNamespaceIndex = (UA_UInt16)server->namespacesSize; UA_String_copy(url, &server->externalNamespaces[size].url); ++server->externalNamespacesSize; UA_Server_addNamespace(server, urlString); return UA_STATUSCODE_GOOD; } #endif /* UA_ENABLE_EXTERNAL_NAMESPACES*/ UA_StatusCode UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId, UA_NodeIteratorCallback callback, void *handle) { UA_RCU_LOCK(); const UA_Node *parent = UA_NodeStore_get(server->nodestore, &parentNodeId); if(!parent) { UA_RCU_UNLOCK(); return UA_STATUSCODE_BADNODEIDINVALID; } /* TODO: We need to do an ugly copy of the references array since users may * delete references from within the callback. In single-threaded mode this * changes the same node we point at here. In multi-threaded mode, this * creates a new copy as nodes are truly immutable. */ UA_ReferenceNode *refs = NULL; size_t refssize = parent->referencesSize; UA_StatusCode retval = UA_Array_copy(parent->references, parent->referencesSize, (void**)&refs, &UA_TYPES[UA_TYPES_REFERENCENODE]); if(retval != UA_STATUSCODE_GOOD) { UA_RCU_UNLOCK(); return retval; } for(size_t i = parent->referencesSize; i > 0; --i) { UA_ReferenceNode *ref = &refs[i-1]; retval |= callback(ref->targetId.nodeId, ref->isInverse, ref->referenceTypeId, handle); } UA_RCU_UNLOCK(); UA_Array_delete(refs, refssize, &UA_TYPES[UA_TYPES_REFERENCENODE]); return retval; } /********************/ /* Server Lifecycle */ /********************/ /* The server needs to be stopped before it can be deleted */ void UA_Server_delete(UA_Server *server) { // Delete the timed work UA_RepeatedJobsList_deleteMembers(&server->repeatedJobs); // Delete all internal data UA_SecureChannelManager_deleteMembers(&server->secureChannelManager); UA_SessionManager_deleteMembers(&server->sessionManager); UA_RCU_LOCK(); UA_NodeStore_delete(server->nodestore); UA_RCU_UNLOCK(); #ifdef UA_ENABLE_EXTERNAL_NAMESPACES UA_Server_deleteExternalNamespaces(server); #endif UA_Array_delete(server->namespaces, server->namespacesSize, &UA_TYPES[UA_TYPES_STRING]); UA_Array_delete(server->endpointDescriptions, server->endpointDescriptionsSize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); #ifdef UA_ENABLE_DISCOVERY registeredServer_list_entry *rs, *rs_tmp; LIST_FOREACH_SAFE(rs, &server->registeredServers, pointers, rs_tmp) { LIST_REMOVE(rs, pointers); UA_RegisteredServer_deleteMembers(&rs->registeredServer); UA_free(rs); } if(server->periodicServerRegisterJob) UA_free(server->periodicServerRegisterJob); # ifdef UA_ENABLE_DISCOVERY_MULTICAST if(server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) destroyMulticastDiscoveryServer(server); serverOnNetwork_list_entry *son, *son_tmp; LIST_FOREACH_SAFE(son, &server->serverOnNetwork, pointers, son_tmp) { LIST_REMOVE(son, pointers); UA_ServerOnNetwork_deleteMembers(&son->serverOnNetwork); if(son->pathTmp) UA_free(son->pathTmp); UA_free(son); } for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_PRIME; i++) { serverOnNetwork_hash_entry* currHash = server->serverOnNetworkHash[i]; while(currHash) { serverOnNetwork_hash_entry* nextHash = currHash->next; UA_free(currHash); currHash = nextHash; } } # endif #endif #ifdef UA_ENABLE_MULTITHREADING pthread_cond_destroy(&server->dispatchQueue_condition); pthread_mutex_destroy(&server->dispatchQueue_mutex); #endif UA_free(server); } /* Recurring cleanup. Removing unused and timed-out channels and sessions */ static void UA_Server_cleanup(UA_Server *server, void *_) { 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, nowMonotonic); #endif } /* Create endpoints w/o endpointurl. It is added from the networklayers at startup */ static void addEndpointDefinitions(UA_Server *server) { server->endpointDescriptions = (UA_EndpointDescription*)UA_Array_new(server->config.networkLayersSize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); server->endpointDescriptionsSize = server->config.networkLayersSize; for(size_t i = 0; i < server->config.networkLayersSize; ++i) { UA_EndpointDescription *endpoint = &server->endpointDescriptions[i]; endpoint->securityMode = UA_MESSAGESECURITYMODE_NONE; endpoint->securityPolicyUri = UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#None"); endpoint->transportProfileUri = UA_STRING_ALLOC("http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary"); size_t policies = 0; if(server->config.accessControl.enableAnonymousLogin) ++policies; if(server->config.accessControl.enableUsernamePasswordLogin) ++policies; endpoint->userIdentityTokensSize = policies; endpoint->userIdentityTokens = (UA_UserTokenPolicy*)UA_Array_new(policies, &UA_TYPES[UA_TYPES_USERTOKENPOLICY]); size_t currentIndex = 0; if(server->config.accessControl.enableAnonymousLogin) { UA_UserTokenPolicy_init(&endpoint->userIdentityTokens[currentIndex]); endpoint->userIdentityTokens[currentIndex].tokenType = UA_USERTOKENTYPE_ANONYMOUS; endpoint->userIdentityTokens[currentIndex].policyId = UA_STRING_ALLOC(ANONYMOUS_POLICY); ++currentIndex; } if(server->config.accessControl.enableUsernamePasswordLogin) { UA_UserTokenPolicy_init(&endpoint->userIdentityTokens[currentIndex]); endpoint->userIdentityTokens[currentIndex].tokenType = UA_USERTOKENTYPE_USERNAME; endpoint->userIdentityTokens[currentIndex].policyId = UA_STRING_ALLOC(USERNAME_POLICY); } /* The standard says "the HostName specified in the Server Certificate is the same as the HostName contained in the endpointUrl provided in the EndpointDescription */ UA_String_copy(&server->config.serverCertificate, &endpoint->serverCertificate); UA_ApplicationDescription_copy(&server->config.applicationDescription, &endpoint->server); /* copy the discovery url only once the networlayer has been started */ // UA_String_copy(&server->config.networkLayers[i].discoveryUrl, &endpoint->endpointUrl); } } UA_Server * UA_Server_new(const UA_ServerConfig config) { UA_Server *server = (UA_Server *)UA_calloc(1, sizeof(UA_Server)); if(!server) return NULL; server->config = config; server->startTime = UA_DateTime_now(); server->nodestore = UA_NodeStore_new(); /* Set a seed for non-cyptographic randomness */ #ifndef UA_ENABLE_DETERMINISTIC_RNG UA_random_seed((UA_UInt64)UA_DateTime_now()); #endif /* Initialize the handling of repeated jobs */ #ifdef UA_ENABLE_MULTITHREADING UA_RepeatedJobsList_init(&server->repeatedJobs, (UA_RepeatedJobsListProcessCallback)UA_Server_dispatchJob, server); #else UA_RepeatedJobsList_init(&server->repeatedJobs, (UA_RepeatedJobsListProcessCallback)UA_Server_processJob, server); #endif /* Initialized the linked list for delayed callbacks */ #ifndef UA_ENABLE_MULTITHREADING SLIST_INIT(&server->delayedCallbacks); #endif /* Initialized the dispatch queue for worker threads */ #ifdef UA_ENABLE_MULTITHREADING rcu_init(); cds_wfcq_init(&server->dispatchQueue_head, &server->dispatchQueue_tail); cds_lfs_init(&server->mainLoopJobs); #endif /* Create Namespaces 0 and 1 */ server->namespaces = (UA_String *)UA_Array_new(2, &UA_TYPES[UA_TYPES_STRING]); server->namespaces[0] = UA_STRING_ALLOC("http://opcfoundation.org/UA/"); UA_String_copy(&server->config.applicationDescription.applicationUri, &server->namespaces[1]); server->namespacesSize = 2; /* Create Endpoint Definitions */ addEndpointDefinitions(server); /* Initialized SecureChannel and Session managers */ UA_SecureChannelManager_init(&server->secureChannelManager, server); UA_SessionManager_init(&server->sessionManager, server); /* Add a regular job for cleanup and maintenance */ UA_Job cleanup; cleanup.type = UA_JOBTYPE_METHODCALL; cleanup.job.methodCall.data = NULL; cleanup.job.methodCall.method = UA_Server_cleanup; UA_Server_addRepeatedJob(server, cleanup, 10000, NULL); /* Initialized discovery database */ #ifdef UA_ENABLE_DISCOVERY LIST_INIT(&server->registeredServers); server->registeredServersSize = 0; server->periodicServerRegisterJob = NULL; server->registerServerCallback = NULL; server->registerServerCallbackData = NULL; #endif /* Initialize multicast discovery */ #if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) server->mdnsDaemon = NULL; server->mdnsSocket = 0; server->mdnsMainSrvAdded = UA_FALSE; if(server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) initMulticastDiscoveryServer(server); LIST_INIT(&server->serverOnNetwork); server->serverOnNetworkSize = 0; server->serverOnNetworkRecordIdCounter = 0; server->serverOnNetworkRecordIdLastReset = UA_DateTime_now(); memset(server->serverOnNetworkHash, 0, sizeof(struct serverOnNetwork_hash_entry*) * SERVER_ON_NETWORK_HASH_PRIME); server->serverOnNetworkCallback = NULL; server->serverOnNetworkCallbackData = NULL; #endif /* Initialize Namespace 0 */ #ifndef UA_ENABLE_GENERATE_NAMESPACE0 UA_Server_createNS0(server); #else ua_namespaceinit_generated(server); #endif return server; } /*****************/ /* Repeated Jobs */ /*****************/ UA_StatusCode UA_Server_addRepeatedJob(UA_Server *server, UA_Job job, UA_UInt32 interval, UA_Guid *jobId) { return UA_RepeatedJobsList_addRepeatedJob(&server->repeatedJobs, job, interval, jobId); } UA_StatusCode UA_Server_removeRepeatedJob(UA_Server *server, UA_Guid jobId) { return UA_RepeatedJobsList_removeRepeatedJob(&server->repeatedJobs, jobId); }