feat(core): Add XML en/decode support for Arrays

* Initial Variant encode included

Signed-off-by: Srdjan Usorac <uso@keba.com>
This commit is contained in:
Srdjan Usorac 2022-12-27 09:57:43 +01:00 committed by Julius Pfrommer
parent 17789c970c
commit dbdac8d2af
2 changed files with 844 additions and 18 deletions

View File

@ -109,7 +109,17 @@ XmlEncTypeDef xmlEncTypeDefs[UA_DATATYPEKINDS] = {
"</xs:sequence>"
"</xs:complexType>", 330}, /* ExtensionObject */
{"", 0}, /* DataValue */
{"", 0}, /* Variant */
{"<xs:complexType name=\"Variant\">"
"<xs:sequence>"
"<xs:element name=\"Value\" minOccurs=\"0\" nillable=\"true\">"
"<xs:complexType>"
"<xs:sequence>"
"<xs:any minOccurs=\"0\" processContents=\"lax\"/>"
"</xs:sequence>"
"</xs:complexType>"
"</xs:element>"
"</xs:sequence>"
"</xs:complexType>", 248}, /* Variant */
{"", 0}, /* DiagnosticInfo */
{"", 0}, /* Decimal */
{"", 0}, /* Enum */
@ -146,6 +156,43 @@ static const char* UA_XML_EXTENSIONOBJECT_TYPEID = "TypeId"; // NodeId
static const char* UA_XML_EXTENSIONOBJECT_BODY = "Body";
static const char* UA_XML_EXTENSIONOBJECT_BYTESTRING = "ByteString";
/* Variant */
static const char* UA_XML_VARIANT_VALUE = "Value";
const char* xsdTypeNames[UA_DATATYPEKINDS] = {
"xs:boolean", /* Boolean */
"xs:byte", /* SByte */
"xs:unsignedByte", /* Byte */
"xs:short", /* Int16 */
"xs:unsignedShort", /* UInt16 */
"xs:int", /* Int32 */
"xs:unsignedInt", /* UInt32 */
"xs:long", /* Int64 */
"xs:unsignedLong", /* UInt64 */
"xs:float", /* Float */
"xs:double", /* Double */
"xs:string", /* String */
"xs:dateTime", /* DateTime */
"tns:Guid", /* Guid */
"xs:base64Binary", /* ByteString */
"tns:XmlElement", /* XmlElement */
"tns:NodeId", /* NodeId */
"tns:ExpandedNodeId", /* ExpandedNodeId */
"tns:StatusCode", /* StatusCode */
"tns:QualifiedName", /* QualifiedName */
"tns:LocalizedText", /* LocalizedText */
"tns:ExtensionObject", /* ExtensionObject */
"tns:DataValue", /* DataValue */
"tns:Variant", /* Variant */
"tns:DiagnosticInfo", /* DiagnosticInfo */
"tns:Decimal", /* Decimal */
NULL, /* Enum */
NULL, /* Structure */
NULL, /* Structure with optional fields */
NULL, /* Union */
NULL /* BitfieldCluster */
};
/************/
/* Encoding */
/************/
@ -160,6 +207,15 @@ static const char* UA_XML_EXTENSIONOBJECT_BYTESTRING = "ByteString";
(type->typeKind < UA_DATATYPEKIND_GUID || \
type->typeKind == UA_DATATYPEKIND_BYTESTRING)
static bool
isNullXml(const void *p, const UA_DataType *type) {
if(UA_DataType_isNumeric(type) || type->typeKind == UA_DATATYPEKIND_BOOLEAN)
return false;
UA_STACKARRAY(char, buf, type->memSize);
memset(buf, 0, type->memSize);
return (UA_order(buf, p, type) == UA_ORDER_EQ);
}
static status UA_FUNC_ATTR_WARN_UNUSED_RESULT
xmlEncodeWriteChars(CtxXml *ctx, const char *c, size_t len) {
if(ctx->pos + len > ctx->end)
@ -533,6 +589,62 @@ ENCODE_XML(ExtensionObject) {
return ret;
}
static status
Array_encodeXml(CtxXml *ctx, const void *ptr, size_t length,
const UA_DataType *type) {
size_t arrNameLen = strlen("ListOf") + strlen(type->typeName);
char* arrName = (char*)UA_malloc((arrNameLen + 1) * sizeof(char));
arrName[0] = 'L';
arrName[1] = 'i';
arrName[2] = 's';
arrName[3] = 't';
arrName[4] = 'O';
arrName[5] = 'f';
for(size_t i = 0; i < strlen(type->typeName); ++i)
arrName[strlen("ListOf") + i] = type->typeName[i];
arrName[arrNameLen] = '\0';
status ret = writeXmlElemNameBegin(ctx, arrName);
if(!ptr)
goto finish;
uintptr_t uptr = (uintptr_t)ptr;
for(size_t i = 0; i < length && ret == UA_STATUSCODE_GOOD; ++i) {
if(isNullXml((const void*)uptr, type))
ret |= xmlEncodeWriteChars(ctx, "null", 4);
else
ret |= writeXmlElement(ctx, type->typeName, (const void*)uptr, type);
uptr += type->memSize;
}
finish:
ret |= writeXmlElemNameEnd(ctx, arrName, &UA_TYPES[UA_TYPES_VARIANT]);
UA_free(arrName);
return ret;
}
ENCODE_XML(Variant) {
if(!src->type)
return UA_STATUSCODE_BADENCODINGERROR;
/* Set the content type in the encoding mask */
const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO);
/* Set the array type in the encoding mask */
const bool isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL;
if((!isArray && !isBuiltin) || src->arrayDimensionsSize > 1)
return UA_STATUSCODE_BADNOTIMPLEMENTED;
status ret = writeXmlElemNameBegin(ctx, UA_XML_VARIANT_VALUE);
ret |= Array_encodeXml(ctx, src->data, src->arrayLength, src->type);
return ret | writeXmlElemNameEnd(ctx, UA_XML_VARIANT_VALUE, &UA_TYPES[UA_TYPES_VARIANT]);
}
static status
encodeXmlNotImplemented(CtxXml *ctx, const void *src, const UA_DataType *type) {
(void)ctx, (void)src, (void)type;
@ -563,7 +675,7 @@ const encodeXmlSignature encodeXmlJumpTable[UA_DATATYPEKINDS] = {
(encodeXmlSignature)LocalizedText_encodeXml, /* LocalizedText */
(encodeXmlSignature)ExtensionObject_encodeXml, /* ExtensionObject */
(encodeXmlSignature)encodeXmlNotImplemented, /* DataValue */
(encodeXmlSignature)encodeXmlNotImplemented, /* Variant */
(encodeXmlSignature)Variant_encodeXml, /* Variant */
(encodeXmlSignature)encodeXmlNotImplemented, /* DiagnosticInfo */
(encodeXmlSignature)encodeXmlNotImplemented, /* Decimal */
(encodeXmlSignature)encodeXmlNotImplemented, /* Enum */
@ -1471,8 +1583,36 @@ DECODE_XML(ExtensionObject) {
static status
Array_decodeXml(ParseCtxXml *ctx, void **dst, const UA_DataType *type) {
(void)dst, (void)type, (void)ctx;
return UA_STATUSCODE_BADNOTIMPLEMENTED;
if(strncmp(ctx->dataMembers[ctx->index]->name, "ListOf", strlen("ListOf")))
return UA_STATUSCODE_BADDECODINGERROR;
if(ctx->dataMembers[ctx->index]->type == XML_DATA_TYPE_PRIMITIVE) {
/* Return early for empty arrays */
*dst = UA_EMPTY_ARRAY_SENTINEL;
return UA_STATUSCODE_GOOD;
}
size_t length = ctx->dataMembers[ctx->index]->value.complex.membersSize;
ctx->index++; /* Go to first array member. */
/* Allocate memory */
*dst = UA_calloc(length, type->memSize);
if(*dst == NULL)
return UA_STATUSCODE_BADOUTOFMEMORY;
/* Decode array members */
uintptr_t ptr = (uintptr_t)*dst;
for(size_t i = 0; i < length; ++i) {
status ret = decodeXmlJumpTable[type->typeKind](ctx, (void*)ptr, type);
if(ret != UA_STATUSCODE_GOOD) {
UA_Array_delete(*dst, i + 1, type);
*dst = NULL;
return ret;
}
ptr += type->memSize;
}
return UA_STATUSCODE_GOOD;
}
static status
@ -1545,17 +1685,19 @@ addNewXmlMember(XmlData *parent, const char *name) {
static void
deleteData(XmlData *data) {
if(data->type == XML_DATA_TYPE_PRIMITIVE) {
if(data->value.primitive.value != NULL)
UA_free((void*)(uintptr_t)data->value.primitive.value);
if(data != NULL) {
if(data->type == XML_DATA_TYPE_PRIMITIVE) {
if(data->value.primitive.value != NULL)
UA_free((void*)(uintptr_t)data->value.primitive.value);
}
else {
for(size_t cnt = 0LU; cnt < data->value.complex.membersSize; ++cnt)
deleteData(data->value.complex.members[cnt]);
UA_free(data->value.complex.members);
}
UA_free((void*)(uintptr_t)data->name);
UA_free(data);
}
else {
for(size_t cnt = 0LU; cnt < data->value.complex.membersSize; ++cnt)
deleteData(data->value.complex.members[cnt]);
UA_free(data->value.complex.members);
}
UA_free((void*)(uintptr_t)data->name);
UA_free(data);
}
static void
@ -1669,7 +1811,6 @@ UA_decodeXml(const UA_ByteString *src, void *dst, const UA_DataType *type,
memset(&value, 0, sizeof(XmlValue));
memset(&parseCtx, 0, sizeof(XmlParsingCtx));
memset(&ctx, 0, sizeof(ParseCtxXml));
memset(dst, 0, type->memSize);
ctx.dataMembers = (XmlData**)UA_malloc(UA_XML_MAXMEMBERSCOUNT * sizeof(XmlData*));
ctx.parseCtx = &parseCtx;
@ -1689,12 +1830,12 @@ UA_decodeXml(const UA_ByteString *src, void *dst, const UA_DataType *type,
}
/* Decode */
memset(dst, 0, type->memSize); /* Initialize the value */
if(ctx.value->isArray)
ret = Array_decodeXml(&ctx, (void**)dst, type);
else
else {
memset(dst, 0, type->memSize); /* Initialize the value */
ret = decodeXmlJumpTable[type->typeKind](&ctx, dst, type);
}
if(ret != UA_STATUSCODE_GOOD) {
deleteData(ctx.value->data);

View File

@ -1903,6 +1903,435 @@ START_TEST(UA_ExtensionObject_print_xml_encode) {
UA_ExtensionObject_delete(src);
}
/* Array */
START_TEST(UA_Array_Variant_Byte_xml_encode) {
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_Byte zero[2] = {42, 43};
UA_Variant_setArrayCopy(src, zero, 2, &UA_TYPES[UA_TYPES_BYTE]);
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
const size_t variantXmlSectLen = 34;
size_t size = UA_calcSizeXml((void*)src, type, NULL);
ck_assert_uint_eq(size,
xmlEncTypeDefs[type->typeKind].xmlEncTypeDefLen + variantXmlSectLen + 55);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_encodeXml((void*)src, type, &buf, NULL);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"<Variant>"
"<Value>"
"<ListOfByte>"
"<Byte>42</Byte>"
"<Byte>43</Byte>"
"</ListOfByte>"
"</Value>"
"</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_Variant_delete(src);
}
END_TEST
START_TEST(UA_Array_Variant_UInt16_xml_encode) {
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_UInt16 zero[2] = {42, 43};
UA_Variant_setArrayCopy(src, zero, 2, &UA_TYPES[UA_TYPES_UINT16]);
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
const size_t variantXmlSectLen = 34;
size_t size = UA_calcSizeXml((void*)src, type, NULL);
ck_assert_uint_eq(size,
xmlEncTypeDefs[type->typeKind].xmlEncTypeDefLen + variantXmlSectLen + 67);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_encodeXml((void*)src, type, &buf, NULL);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"<Variant>"
"<Value>"
"<ListOfUInt16>"
"<UInt16>42</UInt16>"
"<UInt16>43</UInt16>"
"</ListOfUInt16>"
"</Value>"
"</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_Variant_delete(src);
}
END_TEST
START_TEST(UA_Array_Variant_Null_xml_encode) {
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_Variant_setArray(src, NULL, 0, &UA_TYPES[UA_TYPES_UINT16]);
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
size_t size = UA_calcSizeXml((void*)src, type, NULL);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_encodeXml((void*)src, type, &buf, NULL);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"<Variant>"
"<Value>"
"<ListOfUInt16>"
"</ListOfUInt16>"
"</Value>"
"</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_Variant_delete(src);
}
END_TEST
START_TEST(UA_Array_Variant_String_xml_encode) {
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_String zero[2] = {UA_STRING("eins"),UA_STRING("zwei")};
UA_Variant_setArrayCopy(src, zero, 2, &UA_TYPES[UA_TYPES_STRING]);
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
size_t size = UA_calcSizeXml((void*)src, type, NULL);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_encodeXml((void*)src, type, &buf, NULL);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"<Variant>"
"<Value>"
"<ListOfString>"
"<String>eins</String>"
"<String>zwei</String>"
"</ListOfString>"
"</Value>"
"</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_Variant_delete(src);
}
END_TEST
START_TEST(UA_Array_Variant_ExtensionObject_ByteString_xml_encode) {
UA_ExtensionObject eo1;
UA_ExtensionObject_init(&eo1);
eo1.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
eo1.content.encoded.typeId = UA_NODEID_NUMERIC(2, 1234);
eo1.content.encoded.body = UA_BYTESTRING_ALLOC("123456789012345678901234567890");
UA_ExtensionObject eo2;
UA_ExtensionObject_init(&eo2);
eo2.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
eo2.content.encoded.typeId = UA_NODEID_NUMERIC(3, 5678);
eo2.content.encoded.body = UA_BYTESTRING_ALLOC("98765432109876543210987654321");
UA_ExtensionObject eo3;
UA_ExtensionObject_init(&eo3);
eo3.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
eo3.content.encoded.typeId = UA_NODEID_NUMERIC(4, 9999);
eo3.content.encoded.body = UA_BYTESTRING_ALLOC("1357911131517192123252729");
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_ExtensionObject zero[3] = {eo1, eo2, eo3};
UA_Variant_setArrayCopy(src, zero, 3, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
size_t size = UA_calcSizeXml((void*)src, type, NULL);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_encodeXml((void*)src, type, &buf, NULL);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"<Variant>"
"<Value>"
"<ListOfExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=2;i=1234</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>123456789012345678901234567890</ByteString>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=3;i=5678</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>98765432109876543210987654321</ByteString>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=4;i=9999</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>1357911131517192123252729</ByteString>"
"</Body>"
"</ExtensionObject>"
"</ListOfExtensionObject>"
"</Value>"
"</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_ExtensionObject_clear(&eo1);
UA_ExtensionObject_clear(&eo2);
UA_ExtensionObject_clear(&eo3);
UA_Variant_delete(src);
}
END_TEST
START_TEST(UA_Array_Variant_ExtensionObject_Xml_xml_encode) {
UA_ExtensionObject eo1;
UA_ExtensionObject_init(&eo1);
eo1.encoding = UA_EXTENSIONOBJECT_ENCODED_XML;
eo1.content.encoded.typeId = UA_NODEID_NUMERIC(0, 297);
eo1.content.encoded.body = UA_BYTESTRING_ALLOC("<Argument>"
"<Name>ObjectToMoveOrCopy</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
UA_ExtensionObject eo2;
UA_ExtensionObject_init(&eo2);
eo2.encoding = UA_EXTENSIONOBJECT_ENCODED_XML;
eo2.content.encoded.typeId = UA_NODEID_NUMERIC(0, 297);
eo2.content.encoded.body = UA_BYTESTRING_ALLOC("<Argument>"
"<Name>TargetDirectory</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
UA_ExtensionObject eo3;
UA_ExtensionObject_init(&eo3);
eo3.encoding = UA_EXTENSIONOBJECT_ENCODED_XML;
eo3.content.encoded.typeId = UA_NODEID_NUMERIC(0, 297);
eo3.content.encoded.body = UA_BYTESTRING_ALLOC("<Argument>"
"<Name>CreateCopy</Name>"
"<DataType>"
"<Identifier>i=1</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
UA_ExtensionObject eo4;
UA_ExtensionObject_init(&eo4);
eo4.encoding = UA_EXTENSIONOBJECT_ENCODED_XML;
eo4.content.encoded.typeId = UA_NODEID_NUMERIC(0, 297);
eo4.content.encoded.body = UA_BYTESTRING_ALLOC("<Argument>"
"<Name>NewName</Name>"
"<DataType>"
"<Identifier>i=12</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_ExtensionObject zero[4] = {eo1, eo2, eo3, eo4};
UA_Variant_setArrayCopy(src, zero, 4, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
size_t size = UA_calcSizeXml((void*)src, type, NULL);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_encodeXml((void*)src, type, &buf, NULL);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"<Variant>"
"<Value>"
"<ListOfExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>ObjectToMoveOrCopy</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>TargetDirectory</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>CreateCopy</Name>"
"<DataType>"
"<Identifier>i=1</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>NewName</Name>"
"<DataType>"
"<Identifier>i=12</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"</ListOfExtensionObject>"
"</Value>"
"</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_ExtensionObject_clear(&eo1);
UA_ExtensionObject_clear(&eo2);
UA_ExtensionObject_clear(&eo3);
UA_ExtensionObject_clear(&eo4);
UA_Variant_delete(src);
}
END_TEST
START_TEST(UA_Array_Variant_ExtensionObject_print_xml_encode) {
UA_ExtensionObject eo1;
UA_ExtensionObject_init(&eo1);
eo1.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
eo1.content.encoded.typeId = UA_NODEID_NUMERIC(2, 1234);
eo1.content.encoded.body = UA_BYTESTRING_ALLOC("123456789012345678901234567890");
UA_ExtensionObject eo2;
UA_ExtensionObject_init(&eo2);
eo2.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
eo2.content.encoded.typeId = UA_NODEID_NUMERIC(3, 5678);
eo2.content.encoded.body = UA_BYTESTRING_ALLOC("98765432109876543210987654321");
UA_Variant *src = UA_Variant_new();
UA_Variant_init(src);
UA_ExtensionObject zero[2] = {eo1, eo2};
UA_Variant_setArrayCopy(src, zero, 2, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
UA_EncodeXmlOptions options;
memset(&options, 0, sizeof(UA_EncodeXmlOptions));
options.prettyPrint = true;
const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT];
size_t size = UA_calcSizeXml((void*)src, type, &options);
UA_ByteString buf;
UA_ByteString_allocBuffer(&buf, size + 1);
status s = UA_printXml((void*)src, type, &buf);
ck_assert_int_eq(s, UA_STATUSCODE_GOOD);
char result[size + 1];
sprintf(result, "%s"
"\n<Variant>"
"\n\t<Value>"
"\n\t\t<ListOfExtensionObject>"
"\n\t\t\t<ExtensionObject>"
"\n\t\t\t\t<TypeId>"
"\n\t\t\t\t\t<Identifier>ns=2;i=1234</Identifier>"
"\n\t\t\t\t</TypeId>"
"\n\t\t\t\t<Body>"
"\n\t\t\t\t\t<ByteString>123456789012345678901234567890</ByteString>"
"\n\t\t\t\t</Body>"
"\n\t\t\t</ExtensionObject>"
"\n\t\t\t<ExtensionObject>"
"\n\t\t\t\t<TypeId>"
"\n\t\t\t\t\t<Identifier>ns=3;i=5678</Identifier>"
"\n\t\t\t\t</TypeId>"
"\n\t\t\t\t<Body>"
"\n\t\t\t\t\t<ByteString>98765432109876543210987654321</ByteString>"
"\n\t\t\t\t</Body>"
"\n\t\t\t</ExtensionObject>"
"\n\t\t</ListOfExtensionObject>"
"\n\t</Value>"
"\n</Variant>",
xmlEncTypeDefs[type->typeKind].xmlEncTypeDef);
buf.data[size] = 0; /* zero terminate */
ck_assert_str_eq(result, (char*)buf.data);
UA_ByteString_clear(&buf);
UA_ExtensionObject_clear(&eo1);
UA_ExtensionObject_clear(&eo2);
UA_Variant_delete(src);
}
END_TEST
// ---------------------------DECODE-------------------------------------
@ -3708,6 +4137,247 @@ START_TEST(UA_ExtensionObject_InvalidBody_xml_decode) {
}
END_TEST
/* Array */
START_TEST(UA_Array_Boolean_xml_decode) {
UA_Boolean *out;
UA_ByteString buf = UA_STRING("<ListOfBoolean>"
"<Boolean>true</Boolean>"
"<Boolean>false</Boolean>"
"</ListOfBoolean>");
UA_StatusCode retval = UA_decodeXml(&buf, &out, &UA_TYPES[UA_TYPES_BOOLEAN], NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_int_eq(out[0], true);
ck_assert_int_eq(out[1], false);
UA_Array_delete(out, 2, &UA_TYPES[UA_TYPES_BOOLEAN]);
}
END_TEST
START_TEST(UA_Array_Number_xml_decode) {
UA_UInt32 *out;
UA_ByteString buf = UA_STRING("<ListOfUInt32>"
"<UInt32>0</UInt32>"
"<UInt32>1</UInt32>"
"<UInt32>2</UInt32>"
"<UInt32>3</UInt32>"
"<UInt32>4</UInt32>"
"<UInt32>5</UInt32>"
"<UInt32>6</UInt32>"
"<UInt32>7</UInt32>"
"<UInt32>8</UInt32>"
"<UInt32>9</UInt32>"
"</ListOfUInt32>");
UA_StatusCode retval = UA_decodeXml(&buf, &out, &UA_TYPES[UA_TYPES_UINT32], NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 10; ++i)
ck_assert_int_eq(out[i], (UA_UInt32)i);
UA_Array_delete(out, 10, &UA_TYPES[UA_TYPES_UINT32]);
}
END_TEST
START_TEST(UA_Array_Null_xml_decode) {
UA_UInt32 *out;
UA_ByteString buf = UA_STRING("<ListOfUInt32>"
"</ListOfUInt32>");
UA_StatusCode retval = UA_decodeXml(&buf, &out, &UA_TYPES[UA_TYPES_UINT32], NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_ptr_eq(out, (UA_UInt32*)UA_EMPTY_ARRAY_SENTINEL);
}
END_TEST
START_TEST(UA_Array_String_xml_decode) {
UA_String *out;
UA_ByteString buf = UA_STRING("<ListOfString>"
"<String>eins</String>"
"<String>zwei</String>"
"<String>drei</String>"
"<String>vier</String>"
"</ListOfString>");
UA_StatusCode retval = UA_decodeXml(&buf, &out, &UA_TYPES[UA_TYPES_STRING], NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
UA_String testData1 = UA_STRING("eins");
UA_String testData2 = UA_STRING("zwei");
UA_String testData3 = UA_STRING("drei");
UA_String testData4 = UA_STRING("vier");
ck_assert(UA_String_equal(&out[0], &testData1));
ck_assert(UA_String_equal(&out[1], &testData2));
ck_assert(UA_String_equal(&out[2], &testData3));
ck_assert(UA_String_equal(&out[3], &testData4));
UA_Array_delete(out, 4, &UA_TYPES[UA_TYPES_STRING]);
}
END_TEST
START_TEST(UA_Array_ExtensionObject_ByteString_xml_decode) {
UA_ExtensionObject *out;
UA_ByteString buf = UA_STRING("<ListOfExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=2;i=1234</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>123456789012345678901234567890</ByteString>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=3;i=5678</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>98765432109876543210987654321</ByteString>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=4;i=9999</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>1357911131517192123252729</ByteString>"
"</Body>"
"</ExtensionObject>"
"</ListOfExtensionObject>");
UA_StatusCode retval = UA_decodeXml(&buf, &out, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_int_eq(out[0].encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
UA_String testData1 = UA_STRING("123456789012345678901234567890");
ck_assert(UA_String_equal(&out[0].content.encoded.body, &testData1));
ck_assert_int_eq(out[0].content.encoded.typeId.namespaceIndex, 2);
ck_assert_int_eq(out[0].content.encoded.typeId.identifier.numeric, 1234);
ck_assert_int_eq(out[1].encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
UA_String testData2 = UA_STRING("98765432109876543210987654321");
ck_assert(UA_String_equal(&out[1].content.encoded.body, &testData2));
ck_assert_int_eq(out[1].content.encoded.typeId.namespaceIndex, 3);
ck_assert_int_eq(out[1].content.encoded.typeId.identifier.numeric, 5678);
ck_assert_int_eq(out[2].encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
UA_String testData3 = UA_STRING("1357911131517192123252729");
ck_assert(UA_String_equal(&out[2].content.encoded.body, &testData3));
ck_assert_int_eq(out[2].content.encoded.typeId.namespaceIndex, 4);
ck_assert_int_eq(out[2].content.encoded.typeId.identifier.numeric, 9999);
UA_Array_delete(out, 3, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
}
END_TEST
START_TEST(UA_Array_ExtensionObject_Xml_ByteString_xml_decode) {
UA_ExtensionObject *out;
UA_ByteString buf = UA_STRING("<ListOfExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>ObjectToMoveOrCopy</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>TargetDirectory</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>i=297</Identifier>"
"</TypeId>"
"<Body>"
"<Argument>"
"<Name>CreateCopy</Name>"
"<DataType>"
"<Identifier>i=1</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>"
"</Body>"
"</ExtensionObject>"
"<ExtensionObject>"
"<TypeId>"
"<Identifier>ns=4;i=9999</Identifier>"
"</TypeId>"
"<Body>"
"<ByteString>1357911131517192123252729</ByteString>"
"</Body>"
"</ExtensionObject>"
"</ListOfExtensionObject>");
UA_StatusCode retval = UA_decodeXml(&buf, &out, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_int_eq(out[0].encoding, UA_EXTENSIONOBJECT_ENCODED_XML);
UA_String testData1 = UA_STRING("<Argument>"
"<Name>ObjectToMoveOrCopy</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
ck_assert(UA_String_equal(&out[0].content.encoded.body, &testData1));
ck_assert_int_eq(out[0].content.encoded.typeId.namespaceIndex, 0);
ck_assert_int_eq(out[0].content.encoded.typeId.identifier.numeric, 297);
ck_assert_int_eq(out[1].encoding, UA_EXTENSIONOBJECT_ENCODED_XML);
UA_String testData2 = UA_STRING("<Argument>"
"<Name>TargetDirectory</Name>"
"<DataType>"
"<Identifier>i=17</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
ck_assert(UA_String_equal(&out[1].content.encoded.body, &testData2));
ck_assert_int_eq(out[1].content.encoded.typeId.namespaceIndex, 0);
ck_assert_int_eq(out[1].content.encoded.typeId.identifier.numeric, 297);
ck_assert_int_eq(out[2].encoding, UA_EXTENSIONOBJECT_ENCODED_XML);
UA_String testData3 = UA_STRING("<Argument>"
"<Name>CreateCopy</Name>"
"<DataType>"
"<Identifier>i=1</Identifier>"
"</DataType>"
"<ValueRank>-1</ValueRank>"
"<ArrayDimensions />"
"</Argument>");
ck_assert(UA_String_equal(&out[2].content.encoded.body, &testData3));
ck_assert_int_eq(out[2].content.encoded.typeId.namespaceIndex, 0);
ck_assert_int_eq(out[2].content.encoded.typeId.identifier.numeric, 297);
ck_assert_int_eq(out[3].encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING);
UA_String testData4 = UA_STRING("1357911131517192123252729");
ck_assert(UA_String_equal(&out[3].content.encoded.body, &testData4));
ck_assert_int_eq(out[3].content.encoded.typeId.namespaceIndex, 4);
ck_assert_int_eq(out[3].content.encoded.typeId.identifier.numeric, 9999);
UA_Array_delete(out, 4, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
}
END_TEST
static Suite *testSuite_builtin_xml(void) {
Suite *s = suite_create("Built-in Data Types 62541-5 Xml");
@ -3814,6 +4484,14 @@ static Suite *testSuite_builtin_xml(void) {
tcase_add_test(tc_xml_encode, UA_ExtensionObject_null_xml_encode);
tcase_add_test(tc_xml_encode, UA_ExtensionObject_print_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_Byte_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_UInt16_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_Null_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_String_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_ExtensionObject_ByteString_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_ExtensionObject_Xml_xml_encode);
tcase_add_test(tc_xml_encode, UA_Array_Variant_ExtensionObject_print_xml_encode);
suite_add_tcase(s, tc_xml_encode);
TCase *tc_xml_decode = tcase_create("xml_decode");
@ -3922,6 +4600,13 @@ static Suite *testSuite_builtin_xml(void) {
tcase_add_test(tc_xml_decode, UA_ExtensionObject_EncodedXml_4_xml_decode);
tcase_add_test(tc_xml_decode, UA_ExtensionObject_InvalidBody_xml_decode);
tcase_add_test(tc_xml_decode, UA_Array_Boolean_xml_decode);
tcase_add_test(tc_xml_decode, UA_Array_Number_xml_decode);
tcase_add_test(tc_xml_decode, UA_Array_Null_xml_decode);
tcase_add_test(tc_xml_decode, UA_Array_String_xml_decode);
tcase_add_test(tc_xml_decode, UA_Array_ExtensionObject_ByteString_xml_decode);
tcase_add_test(tc_xml_decode, UA_Array_ExtensionObject_Xml_ByteString_xml_decode);
suite_add_tcase(s, tc_xml_decode);
return s;