/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 | } |