/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 | 21.9M | (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 | 244M | cmpNodeId(const void *a, const void *b) { |
37 | 244M | const NodeEntry *aa = (const NodeEntry*)a; |
38 | 244M | const NodeEntry *bb = (const NodeEntry*)b; |
39 | | |
40 | | /* Compare hash */ |
41 | 244M | if(aa->nodeIdHash < bb->nodeIdHash) |
42 | 104M | return ZIP_CMP_LESS; |
43 | 139M | if(aa->nodeIdHash > bb->nodeIdHash) |
44 | 117M | return ZIP_CMP_MORE; |
45 | | |
46 | | /* Compore nodes in detail */ |
47 | 21.5M | return (enum ZIP_CMP)UA_NodeId_order(&aa->nodeId, &bb->nodeId); |
48 | 139M | } |
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 | 457k | 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) |
Unexecuted instantiation: ua_nodestore_ziptree.c:NodeTree_ZIP_REMOVE |
65 | | |
66 | | static NodeEntry * |
67 | 456k | newEntry(UA_NodeClass nodeClass) { |
68 | 456k | size_t size = sizeof(NodeEntry) - sizeof(UA_NodeId); |
69 | 456k | switch(nodeClass) { |
70 | 38.3k | case UA_NODECLASS_OBJECT: |
71 | 38.3k | size += sizeof(UA_ObjectNode); |
72 | 38.3k | break; |
73 | 306k | case UA_NODECLASS_VARIABLE: |
74 | 306k | size += sizeof(UA_VariableNode); |
75 | 306k | break; |
76 | 18.9k | case UA_NODECLASS_METHOD: |
77 | 18.9k | size += sizeof(UA_MethodNode); |
78 | 18.9k | break; |
79 | 28.1k | case UA_NODECLASS_OBJECTTYPE: |
80 | 28.1k | size += sizeof(UA_ObjectTypeNode); |
81 | 28.1k | break; |
82 | 13.2k | case UA_NODECLASS_VARIABLETYPE: |
83 | 13.2k | size += sizeof(UA_VariableTypeNode); |
84 | 13.2k | break; |
85 | 14.8k | case UA_NODECLASS_REFERENCETYPE: |
86 | 14.8k | size += sizeof(UA_ReferenceTypeNode); |
87 | 14.8k | break; |
88 | 37.3k | case UA_NODECLASS_DATATYPE: |
89 | 37.3k | size += sizeof(UA_DataTypeNode); |
90 | 37.3k | break; |
91 | 0 | case UA_NODECLASS_VIEW: |
92 | 0 | size += sizeof(UA_ViewNode); |
93 | 0 | break; |
94 | 0 | default: |
95 | 0 | return NULL; |
96 | 456k | } |
97 | 456k | NodeEntry *entry = (NodeEntry*)UA_calloc(1, size); |
98 | 456k | if(!entry) |
99 | 0 | return NULL; |
100 | 456k | UA_Node *node = (UA_Node*)&entry->nodeId; |
101 | 456k | node->head.nodeClass = nodeClass; |
102 | 456k | return entry; |
103 | 456k | } |
104 | | |
105 | | static void |
106 | 456k | deleteEntry(NodeEntry *entry) { |
107 | 456k | UA_Node_clear((UA_Node*)&entry->nodeId); |
108 | 456k | UA_free(entry); |
109 | 456k | } |
110 | | |
111 | | static void |
112 | 21.5M | cleanupEntry(NodeEntry *entry) { |
113 | 21.5M | if(entry->refCount > 0) |
114 | 5.64M | return; |
115 | 15.8M | if(entry->deleted) { |
116 | 0 | deleteEntry(entry); |
117 | 0 | return; |
118 | 0 | } |
119 | 15.8M | UA_NodeHead *head = (UA_NodeHead*)&entry->nodeId; |
120 | 53.2M | for(size_t i = 0; i < head->referencesSize; i++) { |
121 | 37.3M | UA_NodeReferenceKind *rk = &head->references[i]; |
122 | 37.3M | if(rk->targetsSize > 16 && !rk->hasRefTree) |
123 | 6.64k | UA_NodeReferenceKind_switch(rk); |
124 | 37.3M | } |
125 | 15.8M | } |
126 | | |
127 | | /***********************/ |
128 | | /* Interface functions */ |
129 | | /***********************/ |
130 | | |
131 | | /* Not yet inserted into the ZipContext */ |
132 | | static UA_Node * |
133 | 443k | zipNsNewNode(UA_Nodestore *_, UA_NodeClass nodeClass) { |
134 | 443k | NodeEntry *entry = newEntry(nodeClass); |
135 | 443k | if(!entry) |
136 | 0 | return NULL; |
137 | 443k | return (UA_Node*)&entry->nodeId; |
138 | 443k | } |
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 | 21.5M | UA_BrowseDirection referenceDirections) { |
151 | 21.5M | NodeEntry dummy; |
152 | 21.5M | dummy.nodeIdHash = UA_NodeId_hash(nodeId); |
153 | 21.5M | dummy.nodeId = *nodeId; |
154 | 21.5M | ZipNodestore *zns = (ZipNodestore*)ns; |
155 | 21.5M | NodeEntry *entry = ZIP_FIND(NodeTree, &zns->root, &dummy); |
156 | 21.5M | if(!entry) |
157 | 18.3k | return NULL; |
158 | 21.5M | ++entry->refCount; |
159 | 21.5M | return (const UA_Node*)&entry->nodeId; |
160 | 21.5M | } |
161 | | |
162 | | static const UA_Node * |
163 | | zipNsGetNodeFromPtr(UA_Nodestore *ns, UA_NodePointer ptr, |
164 | | UA_UInt32 attributeMask, |
165 | | UA_ReferenceTypeSet references, |
166 | 8.74M | UA_BrowseDirection referenceDirections) { |
167 | 8.74M | if(!UA_NodePointer_isLocal(ptr)) |
168 | 0 | return NULL; |
169 | 8.74M | UA_NodeId id = UA_NodePointer_toNodeId(ptr); |
170 | 8.74M | return zipNsGetNode(ns, &id, attributeMask, |
171 | 8.74M | references, referenceDirections); |
172 | 8.74M | } |
173 | | |
174 | | static void |
175 | 21.5M | zipNsReleaseNode(UA_Nodestore *_, const UA_Node *node) { |
176 | 21.5M | if(!node) |
177 | 0 | return; |
178 | 21.5M | NodeEntry *entry = container_of(node, NodeEntry, nodeId); |
179 | 21.5M | UA_assert(entry->refCount > 0); |
180 | 21.5M | --entry->refCount; |
181 | 21.5M | cleanupEntry(entry); |
182 | 21.5M | } |
183 | | |
184 | | static UA_StatusCode |
185 | | zipNsGetNodeCopy(UA_Nodestore *ns, const UA_NodeId *nodeId, |
186 | 13.7k | 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 | 13.7k | const UA_Node *node = |
190 | 13.7k | zipNsGetNode(ns, nodeId, UA_NODEATTRIBUTESMASK_ALL, |
191 | 13.7k | UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); |
192 | 13.7k | if(!node) |
193 | 0 | return UA_STATUSCODE_BADNODEIDUNKNOWN; |
194 | | |
195 | | /* Create the new entry */ |
196 | 13.7k | NodeEntry *ne = newEntry(node->head.nodeClass); |
197 | 13.7k | if(!ne) { |
198 | 0 | zipNsReleaseNode(ns, node); |
199 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
200 | 0 | } |
201 | | |
202 | | /* Copy the node content */ |
203 | 13.7k | UA_Node *nnode = (UA_Node*)&ne->nodeId; |
204 | 13.7k | UA_StatusCode retval = UA_Node_copy(node, nnode); |
205 | 13.7k | zipNsReleaseNode(NULL, node); |
206 | 13.7k | if(retval != UA_STATUSCODE_GOOD) { |
207 | 0 | deleteEntry(ne); |
208 | 0 | return retval; |
209 | 0 | } |
210 | | |
211 | 13.7k | ne->orig = container_of(node, NodeEntry, nodeId); |
212 | 13.7k | *outNode = nnode; |
213 | 13.7k | return UA_STATUSCODE_GOOD; |
214 | 13.7k | } |
215 | | |
216 | | static UA_StatusCode |
217 | 456k | zipNsInsertNode(UA_Nodestore *ns, UA_Node *node, UA_NodeId *addedNodeId) { |
218 | 456k | NodeEntry *entry = container_of(node, NodeEntry, nodeId); |
219 | 456k | 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 | 456k | NodeEntry dummy; |
227 | 456k | memset(&dummy, 0, sizeof(NodeEntry)); |
228 | 456k | dummy.nodeId = node->head.nodeId; |
229 | 456k | if(node->head.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC && |
230 | 456k | node->head.nodeId.identifier.numeric == 0) { |
231 | 13.7k | NodeEntry *found; |
232 | 13.7k | UA_UInt32 mask = 0x2F; |
233 | 13.7k | pcg32_random_t rng; |
234 | 13.7k | pcg32_srandom_r(&rng, zns->size, 0); |
235 | 18.3k | do { |
236 | | /* Generate a random NodeId. Favor "easy" NodeIds. |
237 | | * Always above 50000. */ |
238 | 18.3k | 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 | 18.3k | node->head.nodeId.identifier.numeric = numId; |
248 | | |
249 | | /* Look up the current NodeId */ |
250 | 18.3k | dummy.nodeId.identifier.numeric = numId; |
251 | 18.3k | dummy.nodeIdHash = UA_NodeId_hash(&node->head.nodeId); |
252 | 18.3k | found = ZIP_FIND(NodeTree, &zns->root, &dummy); |
253 | | |
254 | 18.3k | 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 | 4.59k | UA_NodeHead *nh = (UA_NodeHead*)&found->nodeId; |
260 | 4.59k | pcg32_srandom_r(&rng, rng.state, UA_QualifiedName_hash(&nh->browseName)); |
261 | | |
262 | | /* Make the mask less strict when the NodeId already exists */ |
263 | 4.59k | mask = (mask << 1) | 0x01; |
264 | 4.59k | } |
265 | 18.3k | } while(found); |
266 | 443k | } else { |
267 | 443k | dummy.nodeIdHash = UA_NodeId_hash(&node->head.nodeId); |
268 | 443k | if(ZIP_FIND(NodeTree, &zns->root, &dummy)) { /* The nodeid exists */ |
269 | 0 | deleteEntry(entry); |
270 | 0 | return UA_STATUSCODE_BADNODEIDEXISTS; |
271 | 0 | } |
272 | 443k | } |
273 | | |
274 | | /* Copy the NodeId */ |
275 | 456k | if(addedNodeId) { |
276 | 456k | UA_StatusCode retval = UA_NodeId_copy(&node->head.nodeId, addedNodeId); |
277 | 456k | if(retval != UA_STATUSCODE_GOOD) { |
278 | 0 | deleteEntry(entry); |
279 | 0 | return retval; |
280 | 0 | } |
281 | 456k | } |
282 | | |
283 | | /* For new ReferencetypeNodes add to the index map */ |
284 | 456k | if(node->head.nodeClass == UA_NODECLASS_REFERENCETYPE) { |
285 | 14.8k | UA_ReferenceTypeNode *refNode = &node->referenceTypeNode; |
286 | 14.8k | if(zns->referenceTypeCounter >= UA_REFERENCETYPESET_MAX) { |
287 | 0 | deleteEntry(entry); |
288 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
289 | 0 | } |
290 | | |
291 | 14.8k | UA_StatusCode retval = |
292 | 14.8k | UA_NodeId_copy(&node->head.nodeId, |
293 | 14.8k | &zns->referenceTypeIds[zns->referenceTypeCounter]); |
294 | 14.8k | 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 | 14.8k | refNode->referenceTypeIndex = zns->referenceTypeCounter; |
301 | 14.8k | refNode->subTypes = UA_REFTYPESET(zns->referenceTypeCounter); |
302 | 14.8k | zns->referenceTypeCounter++; |
303 | 14.8k | } |
304 | | |
305 | | /* Insert the node */ |
306 | 456k | entry->nodeIdHash = dummy.nodeIdHash; |
307 | 456k | ZIP_INSERT(NodeTree, &zns->root, entry); |
308 | 456k | zns->size++; |
309 | 456k | return UA_STATUSCODE_GOOD; |
310 | 456k | } |
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 | | /* Replace */ |
334 | 0 | ZipNodestore *zns = (ZipNodestore*)ns; |
335 | 0 | ZIP_REMOVE(NodeTree, &zns->root, oldEntry); |
336 | 0 | entry->nodeIdHash = oldEntry->nodeIdHash; |
337 | 0 | ZIP_INSERT(NodeTree, &zns->root, entry); |
338 | 0 | oldEntry->deleted = true; |
339 | |
|
340 | 0 | zipNsReleaseNode(NULL, oldNode); |
341 | 0 | return UA_STATUSCODE_GOOD; |
342 | 0 | } |
343 | | |
344 | | static UA_StatusCode |
345 | 0 | zipNsRemoveNode(UA_Nodestore *ns, const UA_NodeId *nodeId) { |
346 | 0 | ZipNodestore *zns = (ZipNodestore*)ns; |
347 | 0 | NodeEntry dummy; |
348 | 0 | dummy.nodeIdHash = UA_NodeId_hash(nodeId); |
349 | 0 | dummy.nodeId = *nodeId; |
350 | 0 | NodeEntry *entry = ZIP_FIND(NodeTree, &zns->root, &dummy); |
351 | 0 | if(!entry) |
352 | 0 | return UA_STATUSCODE_BADNODEIDUNKNOWN; |
353 | 0 | ZIP_REMOVE(NodeTree, &zns->root, entry); |
354 | 0 | zns->size--; |
355 | 0 | entry->deleted = true; |
356 | 0 | cleanupEntry(entry); |
357 | 0 | return UA_STATUSCODE_GOOD; |
358 | 0 | } |
359 | | |
360 | | static const UA_NodeId * |
361 | 249k | zipNsGetReferenceTypeId(UA_Nodestore *ns, UA_Byte refTypeIndex) { |
362 | 249k | ZipNodestore *zns = (ZipNodestore*)ns; |
363 | 249k | if(refTypeIndex >= zns->referenceTypeCounter) |
364 | 0 | return NULL; |
365 | 249k | return &zns->referenceTypeIds[refTypeIndex]; |
366 | 249k | } |
367 | | |
368 | | struct VisitorData { |
369 | | UA_NodestoreVisitor visitor; |
370 | | void *visitorContext; |
371 | | }; |
372 | | |
373 | | static void * |
374 | 0 | nodeVisitor(void *data, NodeEntry *entry) { |
375 | 0 | struct VisitorData *d = (struct VisitorData*)data; |
376 | 0 | d->visitor(d->visitorContext, (UA_Node*)&entry->nodeId); |
377 | 0 | return NULL; |
378 | 0 | } |
379 | | |
380 | | static void |
381 | | zipNsIterate(UA_Nodestore *ns, UA_NodestoreVisitor visitor, |
382 | 0 | void *visitorCtx) { |
383 | 0 | struct VisitorData d; |
384 | 0 | d.visitor = visitor; |
385 | 0 | d.visitorContext = visitorCtx; |
386 | 0 | ZipNodestore *zns = (ZipNodestore*)ns; |
387 | 0 | ZIP_ITER(NodeTree, &zns->root, nodeVisitor, &d); |
388 | 0 | } |
389 | | |
390 | | static void * |
391 | 456k | deleteNodeVisitor(void *data, NodeEntry *entry) { |
392 | 456k | deleteEntry(entry); |
393 | 456k | return NULL; |
394 | 456k | } |
395 | | |
396 | | /***********************/ |
397 | | /* Nodestore Lifecycle */ |
398 | | /***********************/ |
399 | | |
400 | | static void |
401 | 511 | zipNsFree(UA_Nodestore *ns) { |
402 | 511 | ZipNodestore *zns = (ZipNodestore*)ns; |
403 | 511 | ZIP_ITER(NodeTree, &zns->root, deleteNodeVisitor, NULL); |
404 | | |
405 | | /* Clean up the ReferenceTypes index array */ |
406 | 15.3k | for(size_t i = 0; i < zns->referenceTypeCounter; i++) |
407 | 14.8k | UA_NodeId_clear(&zns->referenceTypeIds[i]); |
408 | | |
409 | 511 | UA_free(zns); |
410 | 511 | } |
411 | | |
412 | | UA_Nodestore * |
413 | 511 | UA_Nodestore_ZipTree(void) { |
414 | | /* Allocate and initialize the context */ |
415 | 511 | ZipNodestore *zns = (ZipNodestore*)UA_calloc(1, sizeof(ZipNodestore)); |
416 | 511 | if(!zns) |
417 | 0 | return NULL; |
418 | | |
419 | 511 | ZIP_INIT(&zns->root); |
420 | 511 | zns->referenceTypeCounter = 0; |
421 | | |
422 | | /* Populate the nodestore */ |
423 | 511 | zns->ns.free = zipNsFree; |
424 | 511 | zns->ns.newNode = zipNsNewNode; |
425 | 511 | zns->ns.deleteNode = zipNsDeleteNode; |
426 | 511 | zns->ns.getNode = zipNsGetNode; |
427 | 511 | zns->ns.getNodeFromPtr = zipNsGetNodeFromPtr; |
428 | 511 | zns->ns.releaseNode = zipNsReleaseNode; |
429 | 511 | zns->ns.getNodeCopy = zipNsGetNodeCopy; |
430 | 511 | zns->ns.insertNode = zipNsInsertNode; |
431 | 511 | zns->ns.replaceNode = zipNsReplaceNode; |
432 | 511 | zns->ns.removeNode = zipNsRemoveNode; |
433 | 511 | zns->ns.getReferenceTypeId = zipNsGetReferenceTypeId; |
434 | 511 | zns->ns.iterate = zipNsIterate; |
435 | | |
436 | | /* All nodes are stored in RAM. Changes are made in-situ. GetEditNode is |
437 | | * identical to GetNode -- but the Node pointer is non-const. */ |
438 | 511 | zns->ns.getEditNode = |
439 | 511 | (UA_Node * (*)(UA_Nodestore *ns, const UA_NodeId *nodeId, |
440 | 511 | UA_UInt32 attributeMask, |
441 | 511 | UA_ReferenceTypeSet references, |
442 | 511 | UA_BrowseDirection referenceDirections))zipNsGetNode; |
443 | 511 | zns->ns.getEditNodeFromPtr = |
444 | 511 | (UA_Node * (*)(UA_Nodestore *ns, UA_NodePointer ptr, |
445 | 511 | UA_UInt32 attributeMask, |
446 | 511 | UA_ReferenceTypeSet references, |
447 | 511 | UA_BrowseDirection referenceDirections))zipNsGetNodeFromPtr; |
448 | | |
449 | 511 | return &zns->ns; |
450 | 511 | } |