diff --git a/src/Opc.Ua.Types/Encoders/BinaryDecoder.cs b/src/Opc.Ua.Types/Encoders/BinaryDecoder.cs index f045ca83e5..8d6dcc29d2 100644 --- a/src/Opc.Ua.Types/Encoders/BinaryDecoder.cs +++ b/src/Opc.Ua.Types/Encoders/BinaryDecoder.cs @@ -2432,6 +2432,12 @@ namespace Opc.Ua private T[] ReadArrayElements(int length, TReader reader) where TReader : struct, IElementReader { + // an empty array is immutable: all share one instance. + if (length == 0) + { + return Array.Empty(); + } + int capacity = GetInitialArrayCapacity(length); if (capacity < length) { @@ -2526,6 +2532,11 @@ namespace Opc.Ua /// against the remaining bytes by the caller. private T[] ReadFixedWidthArray(int length) where T : unmanaged { + if (length == 0) + { + return Array.Empty(); + } + // The caller verified the length against the remaining bytes. If // they are unknown the array grows with the bytes actually read. int capacity = GetRemainingLength() >= 0 diff --git a/tests/Opc.Ua.Types.Tests/Encoders/BinaryDecoderTests.cs b/tests/Opc.Ua.Types.Tests/Encoders/BinaryDecoderTests.cs index ee4e11e25f..be9dacee45 100644 --- a/tests/Opc.Ua.Types.Tests/Encoders/BinaryDecoderTests.cs +++ b/tests/Opc.Ua.Types.Tests/Encoders/BinaryDecoderTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using Moq; using NUnit.Framework; using Opc.Ua.Tests; @@ -767,6 +768,33 @@ namespace Opc.Ua.Types.Tests.Encoders Assert.That(result.Count, Is.Zero); } + /// + /// Zero-length arrays, such as the empty StringTable of every response + /// header, decode as empty, not null, and allocate nothing. + /// + [Test] + public void ReadZeroLengthArraysReturnsEmptyWithoutAllocating() + { + ITelemetryContext telemetryContext = NUnitTelemetryContext.Create(); + var messageContext = ServiceMessageContext.CreateEmpty(telemetryContext); + byte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF]; + using var decoder = new BinaryDecoder(buffer, messageContext); + + ArrayOf strings = decoder.ReadStringArray(null); + ArrayOf integers = decoder.ReadInt32Array(null); + ArrayOf nullStrings = decoder.ReadStringArray(null); + + Assert.That(strings.IsNull, Is.False); + Assert.That(strings.Count, Is.Zero); + Assert.That(MemoryMarshal.TryGetArray(strings.Memory, out ArraySegment stringArray), Is.True); + Assert.That(stringArray.Array, Is.SameAs(Array.Empty())); + Assert.That(integers.IsNull, Is.False); + Assert.That(integers.Count, Is.Zero); + Assert.That(MemoryMarshal.TryGetArray(integers.Memory, out ArraySegment integerArray), Is.True); + Assert.That(integerArray.Array, Is.SameAs(Array.Empty())); + Assert.That(nullStrings.IsNull, Is.True); + } + [Test] public void ReadEncodeableArrayReturnsMultipleElements() {