ua_services_attribute.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. #include "ua_server_internal.h"
  2. #include "ua_types_generated.h"
  3. #include "ua_services.h"
  4. #include "ua_statuscodes.h"
  5. #include "ua_nodestore.h"
  6. #include "ua_util.h"
  7. #include "stdio.h"
  8. #define CHECK_NODECLASS(CLASS) \
  9. if(!(node->nodeClass & (CLASS))) { \
  10. v->hasStatus = UA_TRUE; \
  11. v->status = UA_STATUSCODE_BADNOTREADABLE; \
  12. break; \
  13. }
  14. /** Reads a single attribute from a node in the nodestore. */
  15. static void readValue(UA_Server *server, const UA_ReadValueId *id, UA_DataValue *v) {
  16. UA_Node const *node = UA_NodeStore_get(server->nodestore, &(id->nodeId));
  17. if(!node) {
  18. v->hasStatus = UA_TRUE;
  19. v->status = UA_STATUSCODE_BADNODEIDUNKNOWN;
  20. return;
  21. }
  22. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  23. switch(id->attributeId) {
  24. case UA_ATTRIBUTEID_NODEID:
  25. v->hasVariant = UA_TRUE;
  26. retval |= UA_Variant_copySetValue(&v->value, &node->nodeId, UA_TYPES_NODEID);
  27. break;
  28. case UA_ATTRIBUTEID_NODECLASS:
  29. v->hasVariant = UA_TRUE;
  30. retval |= UA_Variant_copySetValue(&v->value, &node->nodeClass, UA_TYPES_INT32);
  31. break;
  32. case UA_ATTRIBUTEID_BROWSENAME:
  33. v->hasVariant = UA_TRUE;
  34. retval |= UA_Variant_copySetValue(&v->value, &node->browseName, UA_TYPES_QUALIFIEDNAME);
  35. break;
  36. case UA_ATTRIBUTEID_DISPLAYNAME:
  37. retval |= UA_Variant_copySetValue(&v->value, &node->displayName, UA_TYPES_LOCALIZEDTEXT);
  38. if(retval == UA_STATUSCODE_GOOD)
  39. v->hasVariant = UA_TRUE;
  40. break;
  41. case UA_ATTRIBUTEID_DESCRIPTION:
  42. v->hasVariant = UA_TRUE;
  43. retval |= UA_Variant_copySetValue(&v->value, &node->description, UA_TYPES_LOCALIZEDTEXT);
  44. break;
  45. case UA_ATTRIBUTEID_WRITEMASK:
  46. v->hasVariant = UA_TRUE;
  47. retval |= UA_Variant_copySetValue(&v->value, &node->writeMask, UA_TYPES_UINT32);
  48. break;
  49. case UA_ATTRIBUTEID_USERWRITEMASK:
  50. v->hasVariant = UA_TRUE;
  51. retval |= UA_Variant_copySetValue(&v->value, &node->userWriteMask, UA_TYPES_UINT32);
  52. break;
  53. case UA_ATTRIBUTEID_ISABSTRACT:
  54. CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE | UA_NODECLASS_OBJECTTYPE | UA_NODECLASS_VARIABLETYPE |
  55. UA_NODECLASS_DATATYPE);
  56. v->hasVariant = UA_TRUE;
  57. retval |= UA_Variant_copySetValue(&v->value, &((const UA_ReferenceTypeNode *)node)->isAbstract,
  58. UA_TYPES_BOOLEAN);
  59. break;
  60. case UA_ATTRIBUTEID_SYMMETRIC:
  61. CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE);
  62. v->hasVariant = UA_TRUE;
  63. retval |= UA_Variant_copySetValue(&v->value, &((const UA_ReferenceTypeNode *)node)->symmetric,
  64. UA_TYPES_BOOLEAN);
  65. break;
  66. case UA_ATTRIBUTEID_INVERSENAME:
  67. CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE);
  68. v->hasVariant = UA_TRUE;
  69. retval |= UA_Variant_copySetValue(&v->value, &((const UA_ReferenceTypeNode *)node)->inverseName,
  70. UA_TYPES_LOCALIZEDTEXT);
  71. break;
  72. case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
  73. CHECK_NODECLASS(UA_NODECLASS_VIEW);
  74. v->hasVariant = UA_TRUE;
  75. retval |= UA_Variant_copySetValue(&v->value, &((const UA_ViewNode *)node)->containsNoLoops,
  76. UA_TYPES_BOOLEAN);
  77. break;
  78. case UA_ATTRIBUTEID_EVENTNOTIFIER:
  79. CHECK_NODECLASS(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT);
  80. v->hasVariant = UA_TRUE;
  81. retval |= UA_Variant_copySetValue(&v->value, &((const UA_ViewNode *)node)->eventNotifier,
  82. UA_TYPES_BYTE);
  83. break;
  84. case UA_ATTRIBUTEID_VALUE:
  85. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  86. retval = UA_Variant_copy(&((const UA_VariableNode *)node)->value, &v->value);
  87. if(retval == UA_STATUSCODE_GOOD){
  88. v->hasVariant = UA_TRUE;
  89. v->hasSourceTimestamp = UA_TRUE;
  90. v->sourceTimestamp = UA_DateTime_now();
  91. v->hasServerTimestamp = UA_TRUE;
  92. v->serverTimestamp = UA_DateTime_now();
  93. }
  94. break;
  95. case UA_ATTRIBUTEID_DATATYPE:
  96. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  97. v->hasVariant = UA_TRUE;
  98. retval |= UA_Variant_copySetValue(&v->value, &((const UA_VariableTypeNode *)node)->value.type->typeId,
  99. UA_TYPES_NODEID);
  100. break;
  101. case UA_ATTRIBUTEID_VALUERANK:
  102. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  103. v->hasVariant = UA_TRUE;
  104. retval |= UA_Variant_copySetValue(&v->value, &((const UA_VariableTypeNode *)node)->valueRank,
  105. UA_TYPES_INT32);
  106. break;
  107. case UA_ATTRIBUTEID_ARRAYDIMENSIONS:
  108. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  109. {
  110. const UA_VariantData *data = UA_NULL;
  111. UA_VariantData datasourceData;
  112. const UA_VariableNode *vn = (const UA_VariableNode *)node;
  113. if(vn->value.storageType == UA_VARIANT_DATA || vn->value.storageType == UA_VARIANT_DATA_NODELETE)
  114. data = &vn->value.storage.data;
  115. else {
  116. if(vn->value.storage.datasource.read == UA_NULL || (retval = vn->value.storage.datasource.read(vn->value.storage.datasource.handle,
  117. &datasourceData)) != UA_STATUSCODE_GOOD)
  118. break;
  119. data = &datasourceData;
  120. }
  121. retval = UA_Variant_copySetArray(&v->value, data->arrayDimensions, data->arrayDimensionsSize,
  122. UA_TYPES_INT32);
  123. if(retval == UA_STATUSCODE_GOOD)
  124. v->hasVariant = UA_TRUE;
  125. if(vn->value.storageType == UA_VARIANT_DATASOURCE && vn->value.storage.datasource.release != UA_NULL)
  126. vn->value.storage.datasource.release(vn->value.storage.datasource.handle, &datasourceData);
  127. }
  128. break;
  129. case UA_ATTRIBUTEID_ACCESSLEVEL:
  130. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  131. v->hasVariant = UA_TRUE;
  132. retval |= UA_Variant_copySetValue(&v->value, &((const UA_VariableNode *)node)->accessLevel,
  133. UA_TYPES_BYTE);
  134. break;
  135. case UA_ATTRIBUTEID_USERACCESSLEVEL:
  136. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  137. v->hasVariant = UA_TRUE;
  138. retval |= UA_Variant_copySetValue(&v->value, &((const UA_VariableNode *)node)->userAccessLevel,
  139. UA_TYPES_BYTE);
  140. break;
  141. case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
  142. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  143. v->hasVariant = UA_TRUE;
  144. retval |= UA_Variant_copySetValue(&v->value, &((const UA_VariableNode *)node)->minimumSamplingInterval,
  145. UA_TYPES_DOUBLE);
  146. break;
  147. case UA_ATTRIBUTEID_HISTORIZING:
  148. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  149. v->hasVariant = UA_TRUE;
  150. retval |= UA_Variant_copySetValue(&v->value, &((const UA_VariableNode *)node)->historizing,
  151. UA_TYPES_BOOLEAN);
  152. break;
  153. case UA_ATTRIBUTEID_EXECUTABLE:
  154. CHECK_NODECLASS(UA_NODECLASS_METHOD);
  155. v->hasVariant = UA_TRUE;
  156. retval |= UA_Variant_copySetValue(&v->value, &((const UA_MethodNode *)node)->executable,
  157. UA_TYPES_BOOLEAN);
  158. break;
  159. case UA_ATTRIBUTEID_USEREXECUTABLE:
  160. CHECK_NODECLASS(UA_NODECLASS_METHOD);
  161. v->hasVariant = UA_TRUE;
  162. retval |= UA_Variant_copySetValue(&v->value, &((const UA_MethodNode *)node)->userExecutable,
  163. UA_TYPES_BOOLEAN);
  164. break;
  165. default:
  166. v->hasStatus = UA_TRUE;
  167. v->status = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
  168. break;
  169. }
  170. UA_NodeStore_release(node);
  171. if(v->hasVariant && v->value.type == UA_NULL) {
  172. printf("%i", id->attributeId);
  173. UA_assert(UA_FALSE);
  174. }
  175. if(retval != UA_STATUSCODE_GOOD) {
  176. v->hasStatus = UA_TRUE;
  177. v->status = UA_STATUSCODE_BADNOTREADABLE;
  178. }
  179. }
  180. void Service_Read(UA_Server *server, UA_Session *session, const UA_ReadRequest *request,
  181. UA_ReadResponse *response) {
  182. if(request->nodesToReadSize <= 0) {
  183. response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
  184. return;
  185. }
  186. response->results = UA_Array_new(&UA_TYPES[UA_TYPES_DATAVALUE], request->nodesToReadSize);
  187. if(!response->results) {
  188. response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
  189. return;
  190. }
  191. /* ### Begin External Namespaces */
  192. UA_Boolean *isExternal = UA_alloca(sizeof(UA_Boolean) * request->nodesToReadSize);
  193. UA_memset(isExternal, UA_FALSE, sizeof(UA_Boolean)*request->nodesToReadSize);
  194. UA_UInt32 *indices = UA_alloca(sizeof(UA_UInt32) * request->nodesToReadSize);
  195. for(UA_Int32 j = 0;j<server->externalNamespacesSize;j++) {
  196. UA_UInt32 indexSize = 0;
  197. for(UA_Int32 i = 0;i < request->nodesToReadSize;i++) {
  198. if(request->nodesToRead[i].nodeId.namespaceIndex != server->externalNamespaces[j].index)
  199. continue;
  200. isExternal[i] = UA_TRUE;
  201. indices[indexSize] = i;
  202. indexSize++;
  203. }
  204. if(indexSize == 0)
  205. continue;
  206. UA_ExternalNodeStore *ens = &server->externalNamespaces[j].externalNodeStore;
  207. ens->readNodes(ens->ensHandle, &request->requestHeader, request->nodesToRead,
  208. indices, indexSize, response->results, UA_FALSE, response->diagnosticInfos);
  209. }
  210. /* ### End External Namespaces */
  211. response->resultsSize = request->nodesToReadSize;
  212. for(UA_Int32 i = 0;i < response->resultsSize;i++) {
  213. if(!isExternal[i])
  214. readValue(server, &request->nodesToRead[i], &response->results[i]);
  215. }
  216. #ifdef EXTENSION_STATELESS
  217. if(session==&anonymousSession){
  218. /* expiry header */
  219. UA_ExtensionObject additionalHeader;
  220. UA_ExtensionObject_init(&additionalHeader);
  221. additionalHeader.encoding = UA_EXTENSIONOBJECT_ENCODINGMASK_BODYISBYTESTRING;
  222. UA_Variant variant;
  223. UA_Variant_init(&variant);
  224. variant.type = &UA_TYPES[UA_TYPES_DATETIME];
  225. variant.storage.data.arrayLength = request->nodesToReadSize;
  226. UA_DateTime* expireArray = UA_NULL;
  227. expireArray = UA_Array_new(&UA_TYPES[UA_TYPES_DATETIME], request->nodesToReadSize);
  228. variant.storage.data.dataPtr = expireArray;
  229. UA_ByteString str;
  230. UA_ByteString_init(&str);
  231. /*expires in 20 seconds*/
  232. for(UA_Int32 i = 0;i < response->resultsSize;i++) {
  233. expireArray[i] = UA_DateTime_now() + 20 * 100 * 1000 * 1000;
  234. }
  235. size_t offset = 0;
  236. str.data = UA_malloc(UA_Variant_calcSizeBinary(&variant));
  237. str.length = UA_Variant_calcSizeBinary(&variant);
  238. UA_Variant_encodeBinary(&variant, &str, &offset);
  239. additionalHeader.body = str;
  240. response->responseHeader.additionalHeader = additionalHeader;
  241. }
  242. #endif
  243. }
  244. static UA_StatusCode writeValue(UA_Server *server, UA_WriteValue *wvalue) {
  245. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  246. // we might repeat writing, e.g. when the node got replaced mid-work
  247. UA_Boolean done = UA_FALSE;
  248. while(!done) {
  249. const UA_Node *node = UA_NodeStore_get(server->nodestore, &wvalue->nodeId);
  250. if(!node)
  251. return UA_STATUSCODE_BADNODEIDUNKNOWN;
  252. UA_Node* (*newNode)(void);
  253. void (*deleteNode)(UA_Node*);
  254. UA_StatusCode (*copyNode)(const UA_Node*, UA_Node*);
  255. switch(node->nodeClass) {
  256. case UA_NODECLASS_OBJECT:
  257. newNode = (UA_Node *(*)(void))UA_ObjectNode_new;
  258. deleteNode = (void (*)(UA_Node*))UA_ObjectNode_delete;
  259. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_ObjectNode_copy;
  260. break;
  261. case UA_NODECLASS_VARIABLE:
  262. newNode = (UA_Node *(*)(void))UA_VariableNode_new;
  263. deleteNode = (void (*)(UA_Node*))UA_VariableNode_delete;
  264. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_VariableNode_copy;
  265. break;
  266. case UA_NODECLASS_METHOD:
  267. newNode = (UA_Node *(*)(void))UA_MethodNode_new;
  268. deleteNode = (void (*)(UA_Node*))UA_MethodNode_delete;
  269. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_MethodNode_copy;
  270. break;
  271. case UA_NODECLASS_OBJECTTYPE:
  272. newNode = (UA_Node *(*)(void))UA_ObjectTypeNode_new;
  273. deleteNode = (void (*)(UA_Node*))UA_ObjectTypeNode_delete;
  274. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_ObjectTypeNode_copy;
  275. break;
  276. case UA_NODECLASS_VARIABLETYPE:
  277. newNode = (UA_Node *(*)(void))UA_VariableTypeNode_new;
  278. deleteNode = (void (*)(UA_Node*))UA_VariableTypeNode_delete;
  279. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_VariableTypeNode_copy;
  280. break;
  281. case UA_NODECLASS_REFERENCETYPE:
  282. newNode = (UA_Node *(*)(void))UA_ReferenceTypeNode_new;
  283. deleteNode = (void (*)(UA_Node*))UA_ReferenceTypeNode_delete;
  284. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_ReferenceTypeNode_copy;
  285. break;
  286. case UA_NODECLASS_DATATYPE:
  287. newNode = (UA_Node *(*)(void))UA_DataTypeNode_new;
  288. deleteNode = (void (*)(UA_Node*))UA_DataTypeNode_delete;
  289. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_DataTypeNode_copy;
  290. break;
  291. case UA_NODECLASS_VIEW:
  292. newNode = (UA_Node *(*)(void))UA_ViewNode_new;
  293. deleteNode = (void (*)(UA_Node*))UA_ViewNode_delete;
  294. copyNode = (UA_StatusCode (*)(const UA_Node*, UA_Node*))UA_ViewNode_copy;
  295. break;
  296. default:
  297. UA_NodeStore_release(node);
  298. return UA_STATUSCODE_BADATTRIBUTEIDINVALID;
  299. }
  300. switch(wvalue->attributeId) {
  301. case UA_ATTRIBUTEID_NODEID:
  302. case UA_ATTRIBUTEID_NODECLASS:
  303. case UA_ATTRIBUTEID_BROWSENAME:
  304. case UA_ATTRIBUTEID_DISPLAYNAME:
  305. case UA_ATTRIBUTEID_DESCRIPTION:
  306. case UA_ATTRIBUTEID_WRITEMASK:
  307. case UA_ATTRIBUTEID_USERWRITEMASK:
  308. case UA_ATTRIBUTEID_ISABSTRACT:
  309. case UA_ATTRIBUTEID_SYMMETRIC:
  310. case UA_ATTRIBUTEID_INVERSENAME:
  311. case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
  312. case UA_ATTRIBUTEID_EVENTNOTIFIER:
  313. retval = UA_STATUSCODE_BADWRITENOTSUPPORTED;
  314. break;
  315. case UA_ATTRIBUTEID_VALUE:
  316. if((node->nodeClass != UA_NODECLASS_VARIABLE) && (node->nodeClass != UA_NODECLASS_VARIABLETYPE)) {
  317. retval = UA_STATUSCODE_BADWRITENOTSUPPORTED;
  318. break;
  319. }
  320. const UA_VariableNode *vn = (const UA_VariableNode*)node;
  321. // has the wvalue a variant of the right type?
  322. // array sizes are not checked yet..
  323. if(!wvalue->value.hasVariant || !UA_NodeId_equal(&vn->value.type->typeId, &wvalue->value.value.type->typeId)) {
  324. retval = UA_STATUSCODE_BADWRITENOTSUPPORTED;
  325. break;
  326. }
  327. if(vn->value.storageType == UA_VARIANT_DATASOURCE) {
  328. if(vn->value.storage.datasource.write != UA_NULL){
  329. retval = vn->value.storage.datasource.write(vn->value.storage.datasource.handle,
  330. &wvalue->value.value.storage.data);
  331. }else{
  332. retval = UA_STATUSCODE_BADINTERNALERROR;
  333. }
  334. done = UA_TRUE;
  335. } else {
  336. // could be a variable or variabletype node. They fit for the value.. member
  337. UA_VariableNode *newVn = (UA_VariableNode*)newNode();
  338. if(!newVn) {
  339. retval = UA_STATUSCODE_BADOUTOFMEMORY;
  340. break;
  341. }
  342. retval = copyNode((const UA_Node*)vn, (UA_Node*)newVn);
  343. if(retval != UA_STATUSCODE_GOOD) {
  344. deleteNode((UA_Node*)newVn);
  345. break;
  346. }
  347. retval = UA_Variant_copy(&wvalue->value.value, &newVn->value);
  348. if(retval != UA_STATUSCODE_GOOD) {
  349. deleteNode((UA_Node*)newVn);
  350. break;
  351. }
  352. if(UA_NodeStore_replace(server->nodestore,node,(UA_Node*)newVn,UA_NULL) == UA_STATUSCODE_GOOD)
  353. done = UA_TRUE;
  354. else
  355. deleteNode((UA_Node*)newVn);
  356. }
  357. break;
  358. case UA_ATTRIBUTEID_DATATYPE:
  359. case UA_ATTRIBUTEID_VALUERANK:
  360. case UA_ATTRIBUTEID_ARRAYDIMENSIONS:
  361. case UA_ATTRIBUTEID_ACCESSLEVEL:
  362. case UA_ATTRIBUTEID_USERACCESSLEVEL:
  363. case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
  364. case UA_ATTRIBUTEID_HISTORIZING:
  365. case UA_ATTRIBUTEID_EXECUTABLE:
  366. case UA_ATTRIBUTEID_USEREXECUTABLE:
  367. retval = UA_STATUSCODE_BADWRITENOTSUPPORTED;
  368. break;
  369. default:
  370. retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
  371. break;
  372. }
  373. if(retval != UA_STATUSCODE_GOOD)
  374. break;
  375. UA_NodeStore_release(node);
  376. }
  377. return retval;
  378. }
  379. void Service_Write(UA_Server *server, UA_Session *session,
  380. const UA_WriteRequest *request, UA_WriteResponse *response) {
  381. UA_assert(server != UA_NULL && session != UA_NULL && request != UA_NULL && response != UA_NULL);
  382. response->results = UA_Array_new(&UA_TYPES[UA_TYPES_STATUSCODE], request->nodesToWriteSize);
  383. if(!response->results) {
  384. response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
  385. return;
  386. }
  387. /* ### Begin External Namespaces */
  388. UA_Boolean *isExternal = UA_alloca(sizeof(UA_Boolean) * request->nodesToWriteSize);
  389. UA_memset(isExternal, UA_FALSE, sizeof(UA_Boolean)*request->nodesToWriteSize);
  390. UA_UInt32 *indices = UA_alloca(sizeof(UA_UInt32) * request->nodesToWriteSize);
  391. for(UA_Int32 j = 0;j<server->externalNamespacesSize;j++) {
  392. UA_UInt32 indexSize = 0;
  393. for(UA_Int32 i = 0;i < request->nodesToWriteSize;i++) {
  394. if(request->nodesToWrite[i].nodeId.namespaceIndex !=
  395. server->externalNamespaces[j].index)
  396. continue;
  397. isExternal[i] = UA_TRUE;
  398. indices[indexSize] = i;
  399. indexSize++;
  400. }
  401. if(indexSize == 0)
  402. continue;
  403. UA_ExternalNodeStore *ens = &server->externalNamespaces[j].externalNodeStore;
  404. ens->writeNodes(ens->ensHandle, &request->requestHeader, request->nodesToWrite,
  405. indices, indexSize, response->results, response->diagnosticInfos);
  406. }
  407. /* ### End External Namespaces */
  408. response->resultsSize = request->nodesToWriteSize;
  409. for(UA_Int32 i = 0;i < request->nodesToWriteSize;i++) {
  410. if(!isExternal[i])
  411. response->results[i] = writeValue(server, &request->nodesToWrite[i]);
  412. }
  413. }