|
@@ -103,6 +103,43 @@ compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
|
|
|
return UA_STATUSCODE_GOOD;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ the description */
|
|
|
+static const UA_Variant *
|
|
|
+convertToMatchingValue(UA_Server *server, const UA_Variant *value,
|
|
|
+ const UA_NodeId *targetDataTypeId, UA_Variant *editableValue) {
|
|
|
+ const UA_DataType *targetDataType = findDataType(targetDataTypeId);
|
|
|
+ if(!targetDataType)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+
|
|
|
+ dimensions are checked later */
|
|
|
+ if(targetDataType == &UA_TYPES[UA_TYPES_BYTE] &&
|
|
|
+ value->type == &UA_TYPES[UA_TYPES_BYTESTRING] &&
|
|
|
+ UA_Variant_isScalar(value)) {
|
|
|
+ UA_ByteString *str = (UA_ByteString*)value->data;
|
|
|
+ editableValue->storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
+ editableValue->type = &UA_TYPES[UA_TYPES_BYTE];
|
|
|
+ editableValue->arrayLength = str->length;
|
|
|
+ editableValue->data = str->data;
|
|
|
+ return editableValue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * is detected with the typeIndex indicating the "true" datatype. */
|
|
|
+ enum type_equivalence te1 = typeEquivalence(targetDataType);
|
|
|
+ enum type_equivalence te2 = typeEquivalence(value->type);
|
|
|
+ if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
|
|
|
+ *editableValue = *value;
|
|
|
+ editableValue->storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
+ editableValue->type = targetDataType;
|
|
|
+ return editableValue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
* - datatype
|
|
|
* - valueranke
|
|
@@ -111,77 +148,51 @@ compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
|
|
|
* byte array to bytestring or uint32 to some enum. If editableValue is non-NULL,
|
|
|
* we try to create a matching variant that points to the original data. */
|
|
|
UA_StatusCode
|
|
|
-typeCheckValue(UA_Server *server, const UA_NodeId *variableDataTypeId,
|
|
|
- UA_Int32 variableValueRank, size_t variableArrayDimensionsSize,
|
|
|
- const UA_UInt32 *variableArrayDimensions, const UA_Variant *value,
|
|
|
+typeCheckValue(UA_Server *server, const UA_NodeId *targetDataTypeId,
|
|
|
+ UA_Int32 targetValueRank, size_t targetArrayDimensionsSize,
|
|
|
+ const UA_UInt32 *targetArrayDimensions, const UA_Variant *value,
|
|
|
const UA_NumericRange *range, UA_Variant *editableValue) {
|
|
|
-
|
|
|
- const UA_NodeId *valueDataTypeId;
|
|
|
+
|
|
|
UA_NodeId basedatatype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
|
|
|
- if(value->type)
|
|
|
- valueDataTypeId = &value->type->typeId;
|
|
|
- else
|
|
|
- valueDataTypeId = &basedatatype;
|
|
|
-
|
|
|
+ if(!value->type) {
|
|
|
+ if(UA_NodeId_equal(targetDataTypeId, &basedatatype))
|
|
|
+ goto check_array;
|
|
|
+ else
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
* the node for opaque types, enums and bytestrings. value contains the
|
|
|
* correct type definition after the following paragraph */
|
|
|
- if(!UA_NodeId_equal(valueDataTypeId, variableDataTypeId)) {
|
|
|
-
|
|
|
- const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
|
|
|
- if(isNodeInTree(server->nodestore, valueDataTypeId,
|
|
|
- variableDataTypeId, &subtypeId, 1))
|
|
|
- goto check_array;
|
|
|
-
|
|
|
- const UA_DataType *variableDataType = findDataType(variableDataTypeId);
|
|
|
- const UA_DataType *valueDataType = findDataType(valueDataTypeId);
|
|
|
- if(!editableValue || !variableDataType || !valueDataType)
|
|
|
- return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ if(UA_NodeId_equal(&value->type->typeId, targetDataTypeId))
|
|
|
+ goto check_array;
|
|
|
|
|
|
-
|
|
|
- dimensions are checked later */
|
|
|
- if(variableDataType == &UA_TYPES[UA_TYPES_BYTE] &&
|
|
|
- valueDataType == &UA_TYPES[UA_TYPES_BYTESTRING] &&
|
|
|
- !range && UA_Variant_isScalar(value)) {
|
|
|
- UA_ByteString *str = (UA_ByteString*)value->data;
|
|
|
- editableValue->storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
- editableValue->type = &UA_TYPES[UA_TYPES_BYTE];
|
|
|
- editableValue->arrayLength = str->length;
|
|
|
- editableValue->data = str->data;
|
|
|
- value = editableValue;
|
|
|
- goto check_array;
|
|
|
- }
|
|
|
+
|
|
|
+ const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
|
|
|
+ if(isNodeInTree(server->nodestore, &value->type->typeId, targetDataTypeId, &subtypeId, 1))
|
|
|
+ goto check_array;
|
|
|
|
|
|
-
|
|
|
- * is detected with the typeIndex indicating the "true" datatype. */
|
|
|
- enum type_equivalence te1 = typeEquivalence(variableDataType);
|
|
|
- enum type_equivalence te2 = typeEquivalence(valueDataType);
|
|
|
- if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
|
|
|
- *editableValue = *value;
|
|
|
- editableValue->storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
- editableValue->type = variableDataType;
|
|
|
- value = editableValue;
|
|
|
- goto check_array;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+ if(!editableValue)
|
|
|
+ return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
+ value = convertToMatchingValue(server, value, targetDataTypeId, editableValue);
|
|
|
+ if(!value)
|
|
|
return UA_STATUSCODE_BADTYPEMISMATCH;
|
|
|
- }
|
|
|
|
|
|
check_array:
|
|
|
- if(range)
|
|
|
+ if(range)
|
|
|
return UA_STATUSCODE_GOOD;
|
|
|
|
|
|
|
|
|
* already hold the valuerank. */
|
|
|
- if(variableArrayDimensionsSize > 0)
|
|
|
- return compatibleArrayDimensions(variableArrayDimensionsSize,
|
|
|
- variableArrayDimensions,
|
|
|
+ if(targetArrayDimensionsSize > 0)
|
|
|
+ return compatibleArrayDimensions(targetArrayDimensionsSize,
|
|
|
+ targetArrayDimensions,
|
|
|
value->arrayDimensionsSize,
|
|
|
value->arrayDimensions);
|
|
|
|
|
|
|
|
|
- return compatibleValueRankValue(variableValueRank, value);
|
|
|
+ return compatibleValueRankValue(targetValueRank, value);
|
|
|
}
|
|
|
|
|
|
|
|
@@ -281,7 +292,7 @@ writeValueRankAttribute(UA_VariableNode *node, UA_Int32 valueRank,
|
|
|
if(node->nodeClass == UA_NODECLASS_VARIABLETYPE &&
|
|
|
UA_Node_hasSubTypeOrInstances((const UA_Node*)node))
|
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
-
|
|
|
+
|
|
|
|
|
|
switch(constraintValueRank) {
|
|
|
case -3:
|
|
@@ -378,7 +389,7 @@ writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
|
|
|
return retval;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
|
|
|
UA_NodeId dtCopy = node->dataType;
|
|
|
retval = UA_NodeId_copy(dataType, &node->dataType);
|
|
@@ -387,49 +398,57 @@ writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
|
|
|
return retval;
|
|
|
}
|
|
|
UA_NodeId_deleteMembers(&dtCopy);
|
|
|
- return UA_STATUSCODE_GOOD;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+static UA_StatusCode
|
|
|
+readValueAttributeFromNode(const UA_VariableNode *vn, UA_DataValue *v,
|
|
|
+ UA_NumericRange *rangeptr) {
|
|
|
+ if(vn->value.data.callback.onRead)
|
|
|
+ vn->value.data.callback.onRead(vn->value.data.callback.handle,
|
|
|
+ vn->nodeId, &v->value, rangeptr);
|
|
|
+ if(rangeptr)
|
|
|
+ return UA_Variant_copyRange(&vn->value.data.value.value, &v->value, *rangeptr);
|
|
|
+ *v = vn->value.data.value;
|
|
|
+ v->value.storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+readValueAttributeFromDataSource(const UA_VariableNode *vn, UA_DataValue *v,
|
|
|
+ UA_TimestampsToReturn timestamps,
|
|
|
+ UA_NumericRange *rangeptr) {
|
|
|
+ if(!vn->value.dataSource.read)
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+ UA_Boolean sourceTimeStamp = (timestamps == UA_TIMESTAMPSTORETURN_SOURCE ||
|
|
|
+ timestamps == UA_TIMESTAMPSTORETURN_BOTH);
|
|
|
+ return vn->value.dataSource.read(vn->value.dataSource.handle, vn->nodeId,
|
|
|
+ sourceTimeStamp, rangeptr, v);
|
|
|
+}
|
|
|
+
|
|
|
static UA_StatusCode
|
|
|
readValueAttributeComplete(const UA_VariableNode *vn, UA_TimestampsToReturn timestamps,
|
|
|
- const UA_ReadValueId *id, UA_DataValue *v) {
|
|
|
+ const UA_String *indexRange, UA_DataValue *v) {
|
|
|
|
|
|
UA_NumericRange range;
|
|
|
UA_NumericRange *rangeptr = NULL;
|
|
|
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
- if(id->indexRange.length > 0) {
|
|
|
- retval = parse_numericrange(&id->indexRange, &range);
|
|
|
+ if(indexRange && indexRange->length > 0) {
|
|
|
+ retval = parse_numericrange(indexRange, &range);
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
|
return retval;
|
|
|
rangeptr = ⦥
|
|
|
}
|
|
|
|
|
|
- if(vn->valueSource == UA_VALUESOURCE_DATA) {
|
|
|
-
|
|
|
- if(vn->value.data.callback.onRead)
|
|
|
- vn->value.data.callback.onRead(vn->value.data.callback.handle,
|
|
|
- vn->nodeId, &v->value, rangeptr);
|
|
|
- if(!rangeptr) {
|
|
|
- *v = vn->value.data.value;
|
|
|
- v->value.storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
- } else {
|
|
|
- retval = UA_Variant_copyRange(&vn->value.data.value.value, &v->value, range);
|
|
|
- }
|
|
|
- } else {
|
|
|
-
|
|
|
- if(!vn->value.dataSource.read) {
|
|
|
- retval = UA_STATUSCODE_BADINTERNALERROR;
|
|
|
- } else {
|
|
|
- UA_Boolean sourceTimeStamp = (timestamps == UA_TIMESTAMPSTORETURN_SOURCE ||
|
|
|
- timestamps == UA_TIMESTAMPSTORETURN_BOTH);
|
|
|
- retval = vn->value.dataSource.read(vn->value.dataSource.handle, vn->nodeId,
|
|
|
- sourceTimeStamp, rangeptr, v);
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+ if(vn->valueSource == UA_VALUESOURCE_DATA)
|
|
|
+ retval = readValueAttributeFromNode(vn, v, rangeptr);
|
|
|
+ else
|
|
|
+ retval = readValueAttributeFromDataSource(vn, v, timestamps, rangeptr);
|
|
|
|
|
|
|
|
|
if(rangeptr)
|
|
@@ -439,55 +458,51 @@ readValueAttributeComplete(const UA_VariableNode *vn, UA_TimestampsToReturn time
|
|
|
|
|
|
UA_StatusCode
|
|
|
readValueAttribute(const UA_VariableNode *vn, UA_DataValue *v) {
|
|
|
- UA_TimestampsToReturn timestamps = UA_TIMESTAMPSTORETURN_NEITHER;
|
|
|
- UA_ReadValueId id;
|
|
|
- UA_ReadValueId_init(&id);
|
|
|
- return readValueAttributeComplete(vn, timestamps, &id, v);
|
|
|
+ return readValueAttributeComplete(vn, UA_TIMESTAMPSTORETURN_NEITHER, NULL, v);
|
|
|
}
|
|
|
|
|
|
static UA_StatusCode
|
|
|
-writeValueAttributeAfterTypeCheck(UA_VariableNode *node, const UA_DataValue *value,
|
|
|
- const UA_NumericRange *rangeptr) {
|
|
|
- UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
- if(!rangeptr) {
|
|
|
-
|
|
|
- UA_DataValue old_value = node->value.data.value;
|
|
|
- retval = UA_DataValue_copy(value, &node->value.data.value);
|
|
|
- if(retval == UA_STATUSCODE_GOOD)
|
|
|
- UA_DataValue_deleteMembers(&old_value);
|
|
|
- else
|
|
|
- node->value.data.value = old_value;
|
|
|
- } else {
|
|
|
-
|
|
|
- if(value->status == UA_STATUSCODE_GOOD) {
|
|
|
- if(!node->value.data.value.hasValue || !value->hasValue)
|
|
|
- return UA_STATUSCODE_BADINDEXRANGEINVALID;
|
|
|
-
|
|
|
-
|
|
|
- UA_Variant editableValue;
|
|
|
- const UA_Variant *v = &value->value;
|
|
|
- if(UA_Variant_isScalar(&value->value)) {
|
|
|
- editableValue = value->value;
|
|
|
- editableValue.arrayLength = 1;
|
|
|
- v = &editableValue;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
|
|
|
- v->data, v->arrayLength, *rangeptr);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- return retval;
|
|
|
- }
|
|
|
+writeValueAttributeWithoutRange(UA_VariableNode *node, const UA_DataValue *value) {
|
|
|
+ UA_DataValue old_value = node->value.data.value;
|
|
|
+ UA_StatusCode retval = UA_DataValue_copy(value, &node->value.data.value);
|
|
|
+ if(retval == UA_STATUSCODE_GOOD)
|
|
|
+ UA_DataValue_deleteMembers(&old_value);
|
|
|
+ else
|
|
|
+ node->value.data.value = old_value;
|
|
|
+ return retval;
|
|
|
+}
|
|
|
|
|
|
-
|
|
|
- node->value.data.value.hasStatus = value->hasStatus;
|
|
|
- node->value.data.value.status = value->status;
|
|
|
- node->value.data.value.hasSourceTimestamp = value->hasSourceTimestamp;
|
|
|
- node->value.data.value.sourceTimestamp = value->sourceTimestamp;
|
|
|
- node->value.data.value.hasSourcePicoseconds = value->hasSourcePicoseconds;
|
|
|
- node->value.data.value.sourcePicoseconds = value->sourcePicoseconds;
|
|
|
+static UA_StatusCode
|
|
|
+writeValueAttributeWithRange(UA_VariableNode *node, const UA_DataValue *value,
|
|
|
+ const UA_NumericRange *rangeptr) {
|
|
|
+
|
|
|
+ if(value->status != node->value.data.value.status ||
|
|
|
+ !value->hasValue || !node->value.data.value.hasValue)
|
|
|
+ return UA_STATUSCODE_BADINDEXRANGEINVALID;
|
|
|
+
|
|
|
+
|
|
|
+ UA_Variant editableValue;
|
|
|
+ const UA_Variant *v = &value->value;
|
|
|
+ if(UA_Variant_isScalar(&value->value)) {
|
|
|
+ editableValue = value->value;
|
|
|
+ editableValue.arrayLength = 1;
|
|
|
+ v = &editableValue;
|
|
|
}
|
|
|
- return retval;
|
|
|
+
|
|
|
+
|
|
|
+ UA_StatusCode retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
|
|
|
+ v->data, v->arrayLength, *rangeptr);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ return retval;
|
|
|
+
|
|
|
+
|
|
|
+ node->value.data.value.hasStatus = value->hasStatus;
|
|
|
+ node->value.data.value.status = value->status;
|
|
|
+ node->value.data.value.hasSourceTimestamp = value->hasSourceTimestamp;
|
|
|
+ node->value.data.value.sourceTimestamp = value->sourceTimestamp;
|
|
|
+ node->value.data.value.hasSourcePicoseconds = value->hasSourcePicoseconds;
|
|
|
+ node->value.data.value.sourcePicoseconds = value->sourcePicoseconds;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
}
|
|
|
|
|
|
UA_StatusCode
|
|
@@ -509,27 +524,28 @@ writeValueAttribute(UA_Server *server, UA_VariableNode *node,
|
|
|
UA_DataValue editableValue = *value;
|
|
|
editableValue.value.storageType = UA_VARIANT_DATA_NODELETE;
|
|
|
|
|
|
-
|
|
|
- if(!editableValue.hasSourceTimestamp) {
|
|
|
- editableValue.sourceTimestamp = UA_DateTime_now();
|
|
|
- editableValue.hasSourceTimestamp = true;
|
|
|
- }
|
|
|
-
|
|
|
|
|
|
if(value->hasValue) {
|
|
|
retval = typeCheckValue(server, &node->dataType, node->valueRank,
|
|
|
node->arrayDimensionsSize, node->arrayDimensions,
|
|
|
&value->value, rangeptr, &editableValue.value);
|
|
|
- if(retval != UA_STATUSCODE_GOOD) {
|
|
|
- UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
|
|
|
- "The new value does not match the variable definiton");
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
goto cleanup;
|
|
|
- }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if(!editableValue.hasSourceTimestamp) {
|
|
|
+ editableValue.sourceTimestamp = UA_DateTime_now();
|
|
|
+ editableValue.hasSourceTimestamp = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
if(node->valueSource == UA_VALUESOURCE_DATA) {
|
|
|
- retval = writeValueAttributeAfterTypeCheck(node, &editableValue, rangeptr);
|
|
|
+ if(!rangeptr)
|
|
|
+ retval = writeValueAttributeWithoutRange(node, &editableValue);
|
|
|
+ else
|
|
|
+ retval = writeValueAttributeWithRange(node, &editableValue, 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);
|
|
@@ -601,7 +617,6 @@ writeIsAbstractAttribute(UA_Node *node, UA_Boolean value) {
|
|
|
|
|
|
|
|
|
static const UA_String binEncoding = {sizeof("DefaultBinary")-1, (UA_Byte*)"DefaultBinary"};
|
|
|
-
|
|
|
|
|
|
|
|
|
#define CHECK_NODECLASS(CLASS) \
|
|
@@ -624,8 +639,8 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE){
|
|
|
+
|
|
|
+ if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE) {
|
|
|
v->hasStatus = true;
|
|
|
v->status = UA_STATUSCODE_BADINDEXRANGENODATA;
|
|
|
return;
|
|
@@ -639,7 +654,7 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+
|
|
|
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
switch(id->attributeId) {
|
|
|
case UA_ATTRIBUTEID_NODEID:
|
|
@@ -689,7 +704,7 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
|
|
|
case UA_ATTRIBUTEID_VALUE:
|
|
|
CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
|
|
|
retval = readValueAttributeComplete((const UA_VariableNode*)node,
|
|
|
- timestamps, id, v);
|
|
|
+ timestamps, &id->indexRange, v);
|
|
|
break;
|
|
|
case UA_ATTRIBUTEID_DATATYPE:
|
|
|
CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
|