Coverage Report

Created: 2026-09-28 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541_15/plugins/ua_nodestore_ziptree.c
Line
Count
Source
1
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
2
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
3
 *
4
 *    Copyright 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
5
 *    Copyright 2017 (c) Julian Grothoff
6
 *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
7
 */
8
9
#include <open62541/server.h>
10
#include <open62541/plugin/nodestore.h>
11
#include <open62541/plugin/nodestore_default.h>
12
#include "ziptree.h"
13
#include "pcg_basic.h"
14
15
#ifndef container_of
16
#define container_of(ptr, type, member) \
17
898M
    (type *)((uintptr_t)ptr - offsetof(type,member))
18
#endif
19
20
struct NodeEntry;
21
typedef struct NodeEntry NodeEntry;
22
23
struct NodeEntry {
24
    ZIP_ENTRY(NodeEntry) zipfields;
25
    UA_UInt32 nodeIdHash;
26
    UA_UInt16 refCount; /* How many consumers have a reference to the node? */
27
    UA_Boolean deleted; /* Node was marked as deleted and can be deleted when refCount == 0 */
28
    NodeEntry *orig;    /* If a copy is made to replace a node, track that we
29
                         * replace only the node from which the copy was made.
30
                         * Important for concurrent operations. */
31
    UA_NodeId nodeId; /* This is actually a UA_Node that also starts with a NodeId */
32
};
33
34
/* Absolute ordering for NodeIds */
35
static enum ZIP_CMP
36
10.0G
cmpNodeId(const void *a, const void *b) {
37
10.0G
    const NodeEntry *aa = (const NodeEntry*)a;
38
10.0G
    const NodeEntry *bb = (const NodeEntry*)b;
39
40
    /* Compare hash */
41
10.0G
    if(aa->nodeIdHash < bb->nodeIdHash)
42
4.34G
        return ZIP_CMP_LESS;
43
5.66G
    if(aa->nodeIdHash > bb->nodeIdHash)
44
4.78G
        return ZIP_CMP_MORE;
45
46
    /* Compore nodes in detail */
47
883M
    return (enum ZIP_CMP)UA_NodeId_order(&aa->nodeId, &bb->nodeId);
48
5.66G
}
49
50
ZIP_HEAD(NodeTree, NodeEntry);
51
typedef struct NodeTree NodeTree;
52
53
typedef struct {
54
    UA_Nodestore ns;
55
56
    NodeTree root;
57
    size_t size;
58
59
    /* Maps ReferenceTypeIndex to the NodeId of the ReferenceType */
60
    UA_NodeId referenceTypeIds[UA_REFERENCETYPESET_MAX];
61
    UA_Byte referenceTypeCounter;
62
} ZipNodestore;
63
64
18.7M
ZIP_FUNCTIONS(NodeTree, NodeEntry, zipfields, NodeEntry, zipfields, cmpNodeId)
ua_nodestore_ziptree.c:NodeTree_ZIP_ITER
Line
Count
Source
64
ZIP_FUNCTIONS(NodeTree, NodeEntry, zipfields, NodeEntry, zipfields, cmpNodeId)
ua_nodestore_ziptree.c:NodeTree_ZIP_INSERT
Line
Count
Source
64
ZIP_FUNCTIONS(NodeTree, NodeEntry, zipfields, NodeEntry, zipfields, cmpNodeId)
ua_nodestore_ziptree.c:NodeTree_ZIP_REMOVE
Line
Count
Source
64
ZIP_FUNCTIONS(NodeTree, NodeEntry, zipfields, NodeEntry, zipfields, cmpNodeId)
65
66
static NodeEntry *
67
17.9M
newEntry(UA_NodeClass nodeClass) {
68
17.9M
    size_t size = sizeof(NodeEntry) - sizeof(UA_NodeId);
69
17.9M
    switch(nodeClass) {
70
1.47M
    case UA_NODECLASS_OBJECT:
71
1.47M
        size += sizeof(UA_ObjectNode);
72
1.47M
        break;
73
12.1M
    case UA_NODECLASS_VARIABLE:
74
12.1M
        size += sizeof(UA_VariableNode);
75
12.1M
        break;
76
720k
    case UA_NODECLASS_METHOD:
77
720k
        size += sizeof(UA_MethodNode);
78
720k
        break;
79
1.07M
    case UA_NODECLASS_OBJECTTYPE:
80
1.07M
        size += sizeof(UA_ObjectTypeNode);
81
1.07M
        break;
82
506k
    case UA_NODECLASS_VARIABLETYPE:
83
506k
        size += sizeof(UA_VariableTypeNode);
84
506k
        break;
85
590k
    case UA_NODECLASS_REFERENCETYPE:
86
590k
        size += sizeof(UA_ReferenceTypeNode);
87
590k
        break;
88
1.42M
    case UA_NODECLASS_DATATYPE:
89
1.42M
        size += sizeof(UA_DataTypeNode);
90
1.42M
        break;
91
0
    case UA_NODECLASS_VIEW:
92
0
        size += sizeof(UA_ViewNode);
93
0
        break;
94
0
    default:
95
0
        return NULL;
96
17.9M
    }
97
17.9M
    NodeEntry *entry = (NodeEntry*)UA_calloc(1, size);
98
17.9M
    if(!entry)
99
0
        return NULL;
100
17.9M
    UA_Node *node = (UA_Node*)&entry->nodeId;
101
17.9M
    node->head.nodeClass = nodeClass;
102
17.9M
    return entry;
103
17.9M
}
104
105
static void
106
17.9M
deleteEntry(NodeEntry *entry) {
107
17.9M
    UA_Node_clear((UA_Node*)&entry->nodeId);
108
17.9M
    UA_free(entry);
109
17.9M
}
110
111
static void
112
881M
cleanupEntry(NodeEntry *entry) {
113
881M
    if(entry->refCount > 0)
114
210M
        return;
115
670M
    if(entry->deleted) {
116
759k
        deleteEntry(entry);
117
759k
        return;
118
759k
    }
119
669M
    UA_NodeHead *head = (UA_NodeHead*)&entry->nodeId;
120
2.24G
    for(size_t i = 0; i < head->referencesSize; i++) {
121
1.57G
        UA_NodeReferenceKind *rk = &head->references[i];
122
1.57G
        if(rk->targetsSize > 16 && !rk->hasRefTree)
123
262k
            UA_NodeReferenceKind_switch(rk);
124
1.57G
    }
125
669M
}
126
127
/***********************/
128
/* Interface functions */
129
/***********************/
130
131
/* Not yet inserted into the ZipContext */
132
static UA_Node *
133
17.9M
zipNsNewNode(UA_Nodestore *_, UA_NodeClass nodeClass) {
134
17.9M
    NodeEntry *entry = newEntry(nodeClass);
135
17.9M
    if(!entry)
136
0
        return NULL;
137
17.9M
    return (UA_Node*)&entry->nodeId;
138
17.9M
}
139
140
/* Not yet inserted into the ZipContext */
141
static void
142
0
zipNsDeleteNode(UA_Nodestore *_, UA_Node *node) {
143
0
    deleteEntry(container_of(node, NodeEntry, nodeId));
144
0
}
145
146
static const UA_Node *
147
zipNsGetNode(UA_Nodestore *ns, const UA_NodeId *nodeId,
148
             UA_UInt32 attributeMask,
149
             UA_ReferenceTypeSet references,
150
808M
             UA_BrowseDirection referenceDirections) {
151
808M
    NodeEntry dummy;
152
808M
    dummy.nodeIdHash = UA_NodeId_hash(nodeId);
153
808M
    dummy.nodeId = *nodeId;
154
808M
    ZipNodestore *zns = (ZipNodestore*)ns;
155
808M
    NodeEntry *entry = ZIP_FIND(NodeTree, &zns->root, &dummy);
156
808M
    if(!entry)
157
943k
        return NULL;
158
807M
    ++entry->refCount;
159
807M
    return (const UA_Node*)&entry->nodeId;
160
808M
}
161
162
static const UA_Node *
163
zipNsGetNodeFromPtr(UA_Nodestore *ns, UA_NodePointer ptr,
164
                    UA_UInt32 attributeMask,
165
                    UA_ReferenceTypeSet references,
166
357M
                    UA_BrowseDirection referenceDirections) {
167
357M
    if(!UA_NodePointer_isLocal(ptr))
168
0
        return NULL;
169
357M
    UA_NodeId id = UA_NodePointer_toNodeId(ptr);
170
357M
    return zipNsGetNode(ns, &id, attributeMask,
171
357M
                        references, referenceDirections);
172
357M
}
173
174
static void
175
880M
zipNsReleaseNode(UA_Nodestore *_, const UA_Node *node) {
176
880M
    if(!node)
177
18.1k
        return;
178
880M
    NodeEntry *entry = container_of(node, NodeEntry, nodeId);
179
880M
    UA_assert(entry->refCount > 0);
180
880M
    --entry->refCount;
181
880M
    cleanupEntry(entry);
182
880M
}
183
184
static UA_StatusCode
185
zipNsGetNodeCopy(UA_Nodestore *ns, const UA_NodeId *nodeId,
186
0
                 UA_Node **outNode) {
187
    /* Get the node (with all attributes and references, the mask and refs are
188
       currently noy evaluated within the plugin.) */
189
0
    const UA_Node *node =
190
0
        zipNsGetNode(ns, nodeId, UA_NODEATTRIBUTESMASK_ALL,
191
0
                     UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
192
0
    if(!node)
193
0
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
194
195
    /* Create the new entry */
196
0
    NodeEntry *ne = newEntry(node->head.nodeClass);
197
0
    if(!ne) {
198
0
        zipNsReleaseNode(ns, node);
199
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
200
0
    }
201
202
    /* Copy the node content */
203
0
    UA_Node *nnode = (UA_Node*)&ne->nodeId;
204
0
    UA_StatusCode retval = UA_Node_copy(node, nnode);
205
0
    zipNsReleaseNode(NULL, node);
206
0
    if(retval != UA_STATUSCODE_GOOD) {
207
0
        deleteEntry(ne);
208
0
        return retval;
209
0
    }
210
211
0
    ne->orig = container_of(node, NodeEntry, nodeId);
212
0
    *outNode = nnode;
213
0
    return UA_STATUSCODE_GOOD;
214
0
}
215
216
static UA_StatusCode
217
17.9M
zipNsInsertNode(UA_Nodestore *ns, UA_Node *node, UA_NodeId *addedNodeId) {
218
17.9M
    NodeEntry *entry = container_of(node, NodeEntry, nodeId);
219
17.9M
    ZipNodestore *zns = (ZipNodestore*)ns;
220
221
    /* Ensure that the NodeId is unique by testing their presence. If the NodeId
222
     * is ns=xx;i=0, then the numeric identifier is replaced with a random
223
     * unused int32. It is ensured that the created identifiers are stable after
224
     * a server restart (assuming that Nodes are created in the same order and
225
     * with the same BrowseName). */
226
17.9M
    NodeEntry dummy;
227
17.9M
    memset(&dummy, 0, sizeof(NodeEntry));
228
17.9M
    dummy.nodeId = node->head.nodeId;
229
17.9M
    if(node->head.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
230
17.9M
       node->head.nodeId.identifier.numeric == 0) {
231
1.00M
        NodeEntry *found;
232
1.00M
        UA_UInt32 mask = 0x2F;
233
1.00M
        pcg32_random_t rng;
234
1.00M
        pcg32_srandom_r(&rng, zns->size, 0);
235
2.69M
        do {
236
            /* Generate a random NodeId. Favor "easy" NodeIds.
237
             * Always above 50000. */
238
2.69M
            UA_UInt32 numId = (pcg32_random_r(&rng) & mask) + 50000;
239
240
#if SIZE_MAX <= UA_UINT32_MAX
241
            /* The compressed "immediate" representation of nodes does not
242
             * support the full range on 32bit systems. Generate smaller
243
             * identifiers as they can be stored more compactly. */
244
            if(numId >= (0x01 << 24))
245
                numId = numId % (0x01 << 24);
246
#endif
247
2.69M
            node->head.nodeId.identifier.numeric = numId;
248
249
            /* Look up the current NodeId */
250
2.69M
            dummy.nodeId.identifier.numeric = numId;
251
2.69M
            dummy.nodeIdHash = UA_NodeId_hash(&node->head.nodeId);
252
2.69M
            found = ZIP_FIND(NodeTree, &zns->root, &dummy);
253
254
2.69M
            if(found) {
255
                /* Reseed the rng using the browseName of the existing node.
256
                 * This ensures that different information models end up with
257
                 * different NodeId sequences, but still stable after a
258
                 * restart. */
259
1.68M
                UA_NodeHead *nh = (UA_NodeHead*)&found->nodeId;
260
1.68M
                pcg32_srandom_r(&rng, rng.state, UA_QualifiedName_hash(&nh->browseName));
261
262
                /* Make the mask less strict when the NodeId already exists */
263
1.68M
                mask = (mask << 1) | 0x01;
264
1.68M
            }
265
2.69M
        } while(found);
266
16.9M
    } else {
267
16.9M
        dummy.nodeIdHash = UA_NodeId_hash(&node->head.nodeId);
268
16.9M
        if(ZIP_FIND(NodeTree, &zns->root, &dummy)) { /* The nodeid exists */
269
0
            deleteEntry(entry);
270
0
            return UA_STATUSCODE_BADNODEIDEXISTS;
271
0
        }
272
16.9M
    }
273
274
    /* Copy the NodeId */
275
17.9M
    if(addedNodeId) {
276
17.9M
        UA_StatusCode retval = UA_NodeId_copy(&node->head.nodeId, addedNodeId);
277
17.9M
        if(retval != UA_STATUSCODE_GOOD) {
278
0
            deleteEntry(entry);
279
0
            return retval;
280
0
        }
281
17.9M
    }
282
283
    /* For new ReferencetypeNodes add to the index map */
284
17.9M
    if(node->head.nodeClass == UA_NODECLASS_REFERENCETYPE) {
285
590k
        UA_ReferenceTypeNode *refNode = &node->referenceTypeNode;
286
590k
        if(zns->referenceTypeCounter >= UA_REFERENCETYPESET_MAX) {
287
0
            deleteEntry(entry);
288
0
            return UA_STATUSCODE_BADINTERNALERROR;
289
0
        }
290
291
590k
        UA_StatusCode retval =
292
590k
            UA_NodeId_copy(&node->head.nodeId,
293
590k
                           &zns->referenceTypeIds[zns->referenceTypeCounter]);
294
590k
        if(retval != UA_STATUSCODE_GOOD) {
295
0
            deleteEntry(entry);
296
0
            return UA_STATUSCODE_BADINTERNALERROR;
297
0
        }
298
299
        /* Assign the ReferenceTypeIndex to the new ReferenceTypeNode */
300
590k
        refNode->referenceTypeIndex = zns->referenceTypeCounter;
301
590k
        refNode->subTypes = UA_REFTYPESET(zns->referenceTypeCounter);
302
590k
        zns->referenceTypeCounter++;
303
590k
    }
304
305
    /* Insert the node */
306
17.9M
    entry->nodeIdHash = dummy.nodeIdHash;
307
17.9M
    ZIP_INSERT(NodeTree, &zns->root, entry);
308
17.9M
    zns->size++;
309
17.9M
    return UA_STATUSCODE_GOOD;
310
17.9M
}
311
312
static UA_StatusCode
313
0
zipNsReplaceNode(UA_Nodestore *ns, UA_Node *node) {
314
    /* Find the node (the mask and refs are not evaluated yet by the plugin)*/
315
0
    const UA_Node *oldNode =
316
0
        zipNsGetNode(ns, &node->head.nodeId, UA_NODEATTRIBUTESMASK_ALL,
317
0
                     UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
318
0
    if(!oldNode) {
319
0
        deleteEntry(container_of(node, NodeEntry, nodeId));
320
0
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
321
0
    }
322
323
    /* Test if the copy is current */
324
0
    NodeEntry *entry = container_of(node, NodeEntry, nodeId);
325
0
    NodeEntry *oldEntry = container_of(oldNode, NodeEntry, nodeId);
326
0
    if(oldEntry != entry->orig) {
327
        /* The node was already updated since the copy was made */
328
0
        deleteEntry(entry);
329
0
        zipNsReleaseNode(NULL, oldNode);
330
0
        return UA_STATUSCODE_BADINTERNALERROR;
331
0
    }
332
333
    /* All failure checks have passed. Move the runtime associations only at
334
     * the commit point so a failed replacement leaves the old node intact. */
335
0
    UA_Node_moveMonitoredItems((UA_Node*)&oldEntry->nodeId, node);
336
337
    /* Replace */
338
0
    ZipNodestore *zns = (ZipNodestore*)ns;
339
0
    ZIP_REMOVE(NodeTree, &zns->root, oldEntry);
340
0
    entry->nodeIdHash = oldEntry->nodeIdHash;
341
0
    ZIP_INSERT(NodeTree, &zns->root, entry);
342
0
    oldEntry->deleted = true;
343
344
0
    zipNsReleaseNode(NULL, oldNode);
345
0
    return UA_STATUSCODE_GOOD;
346
0
}
347
348
static UA_StatusCode
349
759k
zipNsRemoveNode(UA_Nodestore *ns, const UA_NodeId *nodeId) {
350
759k
    ZipNodestore *zns = (ZipNodestore*)ns;
351
759k
    NodeEntry dummy;
352
759k
    dummy.nodeIdHash = UA_NodeId_hash(nodeId);
353
759k
    dummy.nodeId = *nodeId;
354
759k
    NodeEntry *entry = ZIP_FIND(NodeTree, &zns->root, &dummy);
355
759k
    if(!entry)
356
0
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
357
759k
    ZIP_REMOVE(NodeTree, &zns->root, entry);
358
759k
    zns->size--;
359
759k
    entry->deleted = true;
360
759k
    cleanupEntry(entry);
361
759k
    return UA_STATUSCODE_GOOD;
362
759k
}
363
364
static const UA_NodeId *
365
12.1M
zipNsGetReferenceTypeId(UA_Nodestore *ns, UA_Byte refTypeIndex) {
366
12.1M
    ZipNodestore *zns = (ZipNodestore*)ns;
367
12.1M
    if(refTypeIndex >= zns->referenceTypeCounter)
368
0
        return NULL;
369
12.1M
    return &zns->referenceTypeIds[refTypeIndex];
370
12.1M
}
371
372
struct VisitorData {
373
    UA_NodestoreVisitor visitor;
374
    void *visitorContext;
375
};
376
377
static void *
378
0
nodeVisitor(void *data, NodeEntry *entry) {
379
0
    struct VisitorData *d = (struct VisitorData*)data;
380
0
    d->visitor(d->visitorContext, (UA_Node*)&entry->nodeId);
381
0
    return NULL;
382
0
}
383
384
static void
385
zipNsIterate(UA_Nodestore *ns, UA_NodestoreVisitor visitor,
386
0
             void *visitorCtx) {
387
0
    struct VisitorData d;
388
0
    d.visitor = visitor;
389
0
    d.visitorContext = visitorCtx;
390
0
    ZipNodestore *zns = (ZipNodestore*)ns;
391
0
    ZIP_ITER(NodeTree, &zns->root, nodeVisitor, &d);
392
0
}
393
394
static void *
395
17.2M
deleteNodeVisitor(void *data, NodeEntry *entry) {
396
17.2M
    deleteEntry(entry);
397
17.2M
    return NULL;
398
17.2M
}
399
400
/***********************/
401
/* Nodestore Lifecycle */
402
/***********************/
403
404
static void
405
20.6k
zipNsFree(UA_Nodestore *ns) {
406
20.6k
    ZipNodestore *zns = (ZipNodestore*)ns;
407
20.6k
    ZIP_ITER(NodeTree, &zns->root, deleteNodeVisitor, NULL);
408
409
    /* Clean up the ReferenceTypes index array */
410
611k
    for(size_t i = 0; i < zns->referenceTypeCounter; i++)
411
590k
        UA_NodeId_clear(&zns->referenceTypeIds[i]);
412
413
20.6k
    UA_free(zns);
414
20.6k
}
415
416
UA_Nodestore *
417
20.6k
UA_Nodestore_ZipTree(void) {
418
    /* Allocate and initialize the context */
419
20.6k
    ZipNodestore *zns = (ZipNodestore*)UA_calloc(1, sizeof(ZipNodestore));
420
20.6k
    if(!zns)
421
0
        return NULL;
422
423
20.6k
    ZIP_INIT(&zns->root);
424
20.6k
    zns->referenceTypeCounter = 0;
425
426
    /* Populate the nodestore */
427
20.6k
    zns->ns.free = zipNsFree;
428
20.6k
    zns->ns.newNode = zipNsNewNode;
429
20.6k
    zns->ns.deleteNode = zipNsDeleteNode;
430
20.6k
    zns->ns.getNode = zipNsGetNode;
431
20.6k
    zns->ns.getNodeFromPtr = zipNsGetNodeFromPtr;
432
20.6k
    zns->ns.releaseNode = zipNsReleaseNode;
433
20.6k
    zns->ns.getNodeCopy = zipNsGetNodeCopy;
434
20.6k
    zns->ns.insertNode = zipNsInsertNode;
435
20.6k
    zns->ns.replaceNode = zipNsReplaceNode;
436
20.6k
    zns->ns.removeNode = zipNsRemoveNode;
437
20.6k
    zns->ns.getReferenceTypeId = zipNsGetReferenceTypeId;
438
20.6k
    zns->ns.iterate = zipNsIterate;
439
440
    /* All nodes are stored in RAM. Changes are made in-situ. GetEditNode is
441
     * identical to GetNode -- but the Node pointer is non-const. */
442
20.6k
    zns->ns.getEditNode =
443
20.6k
        (UA_Node * (*)(UA_Nodestore *ns, const UA_NodeId *nodeId,
444
20.6k
                       UA_UInt32 attributeMask,
445
20.6k
                       UA_ReferenceTypeSet references,
446
20.6k
                       UA_BrowseDirection referenceDirections))zipNsGetNode;
447
20.6k
    zns->ns.getEditNodeFromPtr =
448
20.6k
        (UA_Node * (*)(UA_Nodestore *ns, UA_NodePointer ptr,
449
20.6k
                       UA_UInt32 attributeMask,
450
20.6k
                       UA_ReferenceTypeSet references,
451
20.6k
                       UA_BrowseDirection referenceDirections))zipNsGetNodeFromPtr;
452
453
20.6k
    return &zns->ns;
454
20.6k
}