ua_services_attribute.c 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. #include "ua_server_internal.h"
  2. #include "ua_services.h"
  3. /**********************/
  4. /* Parse NumericRange */
  5. /**********************/
  6. static size_t
  7. readDimension(UA_Byte *buf, size_t buflen, UA_NumericRangeDimension *dim) {
  8. size_t progress = UA_readNumber(buf, buflen, &dim->min);
  9. if(progress == 0)
  10. return 0;
  11. if(buflen <= progress + 1 || buf[progress] != ':') {
  12. dim->max = dim->min;
  13. return progress;
  14. }
  15. progress++;
  16. size_t progress2 = UA_readNumber(&buf[progress], buflen - progress, &dim->max);
  17. if(progress2 == 0)
  18. return 0;
  19. /* invalid range */
  20. if(dim->min > dim->max)
  21. return 0;
  22. return progress + progress2;
  23. }
  24. #ifndef UA_BUILD_UNIT_TESTS
  25. static
  26. #endif
  27. UA_StatusCode parse_numericrange(const UA_String *str, UA_NumericRange *range) {
  28. size_t idx = 0;
  29. size_t dimensionsMax = 0;
  30. UA_NumericRangeDimension *dimensions = NULL;
  31. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  32. size_t offset = 0;
  33. while(true) {
  34. /* alloc dimensions */
  35. if(idx >= dimensionsMax) {
  36. UA_NumericRangeDimension *newds;
  37. size_t newdssize = sizeof(UA_NumericRangeDimension) * (dimensionsMax + 2);
  38. newds = UA_realloc(dimensions, newdssize);
  39. if(!newds) {
  40. retval = UA_STATUSCODE_BADOUTOFMEMORY;
  41. break;
  42. }
  43. dimensions = newds;
  44. dimensionsMax = dimensionsMax + 2;
  45. }
  46. /* read the dimension */
  47. size_t progress = readDimension(&str->data[offset], str->length - offset,
  48. &dimensions[idx]);
  49. if(progress == 0) {
  50. retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
  51. break;
  52. }
  53. offset += progress;
  54. idx++;
  55. /* loop into the next dimension */
  56. if(offset >= str->length)
  57. break;
  58. if(str->data[offset] != ',') {
  59. retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
  60. break;
  61. }
  62. offset++;
  63. }
  64. if(retval == UA_STATUSCODE_GOOD && idx > 0) {
  65. range->dimensions = dimensions;
  66. range->dimensionsSize = idx;
  67. } else
  68. UA_free(dimensions);
  69. return retval;
  70. }
  71. /******************/
  72. /* Read Attribute */
  73. /******************/
  74. /* force cast for zero-copy reading. ensure that the variant is never written
  75. into. */
  76. static void
  77. forceVariantSetScalar(UA_Variant *v, const void *p, const UA_DataType *t) {
  78. UA_Variant_init(v);
  79. v->type = t;
  80. v->data = (void*)(uintptr_t)p;
  81. v->storageType = UA_VARIANT_DATA_NODELETE;
  82. }
  83. static UA_StatusCode
  84. getVariableNodeValue(UA_Server *server, UA_Session *session,
  85. const UA_VariableNode *vn,
  86. const UA_TimestampsToReturn timestamps,
  87. const UA_ReadValueId *id, UA_DataValue *v) {
  88. UA_NumericRange range;
  89. UA_NumericRange *rangeptr = NULL;
  90. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  91. if(id->indexRange.length > 0) {
  92. retval = parse_numericrange(&id->indexRange, &range);
  93. if(retval != UA_STATUSCODE_GOOD)
  94. return retval;
  95. rangeptr = &range;
  96. }
  97. if(vn->valueSource == UA_VALUESOURCE_DATA) {
  98. if(vn->value.data.callback.onRead)
  99. vn->value.data.callback.onRead(vn->value.data.callback.handle, vn->nodeId,
  100. &v->value, rangeptr);
  101. if(!rangeptr) {
  102. *v = vn->value.data.value;
  103. v->value.storageType = UA_VARIANT_DATA_NODELETE;
  104. } else
  105. retval = UA_Variant_copyRange(&vn->value.data.value.value, &v->value, range);
  106. } else {
  107. if(!vn->value.dataSource.read) {
  108. UA_LOG_DEBUG_SESSION(server->config.logger, session,
  109. "DataSource cannot be read in ReadRequest");
  110. retval = UA_STATUSCODE_BADINTERNALERROR;
  111. } else {
  112. UA_Boolean sourceTimeStamp = (timestamps == UA_TIMESTAMPSTORETURN_SOURCE ||
  113. timestamps == UA_TIMESTAMPSTORETURN_BOTH);
  114. retval = vn->value.dataSource.read(vn->value.dataSource.handle, vn->nodeId,
  115. sourceTimeStamp, rangeptr, v);
  116. }
  117. }
  118. if(rangeptr)
  119. UA_free(range.dimensions);
  120. return retval;
  121. }
  122. static UA_StatusCode
  123. getVariableNodeArrayDimensions(const UA_VariableNode *vn, UA_DataValue *v) {
  124. UA_Variant_setArray(&v->value, vn->arrayDimensions, vn->arrayDimensionsSize,
  125. &UA_TYPES[UA_TYPES_INT32]);
  126. v->value.storageType = UA_VARIANT_DATA_NODELETE;
  127. v->hasValue = true;
  128. return UA_STATUSCODE_GOOD;
  129. }
  130. static UA_StatusCode
  131. getIsAbstractAttribute(const UA_Node *node, UA_Variant *v) {
  132. const UA_Boolean *isAbstract;
  133. switch(node->nodeClass) {
  134. case UA_NODECLASS_REFERENCETYPE:
  135. isAbstract = &((const UA_ReferenceTypeNode*)node)->isAbstract;
  136. break;
  137. case UA_NODECLASS_OBJECTTYPE:
  138. isAbstract = &((const UA_ObjectTypeNode*)node)->isAbstract;
  139. break;
  140. case UA_NODECLASS_VARIABLETYPE:
  141. isAbstract = &((const UA_VariableTypeNode*)node)->isAbstract;
  142. break;
  143. case UA_NODECLASS_DATATYPE:
  144. isAbstract = &((const UA_DataTypeNode*)node)->isAbstract;
  145. break;
  146. default:
  147. return UA_STATUSCODE_BADATTRIBUTEIDINVALID;
  148. }
  149. forceVariantSetScalar(v, isAbstract, &UA_TYPES[UA_TYPES_BOOLEAN]);
  150. return UA_STATUSCODE_GOOD;
  151. }
  152. static const UA_String binEncoding = {sizeof("DefaultBinary")-1, (UA_Byte*)"DefaultBinary"};
  153. /* clang complains about unused variables */
  154. /* static const UA_String xmlEncoding = {sizeof("DefaultXml")-1, (UA_Byte*)"DefaultXml"}; */
  155. #define CHECK_NODECLASS(CLASS) \
  156. if(!(node->nodeClass & (CLASS))) { \
  157. retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID; \
  158. break; \
  159. }
  160. /* Reads a single attribute from a node in the nodestore */
  161. void Service_Read_single(UA_Server *server, UA_Session *session,
  162. const UA_TimestampsToReturn timestamps,
  163. const UA_ReadValueId *id, UA_DataValue *v) {
  164. UA_LOG_DEBUG_SESSION(server->config.logger, session,
  165. "Read the attribute %i", id->attributeId);
  166. if(id->dataEncoding.name.length > 0 &&
  167. !UA_String_equal(&binEncoding, &id->dataEncoding.name)) {
  168. v->hasStatus = true;
  169. v->status = UA_STATUSCODE_BADDATAENCODINGUNSUPPORTED;
  170. return;
  171. }
  172. /* index range for a non-value */
  173. if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE){
  174. v->hasStatus = true;
  175. v->status = UA_STATUSCODE_BADINDEXRANGENODATA;
  176. return;
  177. }
  178. UA_Node const *node = UA_NodeStore_get(server->nodestore, &id->nodeId);
  179. if(!node) {
  180. v->hasStatus = true;
  181. v->status = UA_STATUSCODE_BADNODEIDUNKNOWN;
  182. return;
  183. }
  184. /* When setting the value fails in the switch, we get an error code and set
  185. hasValue to false */
  186. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  187. v->hasValue = true;
  188. switch(id->attributeId) {
  189. case UA_ATTRIBUTEID_NODEID:
  190. forceVariantSetScalar(&v->value, &node->nodeId, &UA_TYPES[UA_TYPES_NODEID]);
  191. break;
  192. case UA_ATTRIBUTEID_NODECLASS:
  193. forceVariantSetScalar(&v->value, &node->nodeClass, &UA_TYPES[UA_TYPES_NODECLASS]);
  194. break;
  195. case UA_ATTRIBUTEID_BROWSENAME:
  196. forceVariantSetScalar(&v->value, &node->browseName, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
  197. break;
  198. case UA_ATTRIBUTEID_DISPLAYNAME:
  199. forceVariantSetScalar(&v->value, &node->displayName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
  200. break;
  201. case UA_ATTRIBUTEID_DESCRIPTION:
  202. forceVariantSetScalar(&v->value, &node->description, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
  203. break;
  204. case UA_ATTRIBUTEID_WRITEMASK:
  205. forceVariantSetScalar(&v->value, &node->writeMask, &UA_TYPES[UA_TYPES_UINT32]);
  206. break;
  207. case UA_ATTRIBUTEID_USERWRITEMASK:
  208. forceVariantSetScalar(&v->value, &node->userWriteMask, &UA_TYPES[UA_TYPES_UINT32]);
  209. break;
  210. case UA_ATTRIBUTEID_ISABSTRACT:
  211. retval = getIsAbstractAttribute(node, &v->value);
  212. break;
  213. case UA_ATTRIBUTEID_SYMMETRIC:
  214. CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE);
  215. forceVariantSetScalar(&v->value, &((const UA_ReferenceTypeNode*)node)->symmetric,
  216. &UA_TYPES[UA_TYPES_BOOLEAN]);
  217. break;
  218. case UA_ATTRIBUTEID_INVERSENAME:
  219. CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE);
  220. forceVariantSetScalar(&v->value, &((const UA_ReferenceTypeNode*)node)->inverseName,
  221. &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
  222. break;
  223. case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
  224. CHECK_NODECLASS(UA_NODECLASS_VIEW);
  225. forceVariantSetScalar(&v->value, &((const UA_ViewNode*)node)->containsNoLoops,
  226. &UA_TYPES[UA_TYPES_BOOLEAN]);
  227. break;
  228. case UA_ATTRIBUTEID_EVENTNOTIFIER:
  229. CHECK_NODECLASS(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT);
  230. forceVariantSetScalar(&v->value, &((const UA_ViewNode*)node)->eventNotifier,
  231. &UA_TYPES[UA_TYPES_BYTE]);
  232. break;
  233. case UA_ATTRIBUTEID_VALUE:
  234. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  235. retval = getVariableNodeValue(server, session, (const UA_VariableNode*)node, timestamps, id, v);
  236. break;
  237. case UA_ATTRIBUTEID_DATATYPE:
  238. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  239. forceVariantSetScalar(&v->value, &((const UA_VariableTypeNode*)node)->dataType,
  240. &UA_TYPES[UA_TYPES_NODEID]);
  241. break;
  242. case UA_ATTRIBUTEID_VALUERANK:
  243. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  244. forceVariantSetScalar(&v->value, &((const UA_VariableTypeNode*)node)->valueRank,
  245. &UA_TYPES[UA_TYPES_INT32]);
  246. break;
  247. case UA_ATTRIBUTEID_ARRAYDIMENSIONS:
  248. CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  249. retval = getVariableNodeArrayDimensions((const UA_VariableNode*)node, v);
  250. break;
  251. case UA_ATTRIBUTEID_ACCESSLEVEL:
  252. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  253. forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->accessLevel,
  254. &UA_TYPES[UA_TYPES_BYTE]);
  255. break;
  256. case UA_ATTRIBUTEID_USERACCESSLEVEL:
  257. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  258. forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->userAccessLevel,
  259. &UA_TYPES[UA_TYPES_BYTE]);
  260. break;
  261. case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
  262. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  263. forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->minimumSamplingInterval,
  264. &UA_TYPES[UA_TYPES_DOUBLE]);
  265. break;
  266. case UA_ATTRIBUTEID_HISTORIZING:
  267. CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
  268. forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->historizing,
  269. &UA_TYPES[UA_TYPES_BOOLEAN]);
  270. break;
  271. case UA_ATTRIBUTEID_EXECUTABLE:
  272. CHECK_NODECLASS(UA_NODECLASS_METHOD);
  273. forceVariantSetScalar(&v->value, &((const UA_MethodNode*)node)->executable,
  274. &UA_TYPES[UA_TYPES_BOOLEAN]);
  275. break;
  276. case UA_ATTRIBUTEID_USEREXECUTABLE:
  277. CHECK_NODECLASS(UA_NODECLASS_METHOD);
  278. forceVariantSetScalar(&v->value, &((const UA_MethodNode*)node)->userExecutable,
  279. &UA_TYPES[UA_TYPES_BOOLEAN]);
  280. break;
  281. default:
  282. retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
  283. }
  284. if(retval != UA_STATUSCODE_GOOD) {
  285. v->hasValue = false;
  286. v->hasStatus = true;
  287. v->status = retval;
  288. }
  289. }
  290. void Service_Read(UA_Server *server, UA_Session *session,
  291. const UA_ReadRequest *request, UA_ReadResponse *response) {
  292. UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing ReadRequest");
  293. if(request->nodesToReadSize <= 0) {
  294. response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
  295. return;
  296. }
  297. /* check if the timestampstoreturn is valid */
  298. if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) {
  299. response->responseHeader.serviceResult = UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID;
  300. return;
  301. }
  302. size_t size = request->nodesToReadSize;
  303. response->results = UA_Array_new(size, &UA_TYPES[UA_TYPES_DATAVALUE]);
  304. if(!response->results) {
  305. response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
  306. return;
  307. }
  308. response->resultsSize = size;
  309. if(request->maxAge < 0) {
  310. response->responseHeader.serviceResult = UA_STATUSCODE_BADMAXAGEINVALID;
  311. return;
  312. }
  313. #ifdef UA_ENABLE_EXTERNAL_NAMESPACES
  314. UA_Boolean isExternal[size];
  315. UA_UInt32 indices[size];
  316. memset(isExternal, false, sizeof(UA_Boolean) * size);
  317. for(size_t j = 0;j<server->externalNamespacesSize;j++) {
  318. size_t indexSize = 0;
  319. for(size_t i = 0;i < size;i++) {
  320. if(request->nodesToRead[i].nodeId.namespaceIndex != server->externalNamespaces[j].index)
  321. continue;
  322. isExternal[i] = true;
  323. indices[indexSize] = (UA_UInt32)i;
  324. indexSize++;
  325. }
  326. if(indexSize == 0)
  327. continue;
  328. UA_ExternalNodeStore *ens = &server->externalNamespaces[j].externalNodeStore;
  329. ens->readNodes(ens->ensHandle, &request->requestHeader, request->nodesToRead,
  330. indices, (UA_UInt32)indexSize, response->results, false,
  331. response->diagnosticInfos);
  332. }
  333. #endif
  334. for(size_t i = 0;i < size;i++) {
  335. #ifdef UA_ENABLE_EXTERNAL_NAMESPACES
  336. if(!isExternal[i])
  337. #endif
  338. Service_Read_single(server, session, request->timestampsToReturn,
  339. &request->nodesToRead[i], &response->results[i]);
  340. }
  341. #ifdef UA_ENABLE_NONSTANDARD_STATELESS
  342. /* Add an expiry header for caching */
  343. if(session->sessionId.namespaceIndex == 0 &&
  344. session->sessionId.identifierType == UA_NODEIDTYPE_NUMERIC &&
  345. session->sessionId.identifier.numeric == 0){
  346. UA_ExtensionObject additionalHeader;
  347. UA_ExtensionObject_init(&additionalHeader);
  348. additionalHeader.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
  349. additionalHeader.content.encoded.typeId =UA_TYPES[UA_TYPES_VARIANT].typeId;
  350. UA_Variant variant;
  351. UA_Variant_init(&variant);
  352. UA_DateTime* expireArray = NULL;
  353. expireArray = UA_Array_new(request->nodesToReadSize,
  354. &UA_TYPES[UA_TYPES_DATETIME]);
  355. variant.data = expireArray;
  356. /* expires in 20 seconds */
  357. for(UA_UInt32 i = 0;i < response->resultsSize;i++) {
  358. expireArray[i] = UA_DateTime_now() + 20 * 100 * 1000 * 1000;
  359. }
  360. UA_Variant_setArray(&variant, expireArray, request->nodesToReadSize,
  361. &UA_TYPES[UA_TYPES_DATETIME]);
  362. size_t offset = 0;
  363. UA_ByteString str;
  364. size_t strlength = UA_calcSizeBinary(&variant, &UA_TYPES[UA_TYPES_VARIANT]);
  365. UA_ByteString_allocBuffer(&str, strlength);
  366. /* No chunking callback for the encoding */
  367. UA_StatusCode retval = UA_encodeBinary(&variant, &UA_TYPES[UA_TYPES_VARIANT],
  368. NULL, NULL, &str, &offset);
  369. UA_Array_delete(expireArray, request->nodesToReadSize, &UA_TYPES[UA_TYPES_DATETIME]);
  370. if(retval == UA_STATUSCODE_GOOD){
  371. additionalHeader.content.encoded.body.data = str.data;
  372. additionalHeader.content.encoded.body.length = offset;
  373. response->responseHeader.additionalHeader = additionalHeader;
  374. }
  375. }
  376. #endif
  377. }
  378. /* Exposes the Read service to local users */
  379. UA_DataValue
  380. UA_Server_read(UA_Server *server, const UA_ReadValueId *item,
  381. UA_TimestampsToReturn timestamps) {
  382. UA_DataValue dv;
  383. UA_DataValue_init(&dv);
  384. UA_RCU_LOCK();
  385. Service_Read_single(server, &adminSession, timestamps, item, &dv);
  386. UA_RCU_UNLOCK();
  387. return dv;
  388. }
  389. /* Used in inline functions exposing the Read service with more syntactic sugar
  390. * for individual attributes */
  391. UA_StatusCode
  392. __UA_Server_read(UA_Server *server, const UA_NodeId *nodeId,
  393. const UA_AttributeId attributeId, void *v) {
  394. /* Call the read service */
  395. UA_ReadValueId item;
  396. UA_ReadValueId_init(&item);
  397. item.nodeId = *nodeId;
  398. item.attributeId = attributeId;
  399. UA_DataValue dv = UA_Server_read(server, &item, UA_TIMESTAMPSTORETURN_NEITHER);
  400. /* Check the return value */
  401. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  402. if(dv.hasStatus)
  403. retval = dv.hasStatus;
  404. else if(!dv.hasValue)
  405. retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
  406. if(retval != UA_STATUSCODE_GOOD) {
  407. UA_DataValue_deleteMembers(&dv);
  408. return retval;
  409. }
  410. /* Prepare the result */
  411. if(attributeId == UA_ATTRIBUTEID_VALUE ||
  412. attributeId == UA_ATTRIBUTEID_ARRAYDIMENSIONS) {
  413. /* Return the entire variant */
  414. if(dv.value.storageType == UA_VARIANT_DATA_NODELETE) {
  415. retval = UA_Variant_copy(&dv.value, v);
  416. } else {
  417. /* storageType is UA_VARIANT_DATA. Copy the entire variant
  418. * (including pointers and all) */
  419. memcpy(v, &dv.value, sizeof(UA_Variant));
  420. }
  421. } else {
  422. /* Return the variant content only */
  423. if(dv.value.storageType == UA_VARIANT_DATA_NODELETE) {
  424. retval = UA_copy(dv.value.data, v, dv.value.type);
  425. } else {
  426. /* storageType is UA_VARIANT_DATA. Copy the content of the type
  427. * (including pointers and all) */
  428. memcpy(v, dv.value.data, dv.value.type->memSize);
  429. /* Delete the "carrier" in the variant */
  430. UA_free(dv.value.data);
  431. }
  432. }
  433. return retval;
  434. }
  435. /*******************/
  436. /* Write Attribute */
  437. /*******************/
  438. UA_StatusCode
  439. UA_Server_editNode(UA_Server *server, UA_Session *session,
  440. const UA_NodeId *nodeId, UA_EditNodeCallback callback,
  441. const void *data) {
  442. UA_StatusCode retval;
  443. do {
  444. #ifndef UA_ENABLE_MULTITHREADING
  445. const UA_Node *node = UA_NodeStore_get(server->nodestore, nodeId);
  446. if(!node)
  447. return UA_STATUSCODE_BADNODEIDUNKNOWN;
  448. UA_Node *editNode = (UA_Node*)(uintptr_t)node; // dirty cast
  449. retval = callback(server, session, editNode, data);
  450. return retval;
  451. #else
  452. UA_Node *copy = UA_NodeStore_getCopy(server->nodestore, nodeId);
  453. if(!copy)
  454. return UA_STATUSCODE_BADOUTOFMEMORY;
  455. retval = callback(server, session, copy, data);
  456. if(retval != UA_STATUSCODE_GOOD) {
  457. UA_NodeStore_deleteNode(copy);
  458. return retval;
  459. }
  460. retval = UA_NodeStore_replace(server->nodestore, copy);
  461. #endif
  462. } while(retval != UA_STATUSCODE_GOOD);
  463. return UA_STATUSCODE_GOOD;
  464. }
  465. enum type_equivalence {
  466. TYPE_EQUIVALENCE_NONE,
  467. TYPE_EQUIVALENCE_ENUM,
  468. TYPE_EQUIVALENCE_OPAQUE
  469. };
  470. static const UA_VariableTypeNode *
  471. getVariableType(UA_Server *server, const UA_VariableNode *node) {
  472. /* The reference to the parent is different for variable and variabletype */
  473. UA_NodeId parentRef;
  474. UA_Boolean inverse;
  475. if(node->nodeClass == UA_NODECLASS_VARIABLE) {
  476. parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION);
  477. inverse = false;
  478. } else {
  479. parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
  480. inverse = true;
  481. }
  482. /* stop at the first matching candidate */
  483. UA_NodeId *parentId = NULL;
  484. for(size_t i = 0; i < node->referencesSize; i++) {
  485. if(node->references[i].isInverse == inverse &&
  486. UA_NodeId_equal(&node->references[i].referenceTypeId, &parentRef)) {
  487. parentId = &node->references[i].targetId.nodeId;
  488. break;
  489. }
  490. }
  491. const UA_Node *parent = UA_NodeStore_get(server->nodestore, parentId);
  492. if(!parent || parent->nodeClass != UA_NODECLASS_VARIABLETYPE)
  493. return NULL;
  494. return (const UA_VariableTypeNode*)parent;
  495. }
  496. static UA_StatusCode
  497. writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
  498. const UA_NodeId *dataType) {
  499. const UA_VariableNode *vt = (const UA_VariableNode*)getVariableType(server, node);
  500. if(!vt)
  501. return UA_STATUSCODE_BADINTERNALERROR; /* should never happen */
  502. const UA_NodeId *vtDataType = &vt->dataType;
  503. UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
  504. UA_Boolean found = false;
  505. UA_StatusCode retval = isNodeInTree(server->nodestore, dataType,
  506. vtDataType, &subtypeId,
  507. 1, 10, &found);
  508. if(retval != UA_STATUSCODE_GOOD)
  509. return retval;
  510. if(!found)
  511. retval = UA_STATUSCODE_BADTYPEMISMATCH;
  512. UA_NodeId dtCopy = node->dataType;
  513. retval = UA_NodeId_copy(dataType, &node->dataType);
  514. if(!retval) {
  515. node->dataType = dtCopy;
  516. return retval;
  517. }
  518. UA_NodeId_deleteMembers(&dtCopy);
  519. return UA_STATUSCODE_GOOD;
  520. }
  521. const UA_DataType *
  522. findDataType(const UA_NodeId *typeId) {
  523. for(size_t i = 0; i < UA_TYPES_COUNT; i++) {
  524. if (UA_TYPES[i].typeId.identifier.numeric == typeId->identifier.numeric)
  525. return &UA_TYPES[i];
  526. }
  527. return NULL;
  528. }
  529. static enum type_equivalence typeEquivalence(const UA_DataType *t) {
  530. if(t->membersSize != 1 || !t->members[0].namespaceZero)
  531. return TYPE_EQUIVALENCE_NONE;
  532. if(t->members[0].memberTypeIndex == UA_TYPES_INT32)
  533. return TYPE_EQUIVALENCE_ENUM;
  534. if(t->members[0].memberTypeIndex == UA_TYPES_BYTE && t->members[0].isArray)
  535. return TYPE_EQUIVALENCE_OPAQUE;
  536. return TYPE_EQUIVALENCE_NONE;
  537. }
  538. /* Tests whether the value can be written into a node. Sometimes it can be
  539. * necessary to transform the content of the value, e.g. byte array to
  540. * bytestring or uint32 to some enum. If successful the equivalent variant
  541. * contains the correct definition (NODELETE, pointing to the members of the
  542. * original value, so do not delete) */
  543. static UA_StatusCode
  544. matchValueWithNodeDefinition(UA_Server *server, const UA_VariableNode *node,
  545. const UA_Variant *value, const UA_NumericRange *range,
  546. UA_Variant *equivalent) {
  547. /* Prepare the output variant */
  548. *equivalent = *value;
  549. equivalent->storageType = UA_VARIANT_DATA_NODELETE;
  550. /* No content is only allowed for BaseDataType */
  551. const UA_NodeId *dataType;
  552. UA_NodeId basedatatype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
  553. if(value->type)
  554. dataType = &value->type->typeId;
  555. else
  556. dataType = &basedatatype;
  557. /* See if the types match. The nodeid on the wire may be != the nodeid in
  558. * the node for opaque types, enums and bytestrings. value contains the
  559. * correct type definition after the following paragraph */
  560. if(!UA_NodeId_equal(&node->dataType, dataType)) {
  561. /* is this a subtype? */
  562. UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
  563. UA_Boolean found = false;
  564. UA_StatusCode retval = isNodeInTree(server->nodestore, dataType,
  565. &node->dataType, &subtypeId,
  566. 1, 10, &found);
  567. if(retval != UA_STATUSCODE_GOOD)
  568. return retval;
  569. if(found)
  570. goto check_array;
  571. /* compare the datatypes for equivalence */
  572. const UA_DataType *nodeDataType = findDataType(&node->dataType);
  573. const UA_DataType *valueDataType = value->type;
  574. if(!nodeDataType || !valueDataType)
  575. return UA_STATUSCODE_BADTYPEMISMATCH;
  576. /* a string is written to a byte array */
  577. if(nodeDataType == &UA_TYPES[UA_TYPES_BYTE] &&
  578. valueDataType == &UA_TYPES[UA_TYPES_BYTESTRING] &&
  579. !UA_Variant_isScalar(&node->value.data.value.value) &&
  580. UA_Variant_isScalar(value)) {
  581. UA_ByteString *str = (UA_ByteString*)value->data;
  582. equivalent->type = &UA_TYPES[UA_TYPES_BYTE];
  583. equivalent->arrayLength = str->length;
  584. equivalent->data = str->data;
  585. goto check_array;
  586. }
  587. /* An enum was sent as an int32, or an opaque type as a bytestring. This
  588. * is detected with the typeIndex indicating the "true" datatype. */
  589. enum type_equivalence te1 = typeEquivalence(nodeDataType);
  590. enum type_equivalence te2 = typeEquivalence(valueDataType);
  591. if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
  592. equivalent->type = nodeDataType;
  593. goto check_array;
  594. }
  595. /* No more possible equivalencies */
  596. return UA_STATUSCODE_BADTYPEMISMATCH;
  597. }
  598. check_array:
  599. /* Check if the valuerank allows for the value dimension */
  600. switch(node->valueRank) {
  601. case -3: /* the value can be a scalar or a one dimensional array */
  602. if(value->arrayDimensionsSize > 1)
  603. return UA_STATUSCODE_BADTYPEMISMATCH;
  604. break;
  605. case -2: /* the value can be a scalar or an array with any number of dimensions */
  606. break;
  607. case -1: /* the value is a scalar */
  608. if(!UA_Variant_isScalar(equivalent))
  609. return UA_STATUSCODE_BADTYPEMISMATCH;
  610. case 0: /* the value is an array with one or more dimensions */
  611. if(UA_Variant_isScalar(equivalent))
  612. return UA_STATUSCODE_BADTYPEMISMATCH;
  613. break;
  614. default: /* >= 1: the value is an array with the specified number of dimensions */
  615. if(value->arrayDimensionsSize != (size_t)node->valueRank)
  616. return UA_STATUSCODE_BADTYPEMISMATCH;
  617. }
  618. /* Ranges are checked in detail during writing into the variant */
  619. if(range)
  620. return UA_STATUSCODE_GOOD;
  621. /* See if the array dimensions match */
  622. if(node->arrayDimensions) {
  623. if(value->arrayDimensionsSize != node->arrayDimensionsSize)
  624. return UA_STATUSCODE_BADTYPEMISMATCH;
  625. /* dimension size zero in the node definition: this value dimension can be any size */
  626. for(size_t i = 0; i < node->arrayDimensionsSize; i++) {
  627. if(node->arrayDimensions[i] != value->arrayDimensions[i] &&
  628. node->arrayDimensions[i] != 0)
  629. return UA_STATUSCODE_BADTYPEMISMATCH;
  630. }
  631. }
  632. return UA_STATUSCODE_GOOD;
  633. }
  634. UA_StatusCode
  635. writeValueAttribute(UA_Server *server, UA_VariableNode *node,
  636. const UA_DataValue *value, const UA_String *indexRange) {
  637. /* Parse the range */
  638. UA_NumericRange range;
  639. UA_NumericRange *rangeptr = NULL;
  640. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  641. if(indexRange && indexRange->length > 0) {
  642. if(!value->hasValue || !node->value.data.value.hasValue)
  643. return UA_STATUSCODE_BADINDEXRANGENODATA;
  644. retval = parse_numericrange(indexRange, &range);
  645. if(retval != UA_STATUSCODE_GOOD)
  646. return retval;
  647. rangeptr = &range;
  648. }
  649. /* Check the type definition and use a possibly transformed variant that
  650. * matches the node data type */
  651. UA_DataValue equivValue;
  652. if(value->hasValue) {
  653. equivValue = *value;
  654. retval = matchValueWithNodeDefinition(server, node, &value->value,
  655. rangeptr, &equivValue.value);
  656. if(retval != UA_STATUSCODE_GOOD)
  657. goto cleanup;
  658. value = &equivValue;
  659. }
  660. /* write the value */
  661. if(node->valueSource == UA_VALUESOURCE_DATA) {
  662. if(!rangeptr)
  663. retval = UA_DataValue_copy(value, &node->value.data.value);
  664. else
  665. retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
  666. value->value.data,
  667. value->value.arrayLength, range);
  668. /* post-write callback */
  669. if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite)
  670. node->value.data.callback.onWrite(node->value.data.callback.handle, node->nodeId,
  671. &node->value.data.value.value, rangeptr);
  672. } else {
  673. /* TODO: Don't make a copy of the node in the multithreaded case */
  674. retval = node->value.dataSource.write(node->value.dataSource.handle, node->nodeId,
  675. &value->value, rangeptr);
  676. }
  677. cleanup:
  678. if(rangeptr)
  679. UA_free(range.dimensions);
  680. return retval;
  681. }
  682. static UA_StatusCode
  683. writeIsAbstractAttribute(UA_Node *node, UA_Boolean value) {
  684. switch(node->nodeClass) {
  685. case UA_NODECLASS_OBJECTTYPE:
  686. ((UA_ObjectTypeNode*)node)->isAbstract = value;
  687. break;
  688. case UA_NODECLASS_REFERENCETYPE:
  689. ((UA_ReferenceTypeNode*)node)->isAbstract = value;
  690. break;
  691. case UA_NODECLASS_VARIABLETYPE:
  692. ((UA_VariableTypeNode*)node)->isAbstract = value;
  693. break;
  694. case UA_NODECLASS_DATATYPE:
  695. ((UA_DataTypeNode*)node)->isAbstract = value;
  696. break;
  697. default:
  698. return UA_STATUSCODE_BADNODECLASSINVALID;
  699. }
  700. return UA_STATUSCODE_GOOD;
  701. }
  702. #define CHECK_DATATYPE(EXP_DT) \
  703. if(!wvalue->value.hasValue || \
  704. &UA_TYPES[UA_TYPES_##EXP_DT] != wvalue->value.value.type || \
  705. !UA_Variant_isScalar(&wvalue->value.value)) { \
  706. retval = UA_STATUSCODE_BADTYPEMISMATCH; \
  707. break; \
  708. }
  709. #define CHECK_NODECLASS_WRITE(CLASS) \
  710. if((node->nodeClass & (CLASS)) == 0) { \
  711. retval = UA_STATUSCODE_BADNODECLASSINVALID; \
  712. break; \
  713. }
  714. /* this function implements the main part of the write service */
  715. static UA_StatusCode
  716. CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
  717. UA_Node *node, const UA_WriteValue *wvalue) {
  718. if(!wvalue->value.hasValue)
  719. return UA_STATUSCODE_BADTYPEMISMATCH;
  720. const void *value = wvalue->value.value.data;
  721. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  722. switch(wvalue->attributeId) {
  723. case UA_ATTRIBUTEID_NODEID:
  724. case UA_ATTRIBUTEID_NODECLASS:
  725. retval = UA_STATUSCODE_BADNOTWRITABLE;
  726. break;
  727. case UA_ATTRIBUTEID_BROWSENAME:
  728. CHECK_DATATYPE(QUALIFIEDNAME);
  729. UA_QualifiedName_deleteMembers(&node->browseName);
  730. UA_QualifiedName_copy(value, &node->browseName);
  731. break;
  732. case UA_ATTRIBUTEID_DISPLAYNAME:
  733. CHECK_DATATYPE(LOCALIZEDTEXT);
  734. UA_LocalizedText_deleteMembers(&node->displayName);
  735. UA_LocalizedText_copy(value, &node->displayName);
  736. break;
  737. case UA_ATTRIBUTEID_DESCRIPTION:
  738. CHECK_DATATYPE(LOCALIZEDTEXT);
  739. UA_LocalizedText_deleteMembers(&node->description);
  740. UA_LocalizedText_copy(value, &node->description);
  741. break;
  742. case UA_ATTRIBUTEID_WRITEMASK:
  743. CHECK_DATATYPE(UINT32);
  744. node->writeMask = *(const UA_UInt32*)value;
  745. break;
  746. case UA_ATTRIBUTEID_USERWRITEMASK:
  747. CHECK_DATATYPE(UINT32);
  748. node->userWriteMask = *(const UA_UInt32*)value;
  749. break;
  750. case UA_ATTRIBUTEID_ISABSTRACT:
  751. CHECK_DATATYPE(BOOLEAN);
  752. retval = writeIsAbstractAttribute(node, *(const UA_Boolean*)value);
  753. break;
  754. case UA_ATTRIBUTEID_SYMMETRIC:
  755. CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
  756. CHECK_DATATYPE(BOOLEAN);
  757. ((UA_ReferenceTypeNode*)node)->symmetric = *(const UA_Boolean*)value;
  758. break;
  759. case UA_ATTRIBUTEID_INVERSENAME:
  760. CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
  761. CHECK_DATATYPE(LOCALIZEDTEXT);
  762. UA_LocalizedText_deleteMembers(&((UA_ReferenceTypeNode*)node)->inverseName);
  763. UA_LocalizedText_copy(value, &((UA_ReferenceTypeNode*)node)->inverseName);
  764. break;
  765. case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
  766. CHECK_NODECLASS_WRITE(UA_NODECLASS_VIEW);
  767. CHECK_DATATYPE(BOOLEAN);
  768. ((UA_ViewNode*)node)->containsNoLoops = *(const UA_Boolean*)value;
  769. break;
  770. case UA_ATTRIBUTEID_EVENTNOTIFIER:
  771. CHECK_NODECLASS_WRITE(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT);
  772. CHECK_DATATYPE(BYTE);
  773. ((UA_ViewNode*)node)->eventNotifier = *(const UA_Byte*)value;
  774. break;
  775. case UA_ATTRIBUTEID_VALUE:
  776. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  777. retval = writeValueAttribute(server, (UA_VariableNode*)node, &wvalue->value, &wvalue->indexRange);
  778. break;
  779. case UA_ATTRIBUTEID_DATATYPE:
  780. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  781. CHECK_DATATYPE(NODEID);
  782. /* TODO: Check if the current value remains valid */
  783. retval = writeDataTypeAttribute(server, (UA_VariableNode*)node, (const UA_NodeId*)value);
  784. break;
  785. case UA_ATTRIBUTEID_VALUERANK:
  786. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  787. CHECK_DATATYPE(INT32);
  788. /* TODO */
  789. retval = UA_STATUSCODE_BADNOTWRITABLE;
  790. break;
  791. case UA_ATTRIBUTEID_ARRAYDIMENSIONS:
  792. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
  793. /* TODO */
  794. retval = UA_STATUSCODE_BADNOTWRITABLE;
  795. break;
  796. case UA_ATTRIBUTEID_ACCESSLEVEL:
  797. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
  798. CHECK_DATATYPE(BYTE);
  799. ((UA_VariableNode*)node)->accessLevel = *(const UA_Byte*)value;
  800. break;
  801. case UA_ATTRIBUTEID_USERACCESSLEVEL:
  802. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
  803. CHECK_DATATYPE(BYTE);
  804. ((UA_VariableNode*)node)->userAccessLevel = *(const UA_Byte*)value;
  805. break;
  806. case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
  807. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
  808. CHECK_DATATYPE(DOUBLE);
  809. ((UA_VariableNode*)node)->minimumSamplingInterval = *(const UA_Double*)value;
  810. break;
  811. case UA_ATTRIBUTEID_HISTORIZING:
  812. CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
  813. CHECK_DATATYPE(BOOLEAN);
  814. ((UA_VariableNode*)node)->historizing = *(const UA_Boolean*)value;
  815. break;
  816. case UA_ATTRIBUTEID_EXECUTABLE:
  817. CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
  818. CHECK_DATATYPE(BOOLEAN);
  819. ((UA_MethodNode*)node)->executable = *(const UA_Boolean*)value;
  820. break;
  821. case UA_ATTRIBUTEID_USEREXECUTABLE:
  822. CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
  823. CHECK_DATATYPE(BOOLEAN);
  824. ((UA_MethodNode*)node)->userExecutable = *(const UA_Boolean*)value;
  825. break;
  826. default:
  827. retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
  828. break;
  829. }
  830. if(retval != UA_STATUSCODE_GOOD)
  831. UA_LOG_INFO_SESSION(server->config.logger, session,
  832. "WriteRequest returned status code 0x%08x", retval);
  833. return retval;
  834. }
  835. UA_StatusCode
  836. Service_Write_single(UA_Server *server, UA_Session *session,
  837. const UA_WriteValue *wvalue) {
  838. return UA_Server_editNode(server, session, &wvalue->nodeId,
  839. (UA_EditNodeCallback)CopyAttributeIntoNode, wvalue);
  840. }
  841. void
  842. Service_Write(UA_Server *server, UA_Session *session,
  843. const UA_WriteRequest *request, UA_WriteResponse *response) {
  844. UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing WriteRequest");
  845. if(request->nodesToWriteSize <= 0) {
  846. response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
  847. return;
  848. }
  849. response->results = UA_Array_new(request->nodesToWriteSize,
  850. &UA_TYPES[UA_TYPES_STATUSCODE]);
  851. if(!response->results) {
  852. response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
  853. return;
  854. }
  855. response->resultsSize = request->nodesToWriteSize;
  856. #ifdef UA_ENABLE_EXTERNAL_NAMESPACES
  857. UA_Boolean isExternal[request->nodesToWriteSize];
  858. UA_UInt32 indices[request->nodesToWriteSize];
  859. memset(isExternal, false, sizeof(UA_Boolean)*request->nodesToWriteSize);
  860. for(size_t j = 0; j < server->externalNamespacesSize; j++) {
  861. UA_UInt32 indexSize = 0;
  862. for(size_t i = 0; i < request->nodesToWriteSize; i++) {
  863. if(request->nodesToWrite[i].nodeId.namespaceIndex !=
  864. server->externalNamespaces[j].index)
  865. continue;
  866. isExternal[i] = true;
  867. indices[indexSize] = (UA_UInt32)i;
  868. indexSize++;
  869. }
  870. if(indexSize == 0)
  871. continue;
  872. UA_ExternalNodeStore *ens = &server->externalNamespaces[j].externalNodeStore;
  873. ens->writeNodes(ens->ensHandle, &request->requestHeader, request->nodesToWrite,
  874. indices, indexSize, response->results, response->diagnosticInfos);
  875. }
  876. #endif
  877. for(size_t i = 0;i < request->nodesToWriteSize;i++) {
  878. #ifdef UA_ENABLE_EXTERNAL_NAMESPACES
  879. if(!isExternal[i])
  880. #endif
  881. response->results[i] = Service_Write_single(server, session,
  882. &request->nodesToWrite[i]);
  883. }
  884. }
  885. UA_StatusCode
  886. UA_Server_write(UA_Server *server, const UA_WriteValue *value) {
  887. UA_RCU_LOCK();
  888. UA_StatusCode retval = Service_Write_single(server, &adminSession, value);
  889. UA_RCU_UNLOCK();
  890. return retval;
  891. }
  892. UA_StatusCode
  893. __UA_Server_write(UA_Server *server, const UA_NodeId *nodeId,
  894. const UA_AttributeId attributeId,
  895. const UA_DataType *attr_type,
  896. const void *value) {
  897. UA_WriteValue wvalue;
  898. UA_WriteValue_init(&wvalue);
  899. wvalue.nodeId = *nodeId;
  900. wvalue.attributeId = attributeId;
  901. if(attributeId != UA_ATTRIBUTEID_VALUE)
  902. /* hacked cast. the target WriteValue is used as const anyway */
  903. UA_Variant_setScalar(&wvalue.value.value, (void*)(uintptr_t)value,
  904. attr_type);
  905. else {
  906. if(attr_type != &UA_TYPES[UA_TYPES_VARIANT])
  907. return UA_STATUSCODE_BADTYPEMISMATCH;
  908. wvalue.value.value = *(const UA_Variant*)value;
  909. }
  910. wvalue.value.hasValue = true;
  911. UA_StatusCode retval = UA_Server_write(server, &wvalue);
  912. return retval;
  913. }