Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1998,10 +1998,15 @@ public ArrayOf<EnumValue> ReadEnumeratedArray(string fieldName)
{
var enums = new List<EnumValue>();

// TODO: We need to blindly discover the structure of the enumeration array
// We peek the first element and push the namespace, then move to it. But
// what if there is no first element? We need test coverage here.
XmlQualifiedName xmlName = Peek(XmlNodeType.Element);
if (xmlName is null)
{
throw ServiceResultException.Create(
StatusCodes.BadDecodingError,
"Unable to read field {0} in function {1}: The enumerated array does not contain any elements.",
fieldName,
nameof(ReadEnumeratedArray));
}
PushNamespace(xmlName.Namespace);

while (MoveToElement(xmlName.Name))
Expand Down
22 changes: 19 additions & 3 deletions Stack/Opc.Ua.Types/Encoders/XmlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2004,9 +2004,15 @@ public ArrayOf<EnumValue> ReadEnumeratedArray(string fieldName)
{
var enums = new List<EnumValue>();

// TODO: We need to blindly discover the structure of the enumeration array
// We peek the first element and push the namespace, then move to it. But
// what if there is no first element? We need test coverage here.
if (!MoveToElement(null))
{
throw ServiceResultException.Create(
StatusCodes.BadDecodingError,
"Unable to read field {0} in function {1}: The enumerated array does not contain any elements.",
fieldName,
nameof(ReadEnumeratedArray));
}

XmlQualifiedName xmlName = Peek(XmlNodeType.Element);
PushNamespace(xmlName.Namespace);

Expand All @@ -2025,6 +2031,16 @@ public ArrayOf<EnumValue> ReadEnumeratedArray(string fieldName)
EndField(fieldName);
return enums.ToArrayOf();
}

if (LastFieldWasEmpty)
{
throw ServiceResultException.Create(
StatusCodes.BadDecodingError,
"Unable to read field {0} in function {1}: The enumerated array does not contain any elements.",
fieldName,
nameof(ReadEnumeratedArray));
}

return isNil ? default : [];
}

Expand Down
22 changes: 22 additions & 0 deletions Tests/Opc.Ua.Types.Tests/Encoders/XmlDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,28 @@ public void ReadEnumeratedArrayThrowsWhenMaxArrayLengthExceeded()
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadEncodingLimitsExceeded));
}

[Test]
public void ReadEnumeratedArrayThrowsWhenNoElementsPresent()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<ListOfTestEnum xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">
<![CDATA[]]>
</ListOfTestEnum>
""";
using var reader = XmlReader.Create(new StringReader(xml));
using var decoder = new XmlDecoder(reader, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ServiceResultException ex = Assert.Throws<ServiceResultException>(
() => decoder.ReadEnumeratedArray("ListOfTestEnum"));

// Assert
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadDecodingError));
}

[Test]
public void DecodeMessageReturnsDecodedValue()
{
Expand Down
120 changes: 120 additions & 0 deletions Tests/Opc.Ua.Types.Tests/Encoders/XmlParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,7 @@ public void ReadEnumeratedThrowsWhenInvalid()

// Assert
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadDecodingError));
Assert.That(ex.Message, Does.Contain("does not contain any elements"));
}

[Test]
Expand Down Expand Up @@ -1544,6 +1545,125 @@ public void ReadEnumeratedArrayThrowsWhenMaxArrayLengthExceeded()
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadEncodingLimitsExceeded));
}

[Test]
public void ReadEnumeratedArrayThrowsBadDecodingErrorWhenNoElementsPresent()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<ListOfTestEnum xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">
<![CDATA[]]>
</ListOfTestEnum>
""";
using var decoder = new XmlParser(xml, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ServiceResultException ex = Assert.Throws<ServiceResultException>(
() => decoder.ReadEnumeratedArray("ListOfTestEnum"));

// Assert
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadDecodingError));
Assert.That(ex.Message, Does.Contain("does not contain any elements"));
}

[Test]
public void ReadEnumeratedArrayShouldThrowBadDecodingErrorWhenEmptyContent()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<ListOfTestEnum xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd"></ListOfTestEnum>
""";
using var decoder = new XmlParser(xml, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ServiceResultException ex = Assert.Throws<ServiceResultException>(
() => decoder.ReadEnumeratedArray("ListOfTestEnum"));

// Assert
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadDecodingError));
Assert.That(ex.Message, Does.Contain("does not contain any elements"));
}

[Test]
public void ReadEnumeratedArrayShouldThrowBadDecodingErrorWhenElementContainsOnlyText()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<ListOfTestEnum xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">invalid text</ListOfTestEnum>
""";
using var decoder = new XmlParser(xml, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ServiceResultException ex = Assert.Throws<ServiceResultException>(
() => decoder.ReadEnumeratedArray("ListOfTestEnum"));

// Assert
Assert.That(ex.StatusCode, Is.EqualTo(StatusCodes.BadDecodingError));
Assert.That(ex.Message, Does.Contain("does not contain any elements"));
}

[Test]
public void ReadEnumeratedArrayReturnsDefaultWhenNil()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<ListOfTestEnum xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
""";
using var decoder = new XmlParser(xml, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ArrayOf<EnumValue> result = decoder.ReadEnumeratedArray("ListOfTestEnum");

// Assert
Assert.That(result.IsNull, Is.True);
}

[Test]
public void ReadEnumeratedArrayReturnsDefaultWhenMissing()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<Other xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd" />
""";
using var decoder = new XmlParser(xml, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ArrayOf<EnumValue> result = decoder.ReadEnumeratedArray("ListOfTestEnum");

// Assert
Assert.That(result.IsNull, Is.True);
}

[Test]
public void ReadEnumeratedArrayShouldReturnCorrectCountForPopulatedArray()
{
// Arrange
ServiceMessageContext messageContext = CreateMockContext();
const string xml = """
<ListOfTestEnum xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">
<TestEnum>Value1</TestEnum>
<TestEnum>Value2</TestEnum>
</ListOfTestEnum>
""";
using var decoder = new XmlParser(xml, messageContext);
decoder.PushNamespace(Namespaces.OpcUaXsd);

// Act
ArrayOf<EnumValue> result = decoder.ReadEnumeratedArray("ListOfTestEnum");

// Assert
Assert.That(result.Count, Is.EqualTo(2));
}

[Test]
public void DecodeMessageReturnsDecodedValue()
{
Expand Down
Loading