|
@@ -1,9 +1,9 @@
|
|
|
#include "ua_server_internal.h"
|
|
|
#include "ua_services.h"
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
static size_t
|
|
|
readDimension(UA_Byte *buf, size_t buflen, UA_NumericRangeDimension *dim) {
|
|
@@ -80,6 +80,10 @@ UA_StatusCode parse_numericrange(const UA_String *str, UA_NumericRange *range) {
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
static void forceVariantSetScalar(UA_Variant *v, const void *p, const UA_DataType *t) {
|
|
|
UA_Variant_init(v);
|
|
@@ -164,7 +168,7 @@ getIsAbstractAttribute(const UA_Node *node, UA_Variant *v) {
|
|
|
|
|
|
static const UA_String binEncoding = {sizeof("DefaultBinary")-1, (UA_Byte*)"DefaultBinary"};
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
#define CHECK_NODECLASS(CLASS) \
|
|
|
if(!(node->nodeClass & (CLASS))) { \
|
|
@@ -298,7 +302,6 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
|
|
|
break;
|
|
|
default:
|
|
|
retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
|
|
|
- break;
|
|
|
}
|
|
|
|
|
|
if(retval != UA_STATUSCODE_GOOD) {
|
|
@@ -407,6 +410,7 @@ void Service_Read(UA_Server *server, UA_Session *session,
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
+
|
|
|
UA_DataValue
|
|
|
UA_Server_read(UA_Server *server, const UA_ReadValueId *item,
|
|
|
UA_TimestampsToReturn timestamps) {
|
|
@@ -418,6 +422,8 @@ UA_Server_read(UA_Server *server, const UA_ReadValueId *item,
|
|
|
return dv;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ * for individual attributes */
|
|
|
UA_StatusCode
|
|
|
__UA_Server_read(UA_Server *server, const UA_NodeId *nodeId,
|
|
|
const UA_AttributeId attributeId, void *v) {
|
|
@@ -466,16 +472,17 @@ __UA_Server_read(UA_Server *server, const UA_NodeId *nodeId,
|
|
|
|
|
|
|
|
|
|
|
|
-UA_StatusCode UA_Server_editNode(UA_Server *server, UA_Session *session,
|
|
|
- const UA_NodeId *nodeId, UA_EditNodeCallback callback,
|
|
|
- const void *data) {
|
|
|
+UA_StatusCode
|
|
|
+UA_Server_editNode(UA_Server *server, UA_Session *session,
|
|
|
+ const UA_NodeId *nodeId, UA_EditNodeCallback callback,
|
|
|
+ const void *data) {
|
|
|
UA_StatusCode retval;
|
|
|
do {
|
|
|
#ifndef UA_ENABLE_MULTITHREADING
|
|
|
const UA_Node *node = UA_NodeStore_get(server->nodestore, nodeId);
|
|
|
if(!node)
|
|
|
return UA_STATUSCODE_BADNODEIDUNKNOWN;
|
|
|
- UA_Node *editNode = (UA_Node*)(uintptr_t)node;
|
|
|
+ UA_Node *editNode = (UA_Node*)(uintptr_t)node;
|
|
|
retval = callback(server, session, editNode, data);
|
|
|
return retval;
|
|
|
#else
|
|
@@ -494,33 +501,30 @@ UA_StatusCode UA_Server_editNode(UA_Server *server, UA_Session *session,
|
|
|
}
|
|
|
|
|
|
static UA_StatusCode
|
|
|
-WriteToDataSource(UA_Server *server, UA_Session *session,
|
|
|
+writeToDataSource(UA_Server *server, UA_Session *session,
|
|
|
const UA_VariableNode *node, const UA_WriteValue *wvalue) {
|
|
|
- UA_assert(wvalue->attributeId == UA_ATTRIBUTEID_VALUE);
|
|
|
- UA_assert(node->nodeClass == UA_NODECLASS_VARIABLE ||
|
|
|
- node->nodeClass == UA_NODECLASS_VARIABLETYPE);
|
|
|
- UA_assert(node->valueSource == UA_VALUESOURCE_DATASOURCE);
|
|
|
-
|
|
|
if(!node->value.dataSource.write)
|
|
|
return UA_STATUSCODE_BADWRITENOTSUPPORTED;
|
|
|
|
|
|
+ UA_NumericRange *rangeptr = NULL;
|
|
|
+ UA_NumericRange range;
|
|
|
+ UA_StatusCode retval;
|
|
|
|
|
|
+
|
|
|
if(wvalue->indexRange.length > 0) {
|
|
|
-
|
|
|
- UA_StatusCode retval;
|
|
|
- UA_NumericRange range;
|
|
|
retval = parse_numericrange(&wvalue->indexRange, &range);
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
|
return retval;
|
|
|
- retval = node->value.dataSource.write(node->value.dataSource.handle, node->nodeId,
|
|
|
- &wvalue->value.value, &range);
|
|
|
- UA_free(range.dimensions);
|
|
|
- return retval;
|
|
|
+ rangeptr = ⦥
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- return node->value.dataSource.write(node->value.dataSource.handle,
|
|
|
- node->nodeId, &wvalue->value.value, NULL);
|
|
|
+
|
|
|
+ retval = node->value.dataSource.write(node->value.dataSource.handle,
|
|
|
+ node->nodeId, &wvalue->value.value,
|
|
|
+ rangeptr);
|
|
|
+ if(rangeptr)
|
|
|
+ UA_free(range.dimensions);
|
|
|
+ return retval;
|
|
|
}
|
|
|
|
|
|
enum type_equivalence {
|
|
@@ -548,109 +552,152 @@ static enum type_equivalence typeEquivalence(const UA_DataType *t) {
|
|
|
return TYPE_EQUIVALENCE_NONE;
|
|
|
}
|
|
|
|
|
|
-UA_StatusCode
|
|
|
-CopyIntoValueAttribute(UA_Server *server, UA_VariableNode *node,
|
|
|
- const UA_DataValue *value, const UA_String *indexRange) {
|
|
|
-
|
|
|
- UA_NumericRange range;
|
|
|
- UA_NumericRange *rangeptr = NULL;
|
|
|
- UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
- if(indexRange && indexRange->length > 0) {
|
|
|
- if(!value->hasValue || !node->value.data.value.hasValue)
|
|
|
- return UA_STATUSCODE_BADINDEXRANGENODATA;
|
|
|
- retval = parse_numericrange(indexRange, &range);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- return retval;
|
|
|
- rangeptr = ⦥
|
|
|
- }
|
|
|
-
|
|
|
- if(!value->hasValue)
|
|
|
- goto write_value;
|
|
|
-
|
|
|
+
|
|
|
+ * necessary to transform the content of the value, e.g. byte array to
|
|
|
+ * bytestring or uint32 to some enum. If successful the equivalent variant
|
|
|
+ * contains the correct definition (NODELETE, pointing to the members of the
|
|
|
+ * original value, so do not delete) */
|
|
|
+static UA_StatusCode
|
|
|
+matchValueWithNodeDefinition(UA_Server *server, UA_VariableNode *node,
|
|
|
+ const UA_Variant *value, const UA_NumericRange *range,
|
|
|
+ UA_Variant *equivalent) {
|
|
|
+
|
|
|
+ *equivalent = *value;
|
|
|
+ equivalent->storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
+
|
|
|
+
|
|
|
+ const UA_NodeId *dataType;
|
|
|
+ UA_NodeId basedatatype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
|
|
|
+ if(value->type)
|
|
|
+ dataType = &value->type->typeId;
|
|
|
+ else
|
|
|
+ dataType = &basedatatype;
|
|
|
+
|
|
|
|
|
|
* the node for opaque types, enums and bytestrings. value contains the
|
|
|
* correct type definition after the following paragraph */
|
|
|
- UA_DataValue cast_v;
|
|
|
- if(!UA_NodeId_equal(&node->dataType, &value->value.type->typeId)) {
|
|
|
- cast_v = *value;
|
|
|
- value = &cast_v;
|
|
|
- const UA_DataType *nodeDataType = findDataType(&node->dataType);
|
|
|
+ if(!UA_NodeId_equal(&node->dataType, dataType)) {
|
|
|
|
|
|
|
|
|
UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
|
|
|
UA_Boolean found = false;
|
|
|
- retval = isNodeInTree(server->nodestore, &value->value.type->typeId,
|
|
|
- &node->dataType, &subtypeId, 1, 10, &found);
|
|
|
+ UA_StatusCode retval = isNodeInTree(server->nodestore, dataType,
|
|
|
+ &node->dataType, &subtypeId,
|
|
|
+ 1, 10, &found);
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
|
- goto cleanup;
|
|
|
+ return retval;
|
|
|
if(found)
|
|
|
goto check_array;
|
|
|
|
|
|
+
|
|
|
+ const UA_DataType *nodeDataType = findDataType(&node->dataType);
|
|
|
+ const UA_DataType *valueDataType = value->type;
|
|
|
+ if(!nodeDataType || !valueDataType)
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+
|
|
|
|
|
|
if(nodeDataType == &UA_TYPES[UA_TYPES_BYTE] &&
|
|
|
- value->value.type == &UA_TYPES[UA_TYPES_BYTESTRING] &&
|
|
|
- (!node->value.data.value.hasValue ||
|
|
|
- !UA_Variant_isScalar(&node->value.data.value.value)) &&
|
|
|
- UA_Variant_isScalar(&value->value)) {
|
|
|
- UA_ByteString *str = (UA_ByteString*)value->value.data;
|
|
|
- cast_v.value.type = &UA_TYPES[UA_TYPES_BYTE];
|
|
|
- cast_v.value.arrayLength = str->length;
|
|
|
- cast_v.value.data = str->data;
|
|
|
+ valueDataType == &UA_TYPES[UA_TYPES_BYTESTRING] &&
|
|
|
+ !UA_Variant_isScalar(&node->value.data.value.value) &&
|
|
|
+ UA_Variant_isScalar(value)) {
|
|
|
+ UA_ByteString *str = (UA_ByteString*)value->data;
|
|
|
+ equivalent->type = &UA_TYPES[UA_TYPES_BYTE];
|
|
|
+ equivalent->arrayLength = str->length;
|
|
|
+ equivalent->data = str->data;
|
|
|
goto check_array;
|
|
|
}
|
|
|
|
|
|
|
|
|
* is detected with the typeIndex indicated the "true" datatype. */
|
|
|
enum type_equivalence te1 = typeEquivalence(nodeDataType);
|
|
|
- enum type_equivalence te2 = typeEquivalence(value->value.type);
|
|
|
+ enum type_equivalence te2 = typeEquivalence(valueDataType);
|
|
|
if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
|
|
|
- cast_v.value.type = nodeDataType;
|
|
|
+ equivalent->type = nodeDataType;
|
|
|
goto check_array;
|
|
|
}
|
|
|
|
|
|
- retval = UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
- goto cleanup;
|
|
|
+
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
}
|
|
|
|
|
|
check_array:
|
|
|
+
|
|
|
+ switch(node->valueRank) {
|
|
|
+ case -3:
|
|
|
+ if(value->arrayDimensionsSize > 1)
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ break;
|
|
|
+ case -2:
|
|
|
+ break;
|
|
|
+ case -1:
|
|
|
+ if(!UA_Variant_isScalar(equivalent))
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ case 0:
|
|
|
+ if(UA_Variant_isScalar(equivalent))
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if(value->arrayDimensionsSize != (size_t)node->valueRank)
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if(range)
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+
|
|
|
|
|
|
- if(!rangeptr && node->arrayDimensions) {
|
|
|
- if(value->value.arrayDimensionsSize != node->arrayDimensionsSize) {
|
|
|
- retval = UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
- goto cleanup;
|
|
|
- }
|
|
|
+ if(node->arrayDimensions) {
|
|
|
+ if(value->arrayDimensionsSize != node->arrayDimensionsSize)
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+
|
|
|
for(size_t i = 0; i < node->arrayDimensionsSize; i++) {
|
|
|
- if(node->arrayDimensions[i] == value->value.arrayDimensions[i] ||
|
|
|
- node->arrayDimensions[i] == 0)
|
|
|
- continue;
|
|
|
- retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
|
|
|
- goto cleanup;
|
|
|
+ if(node->arrayDimensions[i] != value->arrayDimensions[i] &&
|
|
|
+ node->arrayDimensions[i] != 0)
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
}
|
|
|
}
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+writeValueAttribute(UA_Server *server, UA_VariableNode *node,
|
|
|
+ const UA_DataValue *value, const UA_String *indexRange) {
|
|
|
+
|
|
|
+ UA_NumericRange range;
|
|
|
+ UA_NumericRange *rangeptr = NULL;
|
|
|
+ UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
+ if(indexRange && indexRange->length > 0) {
|
|
|
+ if(!value->hasValue || !node->value.data.value.hasValue)
|
|
|
+ return UA_STATUSCODE_BADINDEXRANGENODATA;
|
|
|
+ retval = parse_numericrange(indexRange, &range);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ return retval;
|
|
|
+ rangeptr = ⦥
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * matches the node data type */
|
|
|
+ UA_DataValue equivValue;
|
|
|
+ if(value->hasValue) {
|
|
|
+ equivValue = *value;
|
|
|
+ retval = matchValueWithNodeDefinition(server, node, &value->value,
|
|
|
+ rangeptr, &equivValue.value);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ goto cleanup;
|
|
|
+ value = &equivValue;
|
|
|
+ }
|
|
|
|
|
|
- write_value:
|
|
|
|
|
|
- if(!rangeptr) {
|
|
|
- UA_DataValue save = node->value.data.value;
|
|
|
- UA_DataValue_init(&node->value.data.value);
|
|
|
+ if(!rangeptr)
|
|
|
retval = UA_DataValue_copy(value, &node->value.data.value);
|
|
|
- if(retval == UA_STATUSCODE_GOOD)
|
|
|
- UA_DataValue_deleteMembers(&node->value.data.value);
|
|
|
- else
|
|
|
- node->value.data.value = save;
|
|
|
- } else {
|
|
|
- UA_DataValue save = node->value.data.value;
|
|
|
- node->value.data.value = *value;
|
|
|
- node->value.data.value.value = save.value;
|
|
|
+ else
|
|
|
retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
|
|
|
value->value.data, value->value.arrayLength, range);
|
|
|
- }
|
|
|
|
|
|
|
|
|
- if(node->value.data.callback.onWrite)
|
|
|
- node->value.data.callback.onWrite(node->value.data.callback.handle,
|
|
|
- node->nodeId, &node->value.data.value.value,
|
|
|
- rangeptr);
|
|
|
+ if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite)
|
|
|
+ node->value.data.callback.onWrite(node->value.data.callback.handle, node->nodeId,
|
|
|
+ &node->value.data.value.value, rangeptr);
|
|
|
cleanup:
|
|
|
if(rangeptr)
|
|
|
UA_free(range.dimensions);
|
|
@@ -658,7 +705,7 @@ CopyIntoValueAttribute(UA_Server *server, UA_VariableNode *node,
|
|
|
}
|
|
|
|
|
|
static UA_StatusCode
|
|
|
-CopyIsAbstractIntoNode(UA_Node *node, UA_Boolean value) {
|
|
|
+writeIsAbstractAttribute(UA_Node *node, UA_Boolean value) {
|
|
|
switch(node->nodeClass) {
|
|
|
case UA_NODECLASS_OBJECTTYPE:
|
|
|
((UA_ObjectTypeNode*)node)->isAbstract = value;
|
|
@@ -732,7 +779,7 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
|
|
|
break;
|
|
|
case UA_ATTRIBUTEID_ISABSTRACT:
|
|
|
CHECK_DATATYPE(BOOLEAN);
|
|
|
- retval = CopyIsAbstractIntoNode(node, *(const UA_Boolean*)value);
|
|
|
+ retval = writeIsAbstractAttribute(node, *(const UA_Boolean*)value);
|
|
|
break;
|
|
|
case UA_ATTRIBUTEID_SYMMETRIC:
|
|
|
CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
|
|
@@ -758,11 +805,12 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
|
|
|
case UA_ATTRIBUTEID_VALUE:
|
|
|
CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
|
|
|
if(((const UA_VariableNode*)node)->valueSource == UA_VALUESOURCE_DATA)
|
|
|
- retval = CopyIntoValueAttribute(server, (UA_VariableNode*)node,
|
|
|
- &wvalue->value, &wvalue->indexRange);
|
|
|
+ retval = writeValueAttribute(server, (UA_VariableNode*)node,
|
|
|
+ &wvalue->value, &wvalue->indexRange);
|
|
|
else
|
|
|
|
|
|
- retval = WriteToDataSource(server, session,
|
|
|
+
|
|
|
+ retval = writeToDataSource(server, session,
|
|
|
(const UA_VariableNode*)node, wvalue);
|
|
|
break;
|
|
|
case UA_ATTRIBUTEID_ACCESSLEVEL:
|