Coverage Report

Created: 2026-07-16 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541/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
559M
    (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
6.21G
cmpNodeId(const void *a, const void *b) {
37
6.21G
    const NodeEntry *aa = (const NodeEntry*)a;
38
6.21G
    const NodeEntry *bb = (const NodeEntry*)b;
39
40
    /* Compare hash */
41
6.21G
    if(aa->nodeIdHash < bb->nodeIdHash)
42
2.69G
        return ZIP_CMP_LESS;
43
3.52G
    if(aa->nodeIdHash > bb->nodeIdHash)
44
2.97G
        return ZIP_CMP_MORE;
45
46
    /* Compore nodes in detail */
47
548M
    return (enum ZIP_CMP)UA_NodeId_order(&aa->nodeId, &bb->nodeId);
48
3.52G
}
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
11.9M
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
11.5M
newEntry(UA_NodeClass nodeClass) {
68
11.5M
    size_t size = sizeof(NodeEntry) - sizeof(UA_NodeId);
69
11.5M
    switch(nodeClass) {
70
947k
    case UA_NODECLASS_OBJECT:
71
947k
        size += sizeof(UA_ObjectNode);
72
947k
        break;
73
7.85M
    case UA_NODECLASS_VARIABLE:
74
7.85M
        size += sizeof(UA_VariableNode);
75
7.85M
        break;
76
464k
    case UA_NODECLASS_METHOD:
77
464k
        size += sizeof(UA_MethodNode);
78
464k
        break;
79
690k
    case UA_NODECLASS_OBJECTTYPE:
80
690k
        size += sizeof(UA_ObjectTypeNode);
81
690k
        break;
82
326k
    case UA_NODECLASS_VARIABLETYPE:
83
326k
        size += sizeof(UA_VariableTypeNode);
84
326k
        break;
85
364k
    case UA_NODECLASS_REFERENCETYPE:
86
364k
        size += sizeof(UA_ReferenceTypeNode);
87
364k
        break;
88
917k
    case UA_NODECLASS_DATATYPE:
89
917k
        size += sizeof(UA_DataTypeNode);
90
917k
        break;
91
0
    case UA_NODECLASS_VIEW:
92
0
        size += sizeof(UA_ViewNode);
93
0
        break;
94
0
    default:
95
0
        return NULL;
96
11.5M
    }
97
11.5M
    NodeEntry *entry = (NodeEntry*)UA_calloc(1, size);
98
11.5M
    if(!entry)
99
0
        return NULL;
100
11.5M
    UA_Node *node = (UA_Node*)&entry->nodeId;
101
11.5M
    node->head.nodeClass = nodeClass;
102
#ifdef UA_ENABLE_RBAC
103
    node->head.permissionIndex = UA_PERMISSION_INDEX_INVALID;
104
#endif
105
11.5M
    return entry;
106
11.5M
}
107
108
static void
109
11.5M
deleteEntry(NodeEntry *entry) {
110
11.5M
    UA_Node_clear((UA_Node*)&entry->nodeId);
111
11.5M
    UA_free(entry);
112
11.5M
}
113
114
static void
115
547M
cleanupEntry(NodeEntry *entry) {
116
547M
    if(entry->refCount > 0)
117
135M
        return;
118
411M
    if(entry->deleted) {
119
343k
        deleteEntry(entry);
120
343k
        return;
121
343k
    }
122
411M
    UA_NodeHead *head = (UA_NodeHead*)&entry->nodeId;
123
1.38G
    for(size_t i = 0; i < head->referencesSize; i++) {
124
973M
        UA_NodeReferenceKind *rk = &head->references[i];
125
973M
        if(rk->targetsSize > 16 && !rk->hasRefTree)
126
169k
            UA_NodeReferenceKind_switch(rk);
127
973M
    }
128
411M
}
129
130
/***********************/
131
/* Interface functions */
132
/***********************/
133
134
/* Not yet inserted into the ZipContext */
135
static UA_Node *
136
10.9M
zipNsNewNode(UA_Nodestore *_, UA_NodeClass nodeClass) {
137
10.9M
    NodeEntry *entry = newEntry(nodeClass);
138
10.9M
    if(!entry)
139
0
        return NULL;
140
10.9M
    return (UA_Node*)&entry->nodeId;
141
10.9M
}
142
143
/* Not yet inserted into the ZipContext */
144
static void
145
0
zipNsDeleteNode(UA_Nodestore *_, UA_Node *node) {
146
0
    deleteEntry(container_of(node, NodeEntry, nodeId));
147
0
}
148
149
static const UA_Node *
150
zipNsGetNode(UA_Nodestore *ns, const UA_NodeId *nodeId,
151
             UA_UInt32 attributeMask,
152
             UA_ReferenceTypeSet references,
153
547M
             UA_BrowseDirection referenceDirections) {
154
547M
    NodeEntry dummy;
155
547M
    dummy.nodeIdHash = UA_NodeId_hash(nodeId);
156
547M
    dummy.nodeId = *nodeId;
157
547M
    ZipNodestore *zns = (ZipNodestore*)ns;
158
547M
    NodeEntry *entry = ZIP_FIND(NodeTree, &zns->root, &dummy);
159
547M
    if(!entry)
160
519k
        return NULL;
161
546M
    ++entry->refCount;
162
546M
    return (const UA_Node*)&entry->nodeId;
163
547M
}
164
165
static const UA_Node *
166
zipNsGetNodeFromPtr(UA_Nodestore *ns, UA_NodePointer ptr,
167
                    UA_UInt32 attributeMask,
168
                    UA_ReferenceTypeSet references,
169
224M
                    UA_BrowseDirection referenceDirections) {
170
224M
    if(!UA_NodePointer_isLocal(ptr))
171
0
        return NULL;
172
224M
    UA_NodeId id = UA_NodePointer_toNodeId(ptr);
173
224M
    return zipNsGetNode(ns, &id, attributeMask,
174
224M
                        references, referenceDirections);
175
224M
}
176
177
static void
178
546M
zipNsReleaseNode(UA_Nodestore *_, const UA_Node *node) {
179
546M
    if(!node)
180
3.43k
        return;
181
546M
    NodeEntry *entry = container_of(node, NodeEntry, nodeId);
182
546M
    UA_assert(entry->refCount > 0);
183
546M
    --entry->refCount;
184
546M
    cleanupEntry(entry);
185
546M
}
186
187
static UA_StatusCode
188
zipNsGetNodeCopy(UA_Nodestore *ns, const UA_NodeId *nodeId,
189
643k
                 UA_Node **outNode) {
190
    /* Get the node (with all attributes and references, the mask and refs are
191
       currently noy evaluated within the plugin.) */
192
643k
    const UA_Node *node =
193
643k
        zipNsGetNode(ns, nodeId, UA_NODEATTRIBUTESMASK_ALL,
194
643k
                     UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
195
643k
    if(!node)
196
0
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
197
198
    /* Create the new entry */
199
643k
    NodeEntry *ne = newEntry(node->head.nodeClass);
200
643k
    if(!ne) {
201
0
        zipNsReleaseNode(ns, node);
202
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
203
0
    }
204
205
    /* Copy the node content */
206
643k
    UA_Node *nnode = (UA_Node*)&ne->nodeId;
207
643k
    UA_StatusCode retval = UA_Node_copy(node, nnode);
208
643k
    zipNsReleaseNode(NULL, node);
209
643k
    if(retval != UA_STATUSCODE_GOOD) {
210
0
        deleteEntry(ne);
211
0
        return retval;
212
0
    }
213
214
643k
    ne->orig = container_of(node, NodeEntry, nodeId);
215
643k
    *outNode = nnode;
216
643k
    return UA_STATUSCODE_GOOD;
217
643k
}
218
219
static UA_StatusCode
220
11.5M
zipNsInsertNode(UA_Nodestore *ns, UA_Node *node, UA_NodeId *addedNodeId) {
221
11.5M
    NodeEntry *entry = container_of(node, NodeEntry, nodeId);
222
11.5M
    ZipNodestore *zns = (ZipNodestore*)ns;
223
224
    /* Ensure that the NodeId is unique by testing their presence. If the NodeId
225
     * is ns=xx;i=0, then the numeric identifier is replaced with a random
226
     * unused int32. It is ensured that the created identifiers are stable after
227
     * a server restart (assuming that Nodes are created in the same order and
228
     * with the same BrowseName). */
229
11.5M
    NodeEntry dummy;
230
11.5M
    memset(&dummy, 0, sizeof(NodeEntry));
231
11.5M
    dummy.nodeId = node->head.nodeId;
232
11.5M
    if(node->head.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC &&
233
11.5M
       node->head.nodeId.identifier.numeric == 0) {
234
643k
        NodeEntry *found;
235
643k
        UA_UInt32 mask = 0x2F;
236
643k
        pcg32_random_t rng;
237
643k
        pcg32_srandom_r(&rng, zns->size, 0);
238
1.79M
        do {
239
            /* Generate a random NodeId. Favor "easy" NodeIds.
240
             * Always above 50000. */
241
1.79M
            UA_UInt32 numId = (pcg32_random_r(&rng) & mask) + 50000;
242
243
#if SIZE_MAX <= UA_UINT32_MAX
244
            /* The compressed "immediate" representation of nodes does not
245
             * support the full range on 32bit systems. Generate smaller
246
             * identifiers as they can be stored more compactly. */
247
            if(numId >= (0x01 << 24))
248
                numId = numId % (0x01 << 24);
249
#endif
250
1.79M
            node->head.nodeId.identifier.numeric = numId;
251
252
            /* Look up the current NodeId */
253
1.79M
            dummy.nodeId.identifier.numeric = numId;
254
1.79M
            dummy.nodeIdHash = UA_NodeId_hash(&node->head.nodeId);
255
1.79M
            found = ZIP_FIND(NodeTree, &zns->root, &dummy);
256
257
1.79M
            if(found) {
258
                /* Reseed the rng using the browseName of the existing node.
259
                 * This ensures that different information models end up with
260
                 * different NodeId sequences, but still stable after a
261
                 * restart. */
262
1.14M
                UA_NodeHead *nh = (UA_NodeHead*)&found->nodeId;
263
1.14M
                pcg32_srandom_r(&rng, rng.state, UA_QualifiedName_hash(&nh->browseName));
264
265
                /* Make the mask less strict when the NodeId already exists */
266
1.14M
                mask = (mask << 1) | 0x01;
267
1.14M
            }
268
1.79M
        } while(found);
269
10.9M
    } else {
270
10.9M
        dummy.nodeIdHash = UA_NodeId_hash(&node->head.nodeId);
271
10.9M
        if(ZIP_FIND(NodeTree, &zns->root, &dummy)) { /* The nodeid exists */
272
0
            deleteEntry(entry);
273
0
            return UA_STATUSCODE_BADNODEIDEXISTS;
274
0
        }
275
10.9M
    }
276
277
    /* Copy the NodeId */
278
11.5M
    if(addedNodeId) {
279
11.5M
        UA_StatusCode retval = UA_NodeId_copy(&node->head.nodeId, addedNodeId);
280
11.5M
        if(retval != UA_STATUSCODE_GOOD) {
281
0
            deleteEntry(entry);
282
0
            return retval;
283
0
        }
284
11.5M
    }
285
286
    /* For new ReferencetypeNodes add to the index map */
287
11.5M
    if(node->head.nodeClass == UA_NODECLASS_REFERENCETYPE) {
288
364k
        UA_ReferenceTypeNode *refNode = &node->referenceTypeNode;
289
364k
        if(zns->referenceTypeCounter >= UA_REFERENCETYPESET_MAX) {
290
0
            deleteEntry(entry);
291
0
            return UA_STATUSCODE_BADINTERNALERROR;
292
0
        }
293
294
364k
        UA_StatusCode retval =
295
364k
            UA_NodeId_copy(&node->head.nodeId,
296
364k
                           &zns->referenceTypeIds[zns->referenceTypeCounter]);
297
364k
        if(retval != UA_STATUSCODE_GOOD) {
298
0
            deleteEntry(entry);
299
0
            return UA_STATUSCODE_BADINTERNALERROR;
300
0
        }
301
302
        /* Assign the ReferenceTypeIndex to the new ReferenceTypeNode */
303
364k
        refNode->referenceTypeIndex = zns->referenceTypeCounter;
304
364k
        refNode->subTypes = UA_REFTYPESET(zns->referenceTypeCounter);
305
364k
        zns->referenceTypeCounter++;
306
364k
    }
307
308
    /* Insert the node */
309
11.5M
    entry->nodeIdHash = dummy.nodeIdHash;
310
11.5M
    ZIP_INSERT(NodeTree, &zns->root, entry);
311
11.5M
    zns->size++;
312
11.5M
    return UA_STATUSCODE_GOOD;
313
11.5M
}
314
315
static UA_StatusCode
316
0
zipNsReplaceNode(UA_Nodestore *ns, UA_Node *node) {
317
    /* Find the node (the mask and refs are not evaluated yet by the plugin)*/
318
0
    const UA_Node *oldNode =
319
0
        zipNsGetNode(ns, &node->head.nodeId, UA_NODEATTRIBUTESMASK_ALL,
320
0
                     UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
321
0
    if(!oldNode) {
322
0
        deleteEntry(container_of(node, NodeEntry, nodeId));
323
0
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
324
0
    }
325
326
    /* Test if the copy is current */
327
0
    NodeEntry *entry = container_of(node, NodeEntry, nodeId);
328
0
    NodeEntry *oldEntry = container_of(oldNode, NodeEntry, nodeId);
329
0
    if(oldEntry != entry->orig) {
330
        /* The node was already updated since the copy was made */
331
0
        deleteEntry(entry);
332
0
        zipNsReleaseNode(NULL, oldNode);
333
0
        return UA_STATUSCODE_BADINTERNALERROR;
334
0
    }
335
336
    /* Replace */
337
0
    ZipNodestore *zns = (ZipNodestore*)ns;
338
0
    ZIP_REMOVE(NodeTree, &zns->root, oldEntry);
339
0
    entry->nodeIdHash = oldEntry->nodeIdHash;
340
0
    ZIP_INSERT(NodeTree, &zns->root, entry);
341
0
    oldEntry->deleted = true;
342
343
0
    zipNsReleaseNode(NULL, oldNode);
344
0
    return UA_STATUSCODE_GOOD;
345
0
}
346
347
static UA_StatusCode
348
343k
zipNsRemoveNode(UA_Nodestore *ns, const UA_NodeId *nodeId) {
349
343k
    ZipNodestore *zns = (ZipNodestore*)ns;
350
343k
    NodeEntry dummy;
351
343k
    dummy.nodeIdHash = UA_NodeId_hash(nodeId);
352
343k
    dummy.nodeId = *nodeId;
353
343k
    NodeEntry *entry = ZIP_FIND(NodeTree, &zns->root, &dummy);
354
343k
    if(!entry)
355
0
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
356
343k
    ZIP_REMOVE(NodeTree, &zns->root, entry);
357
343k
    zns->size--;
358
343k
    entry->deleted = true;
359
343k
    cleanupEntry(entry);
360
343k
    return UA_STATUSCODE_GOOD;
361
343k
}
362
363
static const UA_NodeId *
364
7.71M
zipNsGetReferenceTypeId(UA_Nodestore *ns, UA_Byte refTypeIndex) {
365
7.71M
    ZipNodestore *zns = (ZipNodestore*)ns;
366
7.71M
    if(refTypeIndex >= zns->referenceTypeCounter)
367
0
        return NULL;
368
7.71M
    return &zns->referenceTypeIds[refTypeIndex];
369
7.71M
}
370
371
struct VisitorData {
372
    UA_NodestoreVisitor visitor;
373
    void *visitorContext;
374
};
375
376
static void *
377
0
nodeVisitor(void *data, NodeEntry *entry) {
378
0
    struct VisitorData *d = (struct VisitorData*)data;
379
0
    d->visitor(d->visitorContext, (UA_Node*)&entry->nodeId);
380
0
    return NULL;
381
0
}
382
383
static void
384
zipNsIterate(UA_Nodestore *ns, UA_NodestoreVisitor visitor,
385
0
             void *visitorCtx) {
386
0
    struct VisitorData d;
387
0
    d.visitor = visitor;
388
0
    d.visitorContext = visitorCtx;
389
0
    ZipNodestore *zns = (ZipNodestore*)ns;
390
0
    ZIP_ITER(NodeTree, &zns->root, nodeVisitor, &d);
391
0
}
392
393
static void *
394
11.2M
deleteNodeVisitor(void *data, NodeEntry *entry) {
395
11.2M
    deleteEntry(entry);
396
11.2M
    return NULL;
397
11.2M
}
398
399
/***********************/
400
/* Nodestore Lifecycle */
401
/***********************/
402
403
static void
404
13.5k
zipNsFree(UA_Nodestore *ns) {
405
13.5k
    ZipNodestore *zns = (ZipNodestore*)ns;
406
13.5k
    ZIP_ITER(NodeTree, &zns->root, deleteNodeVisitor, NULL);
407
408
    /* Clean up the ReferenceTypes index array */
409
377k
    for(size_t i = 0; i < zns->referenceTypeCounter; i++)
410
364k
        UA_NodeId_clear(&zns->referenceTypeIds[i]);
411
412
13.5k
    UA_free(zns);
413
13.5k
}
414
415
UA_Nodestore *
416
13.5k
UA_Nodestore_ZipTree(void) {
417
    /* Allocate and initialize the context */
418
13.5k
    ZipNodestore *zns = (ZipNodestore*)UA_calloc(1, sizeof(ZipNodestore));
419
13.5k
    if(!zns)
420
0
        return NULL;
421
422
13.5k
    ZIP_INIT(&zns->root);
423
13.5k
    zns->referenceTypeCounter = 0;
424
425
    /* Populate the nodestore */
426
13.5k
    zns->ns.free = zipNsFree;
427
13.5k
    zns->ns.newNode = zipNsNewNode;
428
13.5k
    zns->ns.deleteNode = zipNsDeleteNode;
429
13.5k
    zns->ns.getNode = zipNsGetNode;
430
13.5k
    zns->ns.getNodeFromPtr = zipNsGetNodeFromPtr;
431
13.5k
    zns->ns.releaseNode = zipNsReleaseNode;
432
13.5k
    zns->ns.getNodeCopy = zipNsGetNodeCopy;
433
13.5k
    zns->ns.insertNode = zipNsInsertNode;
434
13.5k
    zns->ns.replaceNode = zipNsReplaceNode;
435
13.5k
    zns->ns.removeNode = zipNsRemoveNode;
436
13.5k
    zns->ns.getReferenceTypeId = zipNsGetReferenceTypeId;
437
13.5k
    zns->ns.iterate = zipNsIterate;
438
439
    /* All nodes are stored in RAM. Changes are made in-situ. GetEditNode is
440
     * identical to GetNode -- but the Node pointer is non-const. */
441
13.5k
    zns->ns.getEditNode =
442
13.5k
        (UA_Node * (*)(UA_Nodestore *ns, const UA_NodeId *nodeId,
443
13.5k
                       UA_UInt32 attributeMask,
444
13.5k
                       UA_ReferenceTypeSet references,
445
13.5k
                       UA_BrowseDirection referenceDirections))zipNsGetNode;
446
13.5k
    zns->ns.getEditNodeFromPtr =
447
13.5k
        (UA_Node * (*)(UA_Nodestore *ns, UA_NodePointer ptr,
448
13.5k
                       UA_UInt32 attributeMask,
449
13.5k
                       UA_ReferenceTypeSet references,
450
13.5k
                       UA_BrowseDirection referenceDirections))zipNsGetNodeFromPtr;
451
452
13.5k
    return &zns->ns;
453
13.5k
}