diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index 1999d4c13..eb77026c1 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -1955,8 +1955,8 @@ DECODE_JSON(String) { /* No escaping */ if(*p != '\\') { /* In the ASCII range, but not a printable character */ - if(*p < 32 || *p == 127) - goto cleanup; + /* if(*p < 32 || *p == 127) */ + /* goto cleanup; */ *(pos++) = *(p++); continue; @@ -2084,21 +2084,27 @@ lookAheadForKey(const char *key, CtxJson *ctx, parseCtx->index++; /* Move to the first key */ while(parseCtx->index < parseCtx->tokenCount && parseCtx->tokenArray[parseCtx->index].start < end) { - UA_assert(getJsmnType(parseCtx) == JSMN_STRING); /* Key must be a string */ + /* Key must be a string (TODO: Make this an assert after replacing jsmn) */ + if(getJsmnType(parseCtx) != JSMN_STRING) + return UA_STATUSCODE_BADDECODINGERROR; - parseCtx->index++; /* Move to the value already */ - UA_assert(parseCtx->index < parseCtx->tokenCount); /* Key followed by a value */ + /* Move index to the value */ + parseCtx->index++; - /* Compare the key */ + /* Value for the key must exist (TODO: Make this an assert after replacing jsmn) */ + if(parseCtx->index >= parseCtx->tokenCount) + return UA_STATUSCODE_BADDECODINGERROR; + + /* Compare the key (previous index) */ if(jsoneq((char*)ctx->pos, &parseCtx->tokenArray[parseCtx->index-1], key) == 0) { - *resultIndex = parseCtx->index; /* Point to the value */ + *resultIndex = parseCtx->index; /* Point result to the current index */ ret = UA_STATUSCODE_GOOD; break; } - skipObject(parseCtx); + skipObject(parseCtx); /* Jump over the value (can also be an array or object) */ } - parseCtx->index = oldIndex; /* Restore index */ + parseCtx->index = oldIndex; /* Restore the old index */ return ret; } @@ -2360,6 +2366,7 @@ DECODE_JSON(DateTime) { denom *= 0.1; pos++; } + frac += 0.00000005; /* Correct rounding when converting to integer */ dt += (UA_DateTime)(frac * UA_DATETIME_SEC); } @@ -2771,7 +2778,9 @@ decodeFields(CtxJson *ctx, ParseCtx *parseCtx, for(size_t currObj = 0; currObj < objectCount && parseCtx->index < parseCtx->tokenCount; currObj++) { - UA_assert(getJsmnType(parseCtx) == JSMN_STRING); /* Key must be a string */ + /* Key must be a string (TODO: Convert to assert when jsmn is replaced) */ + if(getJsmnType(parseCtx) != JSMN_STRING) + return UA_STATUSCODE_BADDECODINGERROR; /* Start searching at the index of currObj */ for(size_t i = currObj; i < entryCount + currObj; i++) { diff --git a/tests/nodeset-compiler/check_nodeset_compiler_testnodeset.c b/tests/nodeset-compiler/check_nodeset_compiler_testnodeset.c index d0d77d684..aa5033ccb 100644 --- a/tests/nodeset-compiler/check_nodeset_compiler_testnodeset.c +++ b/tests/nodeset-compiler/check_nodeset_compiler_testnodeset.c @@ -175,6 +175,50 @@ START_TEST(checkInputArguments) { } END_TEST +START_TEST(checkGuid) { + UA_Variant out; + UA_Variant_init(&out); + UA_StatusCode status = UA_Server_readValue(server, UA_NODEID_NUMERIC(testNamespaceIndex, 7051), &out); + ck_assert(status == UA_STATUSCODE_GOOD); + ck_assert(out.type == &UA_TYPES[UA_TYPES_GUID]); + UA_Guid *scalarData = (UA_Guid *)out.data; + UA_Guid scalarGuidVal = UA_GUID("7822a391-de79-4a59-b08d-b70bc63fecec"); + ck_assert(UA_Guid_equal(scalarData, &scalarGuidVal)); + UA_Variant_clear(&out); + status = UA_Server_readValue(server, UA_NODEID_NUMERIC(testNamespaceIndex, 7052), &out); + ck_assert(status == UA_STATUSCODE_GOOD); + ck_assert(out.type == &UA_TYPES[UA_TYPES_GUID]); + ck_assert(out.arrayLength == 3); + UA_Guid *ArrayData = (UA_Guid *)out.data; + UA_Guid ArrayGuidVal[3] = {UA_GUID("7822a391-1111-4a59-b08d-b70bc63fecec"), + UA_GUID("7822a391-2222-4a59-b08d-b70bc63fecec"), + UA_GUID("7822a391-3333-4a59-b08d-b70bc63fecec")}; + ck_assert(UA_Guid_equal(&ArrayData[0], &ArrayGuidVal[0])); + ck_assert(UA_Guid_equal(&ArrayData[1], &ArrayGuidVal[1])); + ck_assert(UA_Guid_equal(&ArrayData[2], &ArrayGuidVal[2])); + UA_Variant_clear(&out); +} +END_TEST + +START_TEST(checkDataSetMetaData) { + UA_Variant out; + UA_Variant_init(&out); + UA_StatusCode status = UA_Server_readValue(server, UA_NODEID_NUMERIC(testNamespaceIndex, 6021), &out); + ck_assert(status == UA_STATUSCODE_GOOD); + ck_assert(out.type == &UA_TYPES[UA_TYPES_DATASETMETADATATYPE]); + UA_DataSetMetaDataType *p = (UA_DataSetMetaDataType *)out.data; + UA_String dataSetName = UA_STRING("DataSetName"); + ck_assert(UA_String_equal(&p->name, &dataSetName) == UA_TRUE); + ck_assert(p->fieldsSize == 1); + UA_String fieldName = UA_STRING("FieldName"); + ck_assert(UA_String_equal(&p->fields[0].name, &fieldName) == UA_TRUE); + UA_Guid guid = UA_GUID("10000000-2000-3000-4000-500000000000"); + ck_assert(UA_Guid_equal(&p->dataSetClassId, &guid) == UA_TRUE); + + UA_Variant_clear(&out); +} +END_TEST + static Suite *testSuite_Client(void) { Suite *s = suite_create("Server Nodeset Compiler"); TCase *tc_server = tcase_create("Server Testnodeset"); @@ -186,6 +230,8 @@ static Suite *testSuite_Client(void) { tcase_add_test(tc_server, readValueRank); tcase_add_test(tc_server, checkFrameValues); tcase_add_test(tc_server, checkInputArguments); + tcase_add_test(tc_server, checkGuid); + tcase_add_test(tc_server, checkDataSetMetaData); suite_add_tcase(s, tc_server); return s; } diff --git a/tests/nodeset-compiler/testnodeset.xml b/tests/nodeset-compiler/testnodeset.xml index d9625f7b7..f0d384f46 100644 --- a/tests/nodeset-compiler/testnodeset.xml +++ b/tests/nodeset-compiler/testnodeset.xml @@ -8,6 +8,7 @@ i=6 i=7 i=11 + i=14 i=35 i=37 i=38 @@ -21,6 +22,7 @@ ns=1;i=4002 ns=1;i=3006 ns=1;i=1003 + i=14523 @@ -600,4 +602,87 @@ + + TestGuidScalar + + i=63 + ns=1;i=5100 + + + + 7822a391-de79-4a59-b08d-b70bc63fecec + + + + + TestGuidArray + + i=63 + ns=1;i=5100 + + + + + 7822a391-1111-4a59-b08d-b70bc63fecec + + + 7822a391-2222-4a59-b08d-b70bc63fecec + + + 7822a391-3333-4a59-b08d-b70bc63fecec + + + + + + TestDataSetMetaData + + ns=1;i=5001 + i=68 + + + + + i=14794 + + + + + + + + + + DataSetName + + + + FieldName + + 0 + 10 + + i=10 + + -1 + + 0 + + 10000000-2000-3000-4000-500000000000 + + + + + + 10000000-2000-3000-4000-500000000000 + + + 1 + 1 + + + + + + diff --git a/tools/nodeset_compiler/backend_open62541.py b/tools/nodeset_compiler/backend_open62541.py index 1ee4c78f9..e295ab50a 100644 --- a/tools/nodeset_compiler/backend_open62541.py +++ b/tools/nodeset_compiler/backend_open62541.py @@ -238,7 +238,7 @@ _UA_END_DECLS writec("\n".join(code_global)) writec("\n") writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_begin(UA_Server *server, UA_UInt16* ns) {") - if isinstance(node, MethodNode): + if isinstance(node, MethodNode) or isinstance(node.parent, MethodNode): writec("#ifdef UA_ENABLE_METHODCALLS") writec(code) @@ -259,7 +259,7 @@ _UA_END_DECLS writec("return retVal;") - if isinstance(node, MethodNode): + if isinstance(node, MethodNode) or isinstance(node.parent, MethodNode): writec("#else") writec("return UA_STATUSCODE_GOOD;") writec("#endif /* UA_ENABLE_METHODCALLS */") @@ -267,10 +267,10 @@ _UA_END_DECLS writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_finish(UA_Server *server, UA_UInt16* ns) {") - if isinstance(node, MethodNode): + if isinstance(node, MethodNode) or isinstance(node.parent, MethodNode): writec("#ifdef UA_ENABLE_METHODCALLS") writec("return " + generateNodeCode_finish(node)) - if isinstance(node, MethodNode): + if isinstance(node, MethodNode) or isinstance(node.parent, MethodNode): writec("#else") writec("return UA_STATUSCODE_GOOD;") writec("#endif /* UA_ENABLE_METHODCALLS */") diff --git a/tools/nodeset_compiler/backend_open62541_datatypes.py b/tools/nodeset_compiler/backend_open62541_datatypes.py index 15cc63e86..884c5f89c 100644 --- a/tools/nodeset_compiler/backend_open62541_datatypes.py +++ b/tools/nodeset_compiler/backend_open62541_datatypes.py @@ -85,6 +85,12 @@ def generateQualifiedNameCode(value, alloc=False,): return u"UA_QUALIFIEDNAME{}(ns[{}], {})".format("_ALLOC" if alloc else "", str(value.ns), splitStringLiterals(vn)) +def generateGuidCode(value): + if not value or len(value) != 5: + return "UA_GUID_NULL" + else: + return "UA_GUID(\"{}\")".format('-'.join(value)) + def generateNodeIdCode(value): if not value: return "UA_NODEID_NUMERIC(0, 0)" @@ -154,7 +160,7 @@ def generateNodeValueCode(prepend , node, instanceName, valueName, global_var_co elif isinstance(node, DiagnosticInfo): raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented") elif isinstance(node, Guid): - raise Exception("generateNodeValueCode for type " + node.__class__.name + " not implemented") + return prepend + " = " + generateGuidCode(node.value) + ";" elif isinstance(node, ExtensionObject): if asIndirect == False: return prepend + " = *" + str(instanceName) + ";" @@ -163,6 +169,8 @@ def generateNodeValueCode(prepend , node, instanceName, valueName, global_var_co code = [] if idxList is None: raise Exception("No index was passed and the code generation cannot generate the array element") + if len(node) == 0: + return "\n".join(code) # Code generation for structure arrays with fields of type Buildin. # Example: # Structure [] @@ -174,6 +182,10 @@ def generateNodeValueCode(prepend , node, instanceName, valueName, global_var_co typeOfArray = encRule.member_type.name arrayName = encRule.name code.append("UA_STACKARRAY(UA_" + typeOfArray + ", " + arrayName+", {0});".format(len(node))) + # memset is used here instead of UA_Init. Finding the dataType nodeID (to get the type array) + # would require searching whole nodeset to match the type name + code.append("memset({arrayName}, 0, sizeof(UA_{typeOfArray}) * {arrayLength});".format(arrayName=arrayName, typeOfArray=typeOfArray, + arrayLength=len(node))) for idx,subv in enumerate(node): code.append(generateNodeValueCode(arrayName + "[" + str(idx) + "]", subv, instanceName, valueName, global_var_code, asIndirect, encRule=encRule, idxList=idx)) code.append(prepend + "Size = {0};".format(len(node))) @@ -201,6 +213,10 @@ def generateNodeValueCode(prepend , node, instanceName, valueName, global_var_co typeOfArray = encRule.member_type.name arrayName = encRule.name code.append("UA_STACKARRAY(UA_" + typeOfArray + ", " + arrayName+", {0});".format(len(node.value))) + # memset is used here instead of UA_Init. Finding the dataType nodeID (to get the type array) + # would require searching whole nodeset to match the type name + code.append("memset({arrayName}, 0, sizeof(UA_{typeOfArray}) * {arrayLength});".format(arrayName=arrayName, typeOfArray=typeOfArray, + arrayLength=len(node.value))) # Values is a list of lists # The current index must be passed so that the code path for evaluating lists has the current index value and can generate the code correctly. for idx,subv in enumerate(node.value): diff --git a/tools/nodeset_compiler/backend_open62541_nodes.py b/tools/nodeset_compiler/backend_open62541_nodes.py index b7af3666d..7042261d0 100644 --- a/tools/nodeset_compiler/backend_open62541_nodes.py +++ b/tools/nodeset_compiler/backend_open62541_nodes.py @@ -12,7 +12,7 @@ ### Copyright 2018 (c) Jannis Volker ### Copyright 2018 (c) Ralph Lange -from datatypes import ExtensionObject, NodeId, StatusCode, DiagnosticInfo, Guid, Value +from datatypes import ExtensionObject, NodeId, StatusCode, DiagnosticInfo, Value from nodes import ReferenceTypeNode, ObjectNode, VariableNode, VariableTypeNode, MethodNode, ObjectTypeNode, DataTypeNode, ViewNode from backend_open62541_datatypes import makeCIdentifier, generateLocalizedTextCode, generateQualifiedNameCode, generateNodeIdCode, \ generateExpandedNodeIdCode, generateNodeValueCode @@ -394,9 +394,7 @@ def generateValueCode(node, parentNode, nodeset, bootstrapping=True): if isArrayVariableNode(node, parentNode): # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;' - if isinstance(node.value[0], Guid): - logger.warn("Don't know how to print array of GUID in node " + str(parentNode.id)) - elif isinstance(node.value[0], DiagnosticInfo): + if isinstance(node.value[0], DiagnosticInfo): logger.warn("Don't know how to print array of DiagnosticInfo in node " + str(parentNode.id)) elif isinstance(node.value[0], StatusCode): logger.warn("Don't know how to print array of StatusCode in node " + str(parentNode.id)) @@ -423,9 +421,7 @@ def generateValueCode(node, parentNode, nodeset, bootstrapping=True): #scalar value else: # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;' - if isinstance(node.value[0], Guid): - logger.warn("Don't know how to print scalar GUID in node " + str(parentNode.id)) - elif isinstance(node.value[0], DiagnosticInfo): + if isinstance(node.value[0], DiagnosticInfo): logger.warn("Don't know how to print scalar DiagnosticInfo in node " + str(parentNode.id)) elif isinstance(node.value[0], StatusCode): logger.warn("Don't know how to print scalar StatusCode in node " + str(parentNode.id)) diff --git a/tools/nodeset_compiler/datatypes.py b/tools/nodeset_compiler/datatypes.py index 0c0e8e72c..d60508cfa 100644 --- a/tools/nodeset_compiler/datatypes.py +++ b/tools/nodeset_compiler/datatypes.py @@ -347,7 +347,7 @@ class Value(object): childValue = ebodypart.firstChild - if not childValue == ebodypart.ELEMENT_NODE: + if not childValue.nodeType == ebodypart.ELEMENT_NODE: childValue = getNextElementNode(childValue) for e in members: if isinstance(e, StructMember): @@ -355,7 +355,7 @@ class Value(object): if isinstance(e.member_type, BuiltinType): if e.is_array: values = [] - for el in ebodypart.childNodes: + for el in childValue.childNodes: if not el.nodeType == el.ELEMENT_NODE: continue t = self.getTypeByString(e.member_type.name, None) @@ -375,12 +375,13 @@ class Value(object): structure = Structure() structure.alias = e.name structure.value = [] - structure.__parseXMLSingleValue(childValue, parentDataTypeNode, parent, parser, alias=None, encodingPart=e.member_type) + if not len(childValue.childNodes) == 0: + structure.__parseXMLSingleValue(childValue, parentDataTypeNode, parent, parser, alias=None, encodingPart=e.member_type) self.value.append(structure) return structure elif isinstance(e.member_type, EnumerationType): t = self.getTypeByString("Int32", None) - t.parseXML(ebodypart) + t.parseXML(childValue) t.alias = e.name self.value.append(t) else: @@ -846,10 +847,15 @@ class Guid(Value): def parseXML(self, xmlvalue): self.checkXML(xmlvalue) - val = getXmlTextTrimmed(xmlvalue.firstChild) + # Support GUID in format: + # + # 01234567-89AB-CDEF-ABCD-0123456789AB + # + if len(xmlvalue.getElementsByTagName("String")) != 0: + val = getXmlTextTrimmed(xmlvalue.getElementsByTagName("String")[0].firstChild) if val is None: - self.value = [0, 0, 0, 0] # Catch XML by setting the value to a default + self.value = ['00000000', '0000', '0000', '0000', '000000000000'] # Catch XML by setting the value to a default else: self.value = val self.value = self.value.replace("{", "") @@ -862,9 +868,8 @@ class Guid(Value): except Exception: logger.error("Invalid formatting of Guid. Expected {01234567-89AB-CDEF-ABCD-0123456789AB}, got " + \ unicode(xmlvalue.firstChild.data)) - tmp = [0, 0, 0, 0, 0] + self.value = ['00000000', '0000', '0000', '0000', '000000000000'] if len(tmp) != 5: logger.error("Invalid formatting of Guid. Expected {01234567-89AB-CDEF-ABCD-0123456789AB}, got " + \ unicode(xmlvalue.firstChild.data)) - tmp = [0, 0, 0, 0] - self.value = tmp + self.value = ['00000000', '0000', '0000', '0000', '000000000000']