/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. * * Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) * Copyright 2017 (c) Julian Grothoff * Copyright 2017 (c) Stefan Profanter, fortiss GmbH */ #include "ua_nodestore_default.h" #include "../src/ua_util_internal.h" /* TOOO: Move atomic operations to arch definitions */ /* container_of */ #define container_of(ptr, type, member) \ (type *)((uintptr_t)ptr - offsetof(type,member)) #ifdef UA_ENABLE_MULTITHREADING #include #define BEGIN_CRITSECT(NODEMAP) pthread_mutex_lock(&(NODEMAP)->mutex) #define END_CRITSECT(NODEMAP) pthread_mutex_unlock(&(NODEMAP)->mutex) #else #define BEGIN_CRITSECT(NODEMAP) #define END_CRITSECT(NODEMAP) #endif /* The default Nodestore is simply a hash-map from NodeIds to Nodes. To find an * entry, iterate over candidate positions according to the NodeId hash. * * - Tombstone or non-matching NodeId: continue searching * - Matching NodeId: Return the entry * - NULL: Abort the search * * The nodestore uses atomic operations to set entries of the hash-map. If * UA_ENABLE_IMMUTABLE_NODES is configured, the nodestore allows read-access * from an interrupt without seeing corrupted nodes. For true multi-threaded * access, a mutex is used. * * Multi-threading without a mutex could be realized with the Linux RCU mechanism. * But this is not done for this implementation of the nodestore. */ typedef struct UA_NodeMapEntry { struct UA_NodeMapEntry *orig; /* the version this is a copy from (or NULL) */ UA_UInt16 refCount; /* How many consumers have a reference to the node? */ UA_Boolean deleted; /* Node was marked as deleted and can be deleted when refCount == 0 */ UA_Node node; } UA_NodeMapEntry; #define UA_NODEMAP_MINSIZE 64 #define UA_NODEMAP_TOMBSTONE ((UA_NodeMapEntry*)0x01) typedef struct { UA_NodeMapEntry **entries; UA_UInt32 size; UA_UInt32 count; UA_UInt32 sizePrimeIndex; #ifdef UA_ENABLE_MULTITHREADING pthread_mutex_t mutex; /* Protect access */ #endif } UA_NodeMap; /*********************/ /* HashMap Utilities */ /*********************/ /* The size of the hash-map is always a prime number. They are chosen to be * close to the next power of 2. So the size ca. doubles with each prime. */ static UA_UInt32 const primes[] = { 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, 134217689, 268435399, 536870909, 1073741789, 2147483647, 4294967291 }; static UA_UInt32 mod(UA_UInt32 h, UA_UInt32 size) { return h % size; } static UA_UInt32 mod2(UA_UInt32 h, UA_UInt32 size) { return 1 + (h % (size - 2)); } static UA_UInt16 higher_prime_index(UA_UInt32 n) { UA_UInt16 low = 0; UA_UInt16 high = (UA_UInt16)(sizeof(primes) / sizeof(UA_UInt32)); while(low != high) { UA_UInt16 mid = (UA_UInt16)(low + ((high - low) / 2)); if(n > primes[mid]) low = (UA_UInt16)(mid + 1); else high = mid; } return low; } /* returns an empty slot or null if the nodeid exists or if no empty slot is found. */ static UA_NodeMapEntry ** findFreeSlot(const UA_NodeMap *ns, const UA_NodeId *nodeid) { UA_NodeMapEntry **retval = NULL; UA_UInt32 h = UA_NodeId_hash(nodeid); UA_UInt32 size = ns->size; UA_UInt64 idx = mod(h, size); // use 64 bit container to avoid overflow UA_UInt32 startIdx = (UA_UInt32)idx; UA_UInt32 hash2 = mod2(h, size); UA_NodeMapEntry *entry = NULL; do { entry = ns->entries[(UA_UInt32)idx]; if(entry > UA_NODEMAP_TOMBSTONE && UA_NodeId_equal(&entry->node.nodeId, nodeid)) return NULL; if(!retval && entry <= UA_NODEMAP_TOMBSTONE) retval = &ns->entries[(UA_UInt32)idx]; idx += hash2; if(idx >= size) idx -= size; } while((UA_UInt32)idx != startIdx && entry); /* NULL is returned if there is no free slot (idx == startIdx). * Otherwise the first free slot is returned after we are sure, * that the node id cannot be found in the used hashmap (!entry). */ return retval; } /* The occupancy of the table after the call will be about 50% */ static UA_StatusCode expand(UA_NodeMap *ns) { UA_UInt32 osize = ns->size; UA_UInt32 count = ns->count; /* Resize only when table after removal of unused elements is either too full or too empty */ if(count * 2 < osize && (count * 8 > osize || osize <= UA_NODEMAP_MINSIZE)) return UA_STATUSCODE_GOOD; UA_NodeMapEntry **oentries = ns->entries; UA_UInt32 nindex = higher_prime_index(count * 2); UA_UInt32 nsize = primes[nindex]; UA_NodeMapEntry **nentries = (UA_NodeMapEntry **)UA_calloc(nsize, sizeof(UA_NodeMapEntry*)); if(!nentries) return UA_STATUSCODE_BADOUTOFMEMORY; ns->entries = nentries; ns->size = nsize; ns->sizePrimeIndex = nindex; /* recompute the position of every entry and insert the pointer */ for(size_t i = 0, j = 0; i < osize && j < count; ++i) { if(oentries[i] <= UA_NODEMAP_TOMBSTONE) continue; UA_NodeMapEntry **e = findFreeSlot(ns, &oentries[i]->node.nodeId); UA_assert(e); *e = oentries[i]; ++j; } UA_free(oentries); return UA_STATUSCODE_GOOD; } static UA_NodeMapEntry * newEntry(UA_NodeClass nodeClass) { size_t size = sizeof(UA_NodeMapEntry) - sizeof(UA_Node); switch(nodeClass) { case UA_NODECLASS_OBJECT: size += sizeof(UA_ObjectNode); break; case UA_NODECLASS_VARIABLE: size += sizeof(UA_VariableNode); break; case UA_NODECLASS_METHOD: size += sizeof(UA_MethodNode); break; case UA_NODECLASS_OBJECTTYPE: size += sizeof(UA_ObjectTypeNode); break; case UA_NODECLASS_VARIABLETYPE: size += sizeof(UA_VariableTypeNode); break; case UA_NODECLASS_REFERENCETYPE: size += sizeof(UA_ReferenceTypeNode); break; case UA_NODECLASS_DATATYPE: size += sizeof(UA_DataTypeNode); break; case UA_NODECLASS_VIEW: size += sizeof(UA_ViewNode); break; default: return NULL; } UA_NodeMapEntry *entry = (UA_NodeMapEntry*)UA_calloc(1, size); if(!entry) return NULL; entry->node.nodeClass = nodeClass; return entry; } static void deleteEntry(UA_NodeMapEntry *entry) { UA_Node_deleteMembers(&entry->node); UA_free(entry); } static void cleanupEntry(UA_NodeMapEntry *entry) { if(entry->deleted && entry->refCount == 0) deleteEntry(entry); } static UA_StatusCode clearSlot(UA_NodeMap *ns, UA_NodeMapEntry **slot) { UA_NodeMapEntry *entry = *slot; if(UA_atomic_cmpxchg((void**)slot, entry, UA_NODEMAP_TOMBSTONE) != entry) return UA_STATUSCODE_BADINTERNALERROR; entry->deleted = true; cleanupEntry(entry); --ns->count; /* Downsize the hashmap if it is very empty */ if(ns->count * 8 < ns->size && ns->size > 32) expand(ns); /* Can fail. Just continue with the bigger hashmap. */ return UA_STATUSCODE_GOOD; } static UA_NodeMapEntry ** findOccupiedSlot(const UA_NodeMap *ns, const UA_NodeId *nodeid) { UA_UInt32 h = UA_NodeId_hash(nodeid); UA_UInt32 size = ns->size; UA_UInt64 idx = mod(h, size); // use 64 bit container to avoid overflow UA_UInt32 hash2 = mod2(h, size); UA_UInt32 startIdx = (UA_UInt32)idx; UA_NodeMapEntry *entry = NULL; do { entry = ns->entries[(UA_UInt32)idx]; if(entry > UA_NODEMAP_TOMBSTONE && UA_NodeId_equal(&entry->node.nodeId, nodeid)) return &ns->entries[(UA_UInt32)idx]; idx += hash2; if(idx >= size) idx -= size; } while((UA_UInt32)idx != startIdx && entry); /* NULL is returned if there is no free slot (idx == startIdx) * and the node id is not found or if the end of the used slots (!entry) * is reached. */ return NULL; } /***********************/ /* Interface functions */ /***********************/ static UA_Node * UA_NodeMap_newNode(void *context, UA_NodeClass nodeClass) { UA_NodeMapEntry *entry = newEntry(nodeClass); if(!entry) return NULL; return &entry->node; } static void UA_NodeMap_deleteNode(void *context, UA_Node *node) { #ifdef UA_ENABLE_MULTITHREADING UA_NodeMap *ns = (UA_NodeMap*)context; #endif BEGIN_CRITSECT(ns); UA_NodeMapEntry *entry = container_of(node, UA_NodeMapEntry, node); UA_assert(&entry->node == node); deleteEntry(entry); END_CRITSECT(ns); } static const UA_Node * UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid) { UA_NodeMap *ns = (UA_NodeMap*)context; BEGIN_CRITSECT(ns); UA_NodeMapEntry **entry = findOccupiedSlot(ns, nodeid); if(!entry) { END_CRITSECT(ns); return NULL; } ++(*entry)->refCount; END_CRITSECT(ns); return (const UA_Node*)&(*entry)->node; } static void UA_NodeMap_releaseNode(void *context, const UA_Node *node) { if (!node) return; #ifdef UA_ENABLE_MULTITHREADING UA_NodeMap *ns = (UA_NodeMap*)context; #endif BEGIN_CRITSECT(ns); UA_NodeMapEntry *entry = container_of(node, UA_NodeMapEntry, node); UA_assert(&entry->node == node); UA_assert(entry->refCount > 0); --entry->refCount; cleanupEntry(entry); END_CRITSECT(ns); } static UA_StatusCode UA_NodeMap_getNodeCopy(void *context, const UA_NodeId *nodeid, UA_Node **outNode) { UA_NodeMap *ns = (UA_NodeMap*)context; BEGIN_CRITSECT(ns); UA_NodeMapEntry **slot = findOccupiedSlot(ns, nodeid); if(!slot) { END_CRITSECT(ns); return UA_STATUSCODE_BADNODEIDUNKNOWN; } UA_NodeMapEntry *entry = *slot; UA_NodeMapEntry *newItem = newEntry(entry->node.nodeClass); if(!newItem) { END_CRITSECT(ns); return UA_STATUSCODE_BADOUTOFMEMORY; } UA_StatusCode retval = UA_Node_copy(&entry->node, &newItem->node); if(retval == UA_STATUSCODE_GOOD) { newItem->orig = entry; // store the pointer to the original *outNode = &newItem->node; } else { deleteEntry(newItem); } END_CRITSECT(ns); return retval; } static UA_StatusCode UA_NodeMap_removeNode(void *context, const UA_NodeId *nodeid) { UA_NodeMap *ns = (UA_NodeMap*)context; BEGIN_CRITSECT(ns); UA_NodeMapEntry **slot = findOccupiedSlot(ns, nodeid); UA_StatusCode retval = UA_STATUSCODE_GOOD; if(slot) retval = clearSlot(ns, slot); else retval = UA_STATUSCODE_BADNODEIDUNKNOWN; END_CRITSECT(ns); return retval; } static UA_StatusCode UA_NodeMap_insertNode(void *context, UA_Node *node, UA_NodeId *addedNodeId) { UA_NodeMap *ns = (UA_NodeMap*)context; BEGIN_CRITSECT(ns); if(ns->size * 3 <= ns->count * 4) { if(expand(ns) != UA_STATUSCODE_GOOD) { END_CRITSECT(ns); return UA_STATUSCODE_BADINTERNALERROR; } } UA_NodeMapEntry **slot; if(node->nodeId.identifierType == UA_NODEIDTYPE_NUMERIC && node->nodeId.identifier.numeric == 0) { /* create a random nodeid */ /* start at least with 50,000 to make sure we don not conflict with nodes from the spec */ /* if we find a conflict, we just try another identifier until we have tried all possible identifiers */ /* since the size is prime and we don't change the increase val, we will reach the starting id again */ /* E.g. adding a nodeset will create children while there are still other nodes which need to be created */ /* Thus the node ids may collide */ UA_UInt32 size = ns->size; UA_UInt64 identifier = mod(50000 + size+1, UA_UINT32_MAX); // start value, use 64 bit container to avoid overflow UA_UInt32 increase = mod2(ns->count+1, size); UA_UInt32 startId = (UA_UInt32)identifier; // mod ensures us that the id is a valid 32 bit do { node->nodeId.identifier.numeric = (UA_UInt32)identifier; slot = findFreeSlot(ns, &node->nodeId); if(slot) break; identifier += increase; if(identifier >= size) identifier -= size; } while((UA_UInt32)identifier != startId); } else { slot = findFreeSlot(ns, &node->nodeId); } if(!slot) { deleteEntry(container_of(node, UA_NodeMapEntry, node)); END_CRITSECT(ns); return UA_STATUSCODE_BADNODEIDEXISTS; } /* Copy the NodeId */ UA_StatusCode retval = UA_STATUSCODE_GOOD; if(addedNodeId) { retval = UA_NodeId_copy(&node->nodeId, addedNodeId); if(retval != UA_STATUSCODE_GOOD) { deleteEntry(container_of(node, UA_NodeMapEntry, node)); END_CRITSECT(ns); return retval; } } /* Insert the node */ UA_NodeMapEntry *oldEntry = *slot; UA_NodeMapEntry *newEntry = container_of(node, UA_NodeMapEntry, node); if(oldEntry > UA_NODEMAP_TOMBSTONE || UA_atomic_cmpxchg((void**)slot, oldEntry, newEntry) != oldEntry) { deleteEntry(container_of(node, UA_NodeMapEntry, node)); END_CRITSECT(ns); return UA_STATUSCODE_BADNODEIDEXISTS; } ++ns->count; END_CRITSECT(ns); return retval; } static UA_StatusCode UA_NodeMap_replaceNode(void *context, UA_Node *node) { UA_NodeMap *ns = (UA_NodeMap*)context; BEGIN_CRITSECT(ns); /* Find the node */ UA_NodeMapEntry **slot = findOccupiedSlot(ns, &node->nodeId); if(!slot) { END_CRITSECT(ns); return UA_STATUSCODE_BADNODEIDUNKNOWN; } UA_NodeMapEntry *newEntryContainer = container_of(node, UA_NodeMapEntry, node); UA_NodeMapEntry *oldEntryContainer = *slot; /* The node was already updated since the copy was made? */ if(oldEntryContainer != newEntryContainer->orig) { deleteEntry(newEntryContainer); END_CRITSECT(ns); return UA_STATUSCODE_BADINTERNALERROR; } /* Replace the entry with an atomic operation */ if(UA_atomic_cmpxchg((void**)slot, oldEntryContainer, newEntryContainer) != oldEntryContainer) { deleteEntry(newEntryContainer); END_CRITSECT(ns); return UA_STATUSCODE_BADINTERNALERROR; } oldEntryContainer->deleted = true; cleanupEntry(oldEntryContainer); END_CRITSECT(ns); return UA_STATUSCODE_GOOD; } static void UA_NodeMap_iterate(void *context, void *visitorContext, UA_NodestoreVisitor visitor) { UA_NodeMap *ns = (UA_NodeMap*)context; BEGIN_CRITSECT(ns); for(UA_UInt32 i = 0; i < ns->size; ++i) { if(ns->entries[i] > UA_NODEMAP_TOMBSTONE) { END_CRITSECT(ns); UA_NodeMapEntry *entry = ns->entries[i]; entry->refCount++; visitor(visitorContext, &entry->node); entry->refCount--; cleanupEntry(entry); BEGIN_CRITSECT(ns); } } END_CRITSECT(ns); } static void UA_NodeMap_delete(void *context) { UA_NodeMap *ns = (UA_NodeMap*)context; #ifdef UA_ENABLE_MULTITHREADING pthread_mutex_destroy(&ns->mutex); #endif UA_UInt32 size = ns->size; UA_NodeMapEntry **entries = ns->entries; for(UA_UInt32 i = 0; i < size; ++i) { if(entries[i] > UA_NODEMAP_TOMBSTONE) { /* On debugging builds, check that all nodes were release */ UA_assert(entries[i]->refCount == 0); /* Delete the node */ deleteEntry(entries[i]); } } UA_free(ns->entries); UA_free(ns); } UA_StatusCode UA_Nodestore_default_new(UA_Nodestore *ns) { /* Allocate and initialize the nodemap */ UA_NodeMap *nodemap = (UA_NodeMap*)UA_malloc(sizeof(UA_NodeMap)); if(!nodemap) return UA_STATUSCODE_BADOUTOFMEMORY; nodemap->sizePrimeIndex = higher_prime_index(UA_NODEMAP_MINSIZE); nodemap->size = primes[nodemap->sizePrimeIndex]; nodemap->count = 0; nodemap->entries = (UA_NodeMapEntry**) UA_calloc(nodemap->size, sizeof(UA_NodeMapEntry*)); if(!nodemap->entries) { UA_free(nodemap); return UA_STATUSCODE_BADOUTOFMEMORY; } #ifdef UA_ENABLE_MULTITHREADING pthread_mutex_init(&nodemap->mutex, NULL); #endif /* Populate the nodestore */ ns->context = nodemap; ns->deleteNodestore = UA_NodeMap_delete; ns->inPlaceEditAllowed = true; ns->newNode = UA_NodeMap_newNode; ns->deleteNode = UA_NodeMap_deleteNode; ns->getNode = UA_NodeMap_getNode; ns->releaseNode = UA_NodeMap_releaseNode; ns->getNodeCopy = UA_NodeMap_getNodeCopy; ns->insertNode = UA_NodeMap_insertNode; ns->replaceNode = UA_NodeMap_replaceNode; ns->removeNode = UA_NodeMap_removeNode; ns->iterate = UA_NodeMap_iterate; return UA_STATUSCODE_GOOD; }