mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-16 21:02:51 +08:00
Improve ArraySegmentStream with ReadOnlyMemory/Span signatures, avoid extra alloc/copy (#2556)
- Implement `int Read(Span<byte> buffer)` and `void Write(ReadOnlySpan<byte> buffer)` in ArraySegmentStream - for ArraySegmentStream streaming lower the CPU load for .NET8 OPC UA binary encoding/decoding by 50% - Implement dispose to return buffers that were not transferred. - Implement a GetSequence in ArraySegmentStream
This commit is contained in:
@@ -22,10 +22,11 @@ jobs:
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./.azurepipelines/get-matrix.ps1
|
||||
arguments: -FileName azure-pipelines.yml -AgentTable ${{ parameters.agents }}
|
||||
arguments: -FileName *.Tests.csproj -AgentTable ${{ parameters.agents }}
|
||||
- job: testall${{ parameters.jobnamesuffix }}
|
||||
displayName: Tests (${{ parameters.framework }})
|
||||
dependsOn: testprep${{ parameters.jobnamesuffix }}
|
||||
timeoutInMinutes: 120
|
||||
strategy:
|
||||
matrix: $[dependencies.testprep${{ parameters.jobnamesuffix }}.outputs['testmatrix.jobMatrix'] ]
|
||||
variables:
|
||||
@@ -62,12 +63,12 @@ jobs:
|
||||
displayName: Restore ${{ parameters.configuration }}
|
||||
inputs:
|
||||
command: restore
|
||||
projects: '**/*.Tests.csproj'
|
||||
projects: $(file)
|
||||
arguments: '${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }}'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Test ${{ parameters.configuration }}
|
||||
timeoutInMinutes: 60
|
||||
timeoutInMinutes: 30
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.csproj'
|
||||
projects: $(file)
|
||||
arguments: '--no-restore ${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }}'
|
||||
|
||||
@@ -336,6 +336,12 @@ dotnet_diagnostic.IDE1005.severity =
|
||||
# CA1305: Specify IFormatProvider
|
||||
dotnet_diagnostic.CA1305.severity = warning
|
||||
|
||||
# CA1819: Properties should not return arrays
|
||||
dotnet_diagnostic.CA1819.severity = silent
|
||||
|
||||
# CA1721: The property name is confusing given the existence of another method with the same name.
|
||||
dotnet_diagnostic.CA1721.severity = silent
|
||||
|
||||
# exclude generated code
|
||||
[**/Generated/*.cs]
|
||||
generated_code = true
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
|
||||
#define STREAM_WITH_SPAN_SUPPORT
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -63,6 +68,37 @@ namespace Opc.Ua.Bindings
|
||||
|
||||
SetCurrentBuffer(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a writeable stream that creates buffers as necessary.
|
||||
/// </summary>
|
||||
/// <param name="bufferManager">The buffer manager.</param>
|
||||
public ArraySegmentStream(BufferManager bufferManager)
|
||||
: this(bufferManager, bufferManager.MaxSuggestedBufferSize, 0, bufferManager.MaxSuggestedBufferSize)
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (m_buffers != null && m_bufferManager != null)
|
||||
{
|
||||
for (int ii = 0; ii < m_buffers.Count; ii++)
|
||||
{
|
||||
m_bufferManager.ReturnBuffer(m_buffers[ii].Array, "ArraySegmentStream.Dispose");
|
||||
}
|
||||
m_buffers.Clear();
|
||||
m_buffers = null;
|
||||
}
|
||||
m_bufferManager = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
@@ -81,44 +117,77 @@ namespace Opc.Ua.Bindings
|
||||
buffers.Add(new ArraySegment<byte>(m_buffers[ii].Array, m_buffers[ii].Offset, GetBufferCount(ii)));
|
||||
}
|
||||
|
||||
m_buffers.Clear();
|
||||
ClearBuffers();
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns sequence of the buffers stored in the stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The buffers ownership is transferred to the sequence,
|
||||
/// the stream can be disposed.
|
||||
/// The new owner is responisble to dispose the sequence after use.
|
||||
/// </remarks>
|
||||
public BufferSequence GetSequence(string owner)
|
||||
{
|
||||
if (m_buffers.Count == 0)
|
||||
{
|
||||
return new BufferSequence(m_bufferManager, owner, null, ReadOnlySequence<byte>.Empty);
|
||||
}
|
||||
|
||||
int endIndex = GetBufferCount(0);
|
||||
var firstSegment = new BufferSegment(m_buffers[0].Array, m_buffers[0].Offset, endIndex);
|
||||
m_bufferManager.TransferBuffer(m_buffers[0].Array, owner);
|
||||
BufferSegment nextSegment = firstSegment;
|
||||
for (int ii = 1; ii < m_buffers.Count; ii++)
|
||||
{
|
||||
m_bufferManager.TransferBuffer(m_buffers[ii].Array, owner);
|
||||
endIndex = GetBufferCount(ii);
|
||||
nextSegment = nextSegment.Append(m_buffers[ii].Array, m_buffers[ii].Offset, endIndex);
|
||||
}
|
||||
|
||||
var sequence = new ReadOnlySequence<byte>(firstSegment, 0, nextSegment, endIndex);
|
||||
|
||||
ClearBuffers();
|
||||
|
||||
return new BufferSequence(m_bufferManager, owner, firstSegment, sequence);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Overridden Methods
|
||||
/// <summary cref="Stream.CanRead" />
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
get { return m_buffers != null; }
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.CanSeek" />
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return true; }
|
||||
get { return m_buffers != null; }
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.CanWrite" />
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
get { return m_buffers != null; }
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.Flush" />
|
||||
/// <inheritdoc/>
|
||||
public override void Flush()
|
||||
{
|
||||
// nothing to do.
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.Length" />
|
||||
/// <inheritdoc/>
|
||||
public override long Length
|
||||
{
|
||||
get { return GetAbsoluteLength(); }
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.Position" />
|
||||
/// <inheritdoc/>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
@@ -132,7 +201,7 @@ namespace Opc.Ua.Bindings
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.ReadByte()" />
|
||||
/// <inheritdoc/>
|
||||
public override int ReadByte()
|
||||
{
|
||||
do
|
||||
@@ -148,7 +217,11 @@ namespace Opc.Ua.Bindings
|
||||
// copy the bytes requested.
|
||||
if (bytesLeft > 0)
|
||||
{
|
||||
#if STREAM_WITH_SPAN_SUPPORT
|
||||
return m_currentBuffer[m_currentPosition++];
|
||||
#else
|
||||
return m_currentBuffer.Array[m_currentBuffer.Offset + m_currentPosition++];
|
||||
#endif
|
||||
}
|
||||
|
||||
// move to next buffer.
|
||||
@@ -157,7 +230,54 @@ namespace Opc.Ua.Bindings
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.Read(byte[], int, int)" />
|
||||
#if STREAM_WITH_SPAN_SUPPORT
|
||||
/// <summary>
|
||||
/// Helper to benchmark the performance of the stream.
|
||||
/// </summary>
|
||||
internal int ReadMemoryStream(Span<byte> buffer) => base.Read(buffer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
int count = buffer.Length;
|
||||
int offset = 0;
|
||||
int bytesRead = 0;
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
// check for end of stream.
|
||||
if (m_currentBuffer.Array == null)
|
||||
{
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
int bytesLeft = GetBufferCount(m_bufferIndex) - m_currentPosition;
|
||||
|
||||
// copy the bytes requested.
|
||||
if (bytesLeft > count)
|
||||
{
|
||||
m_currentBuffer.AsSpan(m_currentPosition, count).CopyTo(buffer.Slice(offset));
|
||||
bytesRead += count;
|
||||
m_currentPosition += count;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
// copy the bytes available and move to next buffer.
|
||||
m_currentBuffer.AsSpan(m_currentPosition, bytesLeft).CopyTo(buffer.Slice(offset));
|
||||
bytesRead += bytesLeft;
|
||||
|
||||
offset += bytesLeft;
|
||||
count -= bytesLeft;
|
||||
|
||||
// move to next buffer.
|
||||
SetCurrentBuffer(m_bufferIndex + 1);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int bytesRead = 0;
|
||||
@@ -195,7 +315,7 @@ namespace Opc.Ua.Bindings
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.Seek" />
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
@@ -223,6 +343,13 @@ namespace Opc.Ua.Bindings
|
||||
throw new IOException("Cannot seek beyond the beginning of the stream.");
|
||||
}
|
||||
|
||||
// special case
|
||||
if (offset == 0)
|
||||
{
|
||||
SetCurrentBuffer(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int position = (int)offset;
|
||||
|
||||
if (position >= GetAbsolutePosition())
|
||||
@@ -247,13 +374,13 @@ namespace Opc.Ua.Bindings
|
||||
throw new IOException("Cannot seek beyond the end of the stream.");
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.SetLength(long)" />
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.WriteByte(byte)" />
|
||||
/// <inheritdoc/>
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
do
|
||||
@@ -266,16 +393,12 @@ namespace Opc.Ua.Bindings
|
||||
// copy the byte requested.
|
||||
if (bytesLeft >= 1)
|
||||
{
|
||||
#if STREAM_WITH_SPAN_SUPPORT
|
||||
m_currentBuffer[m_currentPosition] = value;
|
||||
#else
|
||||
m_currentBuffer.Array[m_currentBuffer.Offset + m_currentPosition] = value;
|
||||
m_currentPosition++;
|
||||
|
||||
if (m_bufferIndex == m_buffers.Count - 1)
|
||||
{
|
||||
if (m_endOfLastBuffer < m_currentPosition)
|
||||
{
|
||||
m_endOfLastBuffer = m_currentPosition;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
UpdateCurrentPosition(1);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -286,7 +409,47 @@ namespace Opc.Ua.Bindings
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/// <summary cref="Stream.Write(byte[],int,int)" />
|
||||
#if STREAM_WITH_SPAN_SUPPORT
|
||||
/// <summary>
|
||||
/// Helper to benchmark the performance of the stream.
|
||||
/// </summary>
|
||||
internal void WriteMemoryStream(ReadOnlySpan<byte> buffer) => base.Write(buffer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
int count = buffer.Length;
|
||||
int offset = 0;
|
||||
while (count > 0)
|
||||
{
|
||||
// check for end of stream.
|
||||
CheckEndOfStream();
|
||||
|
||||
int bytesLeft = m_currentBuffer.Count - m_currentPosition;
|
||||
|
||||
// copy the bytes requested.
|
||||
if (bytesLeft >= count)
|
||||
{
|
||||
buffer.Slice(offset, count).CopyTo(m_currentBuffer.AsSpan(m_currentPosition));
|
||||
|
||||
UpdateCurrentPosition(count);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the bytes available and move to next buffer.
|
||||
buffer.Slice(offset, bytesLeft).CopyTo(m_currentBuffer.AsSpan(m_currentPosition));
|
||||
|
||||
offset += bytesLeft;
|
||||
count -= bytesLeft;
|
||||
|
||||
// move to next buffer.
|
||||
SetCurrentBuffer(m_bufferIndex + 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
while (count > 0)
|
||||
@@ -301,15 +464,7 @@ namespace Opc.Ua.Bindings
|
||||
{
|
||||
Array.Copy(buffer, offset, m_currentBuffer.Array, m_currentPosition + m_currentBuffer.Offset, count);
|
||||
|
||||
m_currentPosition += count;
|
||||
|
||||
if (m_bufferIndex == m_buffers.Count - 1)
|
||||
{
|
||||
if (m_endOfLastBuffer < m_currentPosition)
|
||||
{
|
||||
m_endOfLastBuffer = m_currentPosition;
|
||||
}
|
||||
}
|
||||
UpdateCurrentPosition(count);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -325,9 +480,14 @@ namespace Opc.Ua.Bindings
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary cref="MemoryStream.ToArray()" />
|
||||
/// <inheritdoc/>
|
||||
public override byte[] ToArray()
|
||||
{
|
||||
if (m_buffers == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ArraySegmentStream));
|
||||
}
|
||||
|
||||
int absoluteLength = GetAbsoluteLength();
|
||||
if (absoluteLength == 0)
|
||||
{
|
||||
@@ -353,6 +513,23 @@ namespace Opc.Ua.Bindings
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Update the current buffer count.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateCurrentPosition(int count)
|
||||
{
|
||||
m_currentPosition += count;
|
||||
|
||||
if (m_bufferIndex == m_buffers.Count - 1)
|
||||
{
|
||||
if (m_endOfLastBuffer < m_currentPosition)
|
||||
{
|
||||
m_endOfLastBuffer = m_currentPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current buffer.
|
||||
/// </summary>
|
||||
@@ -446,6 +623,17 @@ namespace Opc.Ua.Bindings
|
||||
SetCurrentBuffer(m_buffers.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the buffers and resets the state variables.
|
||||
/// </summary>
|
||||
private void ClearBuffers()
|
||||
{
|
||||
m_buffers.Clear();
|
||||
m_bufferIndex = 0;
|
||||
m_endOfLastBuffer = 0;
|
||||
SetCurrentBuffer(0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
@@ -125,6 +125,7 @@ namespace Opc.Ua.Bindings
|
||||
? ArrayPool<byte>.Shared
|
||||
: ArrayPool<byte>.Create(maxBufferSize + kCookieLength, 4);
|
||||
m_maxBufferSize = maxBufferSize;
|
||||
m_maxSuggestedBufferSize = DetermineSuggestedBufferSize(maxBufferSize);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -336,11 +337,54 @@ namespace Opc.Ua.Bindings
|
||||
#endif
|
||||
m_arrayPool.Return(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the suggested max rent size for data in the buffers.
|
||||
/// </summary>
|
||||
/// <param name="maxBufferSize">The max buffer size configured.</param>
|
||||
private int DetermineSuggestedBufferSize(int maxBufferSize)
|
||||
{
|
||||
int bufferArrayPoolSize = RoundUpToPowerOfTwo(maxBufferSize);
|
||||
int maxDataRentSize = RoundUpToPowerOfTwo(maxBufferSize + kCookieLength);
|
||||
if (bufferArrayPoolSize != maxDataRentSize)
|
||||
{
|
||||
Utils.LogWarning("BufferManager: Max buffer size {0} + cookie length {1} may waste memory because it allocates buffers in the next bucket!", maxBufferSize, kCookieLength);
|
||||
return bufferArrayPoolSize - kCookieLength;
|
||||
}
|
||||
return maxBufferSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to round up to the next power of two.
|
||||
/// </summary>
|
||||
private int RoundUpToPowerOfTwo(int value)
|
||||
{
|
||||
int result = 1;
|
||||
|
||||
while (result < value && result != 0)
|
||||
{
|
||||
result <<= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the max size of data in the buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Due to the underlying implementation of the ArrayPool,
|
||||
/// the actual buffer size may be larger than this value.
|
||||
/// To avoid memory waste, use this value as a guideline
|
||||
/// for the maximum buffer size when taking buffers.
|
||||
/// </remarks>
|
||||
public int MaxSuggestedBufferSize => m_maxSuggestedBufferSize;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly string m_name;
|
||||
private readonly int m_maxBufferSize;
|
||||
private readonly int m_maxSuggestedBufferSize;
|
||||
#if TRACE_MEMORY
|
||||
private int m_buffersTaken = 0;
|
||||
#endif
|
||||
@@ -348,7 +392,7 @@ namespace Opc.Ua.Bindings
|
||||
private const byte kCookieLocked = 0xa5;
|
||||
private const byte kCookieUnlocked = 0x5a;
|
||||
#if TRACK_MEMORY
|
||||
const byte kCookieLength = 5;
|
||||
private const byte kCookieLength = 5;
|
||||
class Allocation
|
||||
{
|
||||
public int Id;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to build a ReadOnlySequence from a set of buffers.
|
||||
/// </summary>
|
||||
public sealed class BufferSegment : ReadOnlySequenceSegment<byte>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the base array of the buffer.
|
||||
/// </summary>
|
||||
public byte[] Array() => m_array;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a buffer segment.
|
||||
/// </summary>
|
||||
public BufferSegment(byte[] array, int offset, int length)
|
||||
{
|
||||
Memory = new ReadOnlyMemory<byte>(array, offset, length);
|
||||
m_array = array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a buffer to the sequence.
|
||||
/// </summary>
|
||||
public BufferSegment Append(byte[] array, int offset, int length)
|
||||
{
|
||||
var segment = new BufferSegment(array, offset, length) {
|
||||
RunningIndex = RunningIndex + Memory.Length
|
||||
};
|
||||
Next = segment;
|
||||
return segment;
|
||||
}
|
||||
|
||||
#region Private Fields
|
||||
private byte[] m_array;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to hold a sequence of buffers until disposed.
|
||||
/// </summary>
|
||||
public sealed class BufferSequence : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The constructor to create the sequence of buffers.
|
||||
/// </summary>
|
||||
public BufferSequence(BufferManager bufferManager, string owner, BufferSegment firstSegment, ReadOnlySequence<byte> sequence)
|
||||
{
|
||||
m_bufferManager = bufferManager;
|
||||
m_owner = owner;
|
||||
m_firstSegment = firstSegment;
|
||||
m_sequence = sequence;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
BufferSegment segment = m_firstSegment;
|
||||
while (segment != null)
|
||||
{
|
||||
m_bufferManager.ReturnBuffer(segment.Array(), m_owner);
|
||||
segment = (BufferSegment)segment.Next;
|
||||
}
|
||||
m_sequence = ReadOnlySequence<byte>.Empty;
|
||||
m_firstSegment = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a sequence which can be used to access the buffers.
|
||||
/// </summary>
|
||||
public ReadOnlySequence<byte> Sequence => m_sequence;
|
||||
|
||||
#region Private
|
||||
private BufferManager m_bufferManager;
|
||||
private BufferSegment m_firstSegment;
|
||||
private ReadOnlySequence<byte> m_sequence;
|
||||
private string m_owner;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -283,41 +283,31 @@ namespace Opc.Ua
|
||||
/// </summary>
|
||||
public string CloseAndReturnText()
|
||||
{
|
||||
Close();
|
||||
if (m_memoryStream == null)
|
||||
try
|
||||
{
|
||||
if (m_stream is MemoryStream memoryStream)
|
||||
InternalClose(false);
|
||||
if (m_memoryStream == null)
|
||||
{
|
||||
return Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
if (m_stream is MemoryStream memoryStream)
|
||||
{
|
||||
return Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
}
|
||||
throw new NotSupportedException("Cannot get text from external stream. Use Close or MemoryStream instead.");
|
||||
}
|
||||
throw new NotSupportedException("Cannot get text from external stream. Use Close or MemoryStream instead.");
|
||||
return Encoding.UTF8.GetString(m_memoryStream.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_writer?.Dispose();
|
||||
m_writer = null;
|
||||
}
|
||||
return Encoding.UTF8.GetString(m_memoryStream.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes writing and returns the text length.
|
||||
/// The StreamWriter is disposed.
|
||||
/// </summary>
|
||||
public int Close()
|
||||
{
|
||||
if (!m_dontWriteClosing)
|
||||
{
|
||||
if (m_topLevelIsArray)
|
||||
{
|
||||
m_writer.Write(s_rightSquareBracket);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_writer.Write(s_rightCurlyBrace);
|
||||
}
|
||||
}
|
||||
|
||||
m_writer.Flush();
|
||||
int length = (int)m_writer.BaseStream.Position;
|
||||
m_writer.Dispose();
|
||||
m_writer = null;
|
||||
return length;
|
||||
}
|
||||
public int Close() => InternalClose(true);
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
@@ -339,7 +329,7 @@ namespace Opc.Ua
|
||||
{
|
||||
if (m_writer != null)
|
||||
{
|
||||
Close();
|
||||
InternalClose(true);
|
||||
m_writer = null;
|
||||
}
|
||||
|
||||
@@ -2580,6 +2570,33 @@ namespace Opc.Ua
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Completes writing and returns the text length.
|
||||
/// </summary>
|
||||
private int InternalClose(bool dispose)
|
||||
{
|
||||
if (!m_dontWriteClosing)
|
||||
{
|
||||
if (m_topLevelIsArray)
|
||||
{
|
||||
m_writer.Write(s_rightSquareBracket);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_writer.Write(s_rightCurlyBrace);
|
||||
}
|
||||
}
|
||||
|
||||
m_writer.Flush();
|
||||
int length = (int)m_writer.BaseStream.Position;
|
||||
if (dispose)
|
||||
{
|
||||
m_writer.Dispose();
|
||||
m_writer = null;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a DiagnosticInfo to the stream.
|
||||
/// Ignores InnerDiagnosticInfo field if the nesting level
|
||||
|
||||
@@ -276,6 +276,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// </summary>
|
||||
protected void EncodeDecodeComplexType(
|
||||
IServiceMessageContext encoderContext,
|
||||
MemoryStreamType memoryStreamType,
|
||||
EncodingType encoderType,
|
||||
StructureType structureType,
|
||||
ExpandedNodeId nodeId,
|
||||
@@ -291,7 +292,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
TestContext.Out.WriteLine(expected);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, encoderContext, encoderStream, typeof(DataValue)))
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Core.Tests.Types.Encoders;
|
||||
|
||||
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
{
|
||||
@@ -86,6 +87,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
[Theory]
|
||||
[Category("ComplexTypes")]
|
||||
public void ReEncodeComplexType(
|
||||
MemoryStreamType memoryStreamType,
|
||||
EncodingType encoderType,
|
||||
StructureType structureType
|
||||
)
|
||||
@@ -96,7 +98,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
object emittedType = Activator.CreateInstance(complexType);
|
||||
var baseType = emittedType as BaseComplexType;
|
||||
FillStructWithValues(baseType, true);
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, structureType, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, structureType, nodeId, emittedType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +108,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
[Theory]
|
||||
[Category("ComplexTypes")]
|
||||
public void ReEncodeStructureWithOptionalFieldsComplexType(
|
||||
MemoryStreamType memoryStreamType,
|
||||
EncodingType encoderType,
|
||||
StructureFieldParameter structureFieldParameter
|
||||
)
|
||||
@@ -118,17 +121,17 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
var builtInType = structureFieldParameter.BuiltInType;
|
||||
TestContext.Out.WriteLine($"Optional Field: {structureFieldParameter.BuiltInType} is the only value.");
|
||||
baseType[structureFieldParameter.Name] = DataGenerator.GetRandom(builtInType);
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
TestContext.Out.WriteLine($"Optional Field: {structureFieldParameter.BuiltInType} is null.");
|
||||
baseType[structureFieldParameter.Name] = null;
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
TestContext.Out.WriteLine($"Optional Field: {structureFieldParameter.BuiltInType} is null, all other fields have random values.");
|
||||
FillStructWithValues(baseType, true);
|
||||
baseType[structureFieldParameter.Name] = null;
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
TestContext.Out.WriteLine($"Optional Field: {structureFieldParameter.BuiltInType} has random value.");
|
||||
baseType[structureFieldParameter.Name] = DataGenerator.GetRandom(builtInType);
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -138,6 +141,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
[Theory]
|
||||
[Category("ComplexTypes")]
|
||||
public void ReEncodeUnionComplexType(
|
||||
MemoryStreamType memoryStreamType,
|
||||
EncodingType encoderType,
|
||||
StructureFieldParameter structureFieldParameter
|
||||
)
|
||||
@@ -150,10 +154,10 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
var builtInType = structureFieldParameter.BuiltInType;
|
||||
TestContext.Out.WriteLine($"Union Field: {structureFieldParameter.BuiltInType} is random.");
|
||||
baseType[structureFieldParameter.Name] = DataGenerator.GetRandom(builtInType);
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, StructureType.Union, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, StructureType.Union, nodeId, emittedType);
|
||||
TestContext.Out.WriteLine($"Union Field: {structureFieldParameter.BuiltInType} is null.");
|
||||
baseType[structureFieldParameter.Name] = null;
|
||||
EncodeDecodeComplexType(EncoderContext, encoderType, StructureType.Union, nodeId, emittedType);
|
||||
EncodeDecodeComplexType(EncoderContext, memoryStreamType, encoderType, StructureType.Union, nodeId, emittedType);
|
||||
}
|
||||
#endregion Test Methods
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeStructureRev(
|
||||
JsonValidationData jsonValidationData)
|
||||
JsonValidationData jsonValidationData
|
||||
)
|
||||
{
|
||||
ExpandedNodeId nodeId;
|
||||
Type complexType;
|
||||
@@ -131,6 +132,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
ExtensionObject extensionObject = CreateExtensionObject(StructureType.Structure, nodeId, emittedType);
|
||||
EncodeJsonComplexTypeVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
MemoryStreamType.MemoryStream,
|
||||
extensionObject,
|
||||
true,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
@@ -142,7 +144,8 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeStructureNonRev(
|
||||
JsonValidationData jsonValidationData)
|
||||
JsonValidationData jsonValidationData
|
||||
)
|
||||
{
|
||||
ExpandedNodeId nodeId;
|
||||
Type complexType;
|
||||
@@ -153,6 +156,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
ExtensionObject extensionObject = CreateExtensionObject(StructureType.Structure, nodeId, emittedType);
|
||||
EncodeJsonComplexTypeVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
MemoryStreamType.ArraySegmentStream,
|
||||
extensionObject,
|
||||
false,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
@@ -165,7 +169,8 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeOptionalFieldsRev(
|
||||
JsonValidationData jsonValidationData)
|
||||
JsonValidationData jsonValidationData
|
||||
)
|
||||
{
|
||||
ExpandedNodeId nodeId;
|
||||
Type complexType;
|
||||
@@ -176,6 +181,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
ExtensionObject extensionObject = CreateExtensionObject(StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
EncodeJsonComplexTypeVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
MemoryStreamType.ArraySegmentStream,
|
||||
extensionObject,
|
||||
true,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
@@ -199,6 +205,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
ExtensionObject extensionObject = CreateExtensionObject(StructureType.StructureWithOptionalFields, nodeId, emittedType);
|
||||
EncodeJsonComplexTypeVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
MemoryStreamType.RecyclableMemoryStream,
|
||||
extensionObject,
|
||||
false,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
@@ -210,7 +217,8 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeUnionRev(
|
||||
JsonValidationData jsonValidationData)
|
||||
JsonValidationData jsonValidationData
|
||||
)
|
||||
{
|
||||
ExpandedNodeId nodeId;
|
||||
Type complexType;
|
||||
@@ -221,6 +229,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
ExtensionObject extensionObject = CreateExtensionObject(StructureType.Union, nodeId, emittedType);
|
||||
EncodeJsonComplexTypeVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
MemoryStreamType.ArraySegmentStream,
|
||||
extensionObject,
|
||||
true,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
@@ -232,7 +241,8 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeUnionNonRev(
|
||||
JsonValidationData jsonValidationData)
|
||||
JsonValidationData jsonValidationData
|
||||
)
|
||||
{
|
||||
ExpandedNodeId nodeId;
|
||||
Type complexType;
|
||||
@@ -243,6 +253,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
ExtensionObject extensionObject = CreateExtensionObject(StructureType.Union, nodeId, emittedType);
|
||||
EncodeJsonComplexTypeVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
MemoryStreamType.ArraySegmentStream,
|
||||
extensionObject,
|
||||
false,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
@@ -253,6 +264,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
#region Private Methods
|
||||
protected void EncodeJsonComplexTypeVerifyResult(
|
||||
BuiltInType builtInType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
ExtensionObject data,
|
||||
bool useReversibleEncoding,
|
||||
string expected,
|
||||
@@ -269,7 +281,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
_ = PrettifyAndValidateJson(expected);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(
|
||||
EncodingType.Json, EncoderContext, encoderStream,
|
||||
|
||||
@@ -36,6 +36,7 @@ using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Core.Tests.Types.Encoders;
|
||||
|
||||
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
{
|
||||
@@ -148,7 +149,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// Test the functionality to create a custom complex type.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public async Task CreateMockTypeAsync(EncodingType encodingType)
|
||||
public async Task CreateMockTypeAsync(EncodingType encodingType, MemoryStreamType memoryStreamType)
|
||||
{
|
||||
var mockResolver = new MockResolver();
|
||||
|
||||
@@ -255,7 +256,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
};
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(EncodingType.Json, encoderContext, encoderStream, carType))
|
||||
{
|
||||
@@ -267,7 +268,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
_ = PrettifyAndValidateJson(Encoding.UTF8.GetString(buffer));
|
||||
|
||||
// test encoder/decoder
|
||||
EncodeDecodeComplexType(encoderContext, encodingType, StructureType.Structure, nodeId, car);
|
||||
EncodeDecodeComplexType(encoderContext, memoryStreamType, encodingType, StructureType.Structure, nodeId, car);
|
||||
|
||||
// Test extracting type definition
|
||||
|
||||
@@ -281,7 +282,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// Test the functionality to create a custom complex type.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public async Task CreateMockArrayTypeAsync(EncodingType encodingType)
|
||||
public async Task CreateMockArrayTypeAsync(EncodingType encodingType, MemoryStreamType memoryStreamType)
|
||||
{
|
||||
var mockResolver = new MockResolver();
|
||||
|
||||
@@ -430,7 +431,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
};
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(EncodingType.Json, encoderContext, encoderStream, arraysTypes, false))
|
||||
{
|
||||
@@ -442,7 +443,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
_ = PrettifyAndValidateJson(Encoding.UTF8.GetString(buffer));
|
||||
|
||||
// test encoder/decoder
|
||||
EncodeDecodeComplexType(encoderContext, encodingType, StructureType.Structure, dataTypeNode.NodeId, arrays);
|
||||
EncodeDecodeComplexType(encoderContext, memoryStreamType, encodingType, StructureType.Structure, dataTypeNode.NodeId, arrays);
|
||||
|
||||
// Test extracting type definition
|
||||
|
||||
@@ -456,7 +457,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
/// Create a complex type with a single scalar or array type, with default and random values .
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public async Task CreateMockSingleTypeAsync(EncodingType encodingType, TestType typeDescription, bool randomValues, TestRanks testRank)
|
||||
public async Task CreateMockSingleTypeAsync(EncodingType encodingType, MemoryStreamType memoryStreamType, TestType typeDescription, bool randomValues, TestRanks testRank)
|
||||
{
|
||||
SetRepeatedRandomSeed();
|
||||
|
||||
@@ -611,7 +612,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
};
|
||||
|
||||
byte [] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(EncodingType.Json, encoderContext, encoderStream, arraysTypes, false))
|
||||
{
|
||||
@@ -623,7 +624,7 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
|
||||
_ = PrettifyAndValidateJson(Encoding.UTF8.GetString(buffer));
|
||||
|
||||
// test encoder/decoder
|
||||
EncodeDecodeComplexType(encoderContext, encodingType, StructureType.Structure, dataTypeNode.NodeId, testType);
|
||||
EncodeDecodeComplexType(encoderContext, memoryStreamType, encodingType, StructureType.Structure, dataTypeNode.NodeId, testType);
|
||||
|
||||
// Test extracting type definition
|
||||
|
||||
|
||||
@@ -614,13 +614,16 @@ namespace Opc.Ua.Client.Tests
|
||||
sre = Assert.Throws<ServiceResultException>(() => session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType)));
|
||||
|
||||
// TODO: Both channel should return BadSecureChannelClosed
|
||||
if (endpoint.EndpointUrl.ToString().StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
|
||||
if (!(StatusCodes.BadSecureChannelClosed == sre.StatusCode))
|
||||
{
|
||||
Assert.AreEqual(StatusCodes.BadSessionIdInvalid, sre.StatusCode, sre.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(StatusCodes.BadUnknownResponse, sre.StatusCode, sre.Message);
|
||||
if (endpoint.EndpointUrl.ToString().StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
|
||||
{
|
||||
Assert.AreEqual(StatusCodes.BadSessionIdInvalid, sre.StatusCode, sre.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(StatusCodes.BadUnknownResponse, sre.StatusCode, sre.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ namespace Opc.Ua.Core.Tests.Stack.Bindings
|
||||
public class BufferManagerBenchmarks
|
||||
{
|
||||
//[Params(8192, 65535, 1024 * 1024 - 1)]
|
||||
public int BufferSize { get; set; } = 65535;
|
||||
public int BufferSize { get; set; } = TcpMessageLimits.DefaultMaxBufferSize;
|
||||
|
||||
//[Params( /*8,*/ 64, 256, 1024)]
|
||||
public int Allocations { get; set; } = 256;
|
||||
@@ -0,0 +1,239 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Bindings;
|
||||
|
||||
namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
[TestFixture, Category("BinaryEncoder")]
|
||||
[SetCulture("en-us"), SetUICulture("en-us")]
|
||||
[NonParallelizable]
|
||||
[MemoryDiagnoser]
|
||||
[DisassemblyDiagnoser]
|
||||
public class BinaryDecoderBenchmarks : EncoderBenchmarks
|
||||
{
|
||||
/// <summary>
|
||||
/// Test decoding with internal memory stream.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BinaryDecoderInternalMemoryStreamTest()
|
||||
{
|
||||
using (var binaryDecoder = new BinaryDecoder(m_encodedByteArray, m_context))
|
||||
{
|
||||
TestDecoding(binaryDecoder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test decoding with ArrayPool memory stream.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BinaryDecoderArraySegmentStreamTest()
|
||||
{
|
||||
using (var memoryStream = new ArraySegmentStream(m_encodedBufferList))
|
||||
using (var binaryDecoder = new BinaryDecoder(memoryStream, m_context))
|
||||
{
|
||||
TestDecoding(binaryDecoder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test decoding with ArrayPool memory stream that has no span support.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BinaryDecoderArraySegmentStreamNoSpanTest()
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
using (var arraySegmentStream = new ArraySegmentStreamNoSpan(m_encodedBufferList))
|
||||
#else
|
||||
using (var arraySegmentStream = new ArraySegmentStream(m_encodedBufferList))
|
||||
#endif
|
||||
using (var binaryDecoder = new BinaryDecoder(arraySegmentStream, m_context))
|
||||
{
|
||||
TestDecoding(binaryDecoder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark decoding with memory stream.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[Test]
|
||||
public void BinaryDecoderMemoryStream()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(m_encodedByteArray))
|
||||
{
|
||||
BinaryDecoder_Stream(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark decoding with array segment memory stream.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryDecoderArraySegmentStream()
|
||||
{
|
||||
using (var arraySegmentStream = new ArraySegmentStream(m_encodedBufferList))
|
||||
{
|
||||
BinaryDecoder_Stream(arraySegmentStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark decoding with array segment memory stream without span support.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryDecoderArraySegmentStreamNoSpan()
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
using (var arraySegmentStream = new ArraySegmentStreamNoSpan(m_encodedBufferList))
|
||||
#else
|
||||
using (var arraySegmentStream = new ArraySegmentStream(m_encodedBufferList))
|
||||
#endif
|
||||
{
|
||||
BinaryDecoder_Stream(arraySegmentStream);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
private void TestStreamDecode(MemoryStream memoryStream)
|
||||
{
|
||||
using (var binaryDecoder = new BinaryDecoder(memoryStream, m_context))
|
||||
{
|
||||
TestDecoding(binaryDecoder);
|
||||
TestDecoding(binaryDecoder);
|
||||
binaryDecoder.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void BinaryDecoder_Stream(MemoryStream memoryStream)
|
||||
{
|
||||
using (var binaryDecoder = new BinaryDecoder(memoryStream, m_context))
|
||||
{
|
||||
TestDecoding(binaryDecoder);
|
||||
TestDecoding(binaryDecoder);
|
||||
binaryDecoder.Close();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Test Setup
|
||||
[OneTimeSetUp]
|
||||
public new void OneTimeSetUp()
|
||||
{
|
||||
base.OneTimeSetUp();
|
||||
InitializeEncodedTestData();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public new void OneTimeTearDown()
|
||||
{
|
||||
base.OneTimeTearDown();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Benchmark Setup
|
||||
/// <summary>
|
||||
/// Set up some variables for benchmarks.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public new void GlobalSetup()
|
||||
{
|
||||
base.GlobalSetup();
|
||||
InitializeEncodedBenchmarkData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tear down benchmark variables.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public new void GlobalCleanup()
|
||||
{
|
||||
base.GlobalCleanup();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Initialize encoded data.
|
||||
/// </summary>
|
||||
private void InitializeEncodedTestData()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(StreamBufferSize))
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
TestEncoding(binaryEncoder);
|
||||
m_encodedByteArray = binaryEncoder.CloseAndReturnBuffer();
|
||||
}
|
||||
|
||||
using (var memoryStream = new ArraySegmentStream(m_bufferManager))
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
TestEncoding(binaryEncoder);
|
||||
binaryEncoder.Close();
|
||||
m_encodedBufferList = memoryStream.GetBuffers("writer");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize encoded data.
|
||||
/// </summary>
|
||||
private void InitializeEncodedBenchmarkData()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(StreamBufferSize))
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
TestEncoding(binaryEncoder);
|
||||
m_encodedByteArray = binaryEncoder.CloseAndReturnBuffer();
|
||||
}
|
||||
|
||||
using (var memoryStream = new ArraySegmentStream(m_bufferManager))
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
TestEncoding(binaryEncoder);
|
||||
binaryEncoder.Close();
|
||||
m_encodedBufferList = memoryStream.GetBuffers("writer");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private byte[] m_encodedByteArray;
|
||||
private BufferCollection m_encodedBufferList;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2023 The OPC Foundation, Inc. All rights reserved.
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
@@ -27,11 +27,10 @@
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.IO;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Bindings;
|
||||
|
||||
@@ -42,167 +41,206 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
[NonParallelizable]
|
||||
[MemoryDiagnoser]
|
||||
[DisassemblyDiagnoser]
|
||||
public class BinaryEncoderBenchmarks
|
||||
public class BinaryEncoderBenchmarks : EncoderBenchmarks
|
||||
{
|
||||
const int kBufferSize = 4096;
|
||||
|
||||
[Params(1, 2, 8, 64)]
|
||||
public int PayLoadSize { get; set; } = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with internal memory stream.
|
||||
/// Test encoding with internal memory stream.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderConstructor2()
|
||||
public void BinaryEncoderInternalMemoryStreamTest()
|
||||
{
|
||||
using (var binaryEncoder = new BinaryEncoder(m_context))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
_ = binaryEncoder.CloseAndReturnBuffer();
|
||||
var result = binaryEncoder.CloseAndReturnBuffer();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test encoding with internal memory stream,
|
||||
/// uses reflection to get array from memory stream.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BinaryEncoderConstructorStreamwriterReflection2()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(StreamBufferSize))
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
var result = binaryEncoder.CloseAndReturnBuffer();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with ArrayPool memory stream.
|
||||
/// Test encoding with ArrayPool memory stream.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderConstructor3()
|
||||
[Theory]
|
||||
public void BinaryEncoderArraySegmentStreamTest(bool toArray)
|
||||
{
|
||||
using (var stream = new ArraySegmentStream(m_bufferManager, kBufferSize, 0, kBufferSize))
|
||||
using (var memoryStream = new ArraySegmentStream(m_bufferManager))
|
||||
{
|
||||
using (var binaryEncoder = new BinaryEncoder(stream, m_context, false))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
_ = binaryEncoder.CloseAndReturnBuffer();
|
||||
}
|
||||
TestStreamEncode(memoryStream, toArray);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test encoding with memory stream kept open.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void BinaryEncoderMemoryStreamTest(bool toArray)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream(StreamBufferSize))
|
||||
{
|
||||
TestStreamEncode(memoryStream, toArray);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test encoding with recyclable memory stream kept open.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void BinaryEncoderRecyclableMemoryStream(bool toArray)
|
||||
{
|
||||
using (var memoryStream = new RecyclableMemoryStream(m_memoryManager))
|
||||
{
|
||||
TestStreamEncode(memoryStream, toArray);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with memory stream kept open.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Benchmark(Baseline = true)]
|
||||
[Test]
|
||||
public void BinaryEncoderConstructorStreamwriter2()
|
||||
public void BinaryEncoderMemoryStream()
|
||||
{
|
||||
using (IEncoder binaryEncoder = new BinaryEncoder(m_memoryStream, m_context, true))
|
||||
using (var memoryStream = new MemoryStream(StreamBufferSize))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
int length = binaryEncoder.Close();
|
||||
var result = Encoding.UTF8.GetString(m_memoryStream.ToArray());
|
||||
BinaryEncoder_StreamLeaveOpen(memoryStream);
|
||||
// get buffer for write
|
||||
_ = memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encoding test with memory stream kept open.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderStreamLeaveOpenMemoryStream()
|
||||
{
|
||||
BinaryEncoder_StreamLeaveOpen(m_memoryStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with recyclable memory stream kept open.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderStreamLeaveOpenRecyclableMemoryStream()
|
||||
public void BinaryEncoderRecyclableMemoryStream()
|
||||
{
|
||||
BinaryEncoder_StreamLeaveOpen(m_recyclableMemoryStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encoding test with memory stream kept open.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderStreamLeaveOpenArraySegmentStream()
|
||||
{
|
||||
BinaryEncoder_StreamLeaveOpen(m_arraySegmentStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with memory stream kept open,
|
||||
/// use internal reflection to get string from memory stream.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderConstructorStreamwriterReflection2()
|
||||
{
|
||||
using (var binaryEncoder = new BinaryEncoder(m_memoryStream, m_context, true))
|
||||
using (var recyclableMemoryStream = new RecyclableMemoryStream(m_memoryManager))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
var result = binaryEncoder.CloseAndReturnBuffer();
|
||||
BinaryEncoder_StreamLeaveOpen(recyclableMemoryStream);
|
||||
// get buffers for write
|
||||
_ = recyclableMemoryStream.GetReadOnlySequence();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with array segment memory stream kept open.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderArraySegmentStream()
|
||||
{
|
||||
using (var arraySegmentStream = new ArraySegmentStream(m_bufferManager))
|
||||
{
|
||||
BinaryEncoder_StreamLeaveOpen(arraySegmentStream);
|
||||
// get buffers and return them to buffer manager
|
||||
var buffers = arraySegmentStream.GetBuffers("writer");
|
||||
foreach (var buffer in buffers)
|
||||
{
|
||||
m_bufferManager.ReturnBuffer(buffer.Array, "testreturn");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding with array segment memory stream kept open,
|
||||
/// to compare the version without span support.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void BinaryEncoderArraySegmentStreamNoSpan()
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
using (var arraySegmentStream = new ArraySegmentStreamNoSpan(m_bufferManager))
|
||||
#else
|
||||
using (var arraySegmentStream = new ArraySegmentStream(m_bufferManager))
|
||||
#endif
|
||||
{
|
||||
BinaryEncoder_StreamLeaveOpen(arraySegmentStream);
|
||||
// get buffers and return them to buffer manager
|
||||
var buffers = arraySegmentStream.GetBuffers("writer");
|
||||
foreach (var buffer in buffers)
|
||||
{
|
||||
m_bufferManager.ReturnBuffer(buffer.Array, "testreturn");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
private void BinaryEncoder_StreamLeaveOpen(MemoryStream stream)
|
||||
private void TestStreamEncode(MemoryStream memoryStream, bool toArray)
|
||||
{
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
_ = binaryEncoder.Close();
|
||||
}
|
||||
using (var binaryEncoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(binaryEncoder);
|
||||
if (toArray)
|
||||
{
|
||||
int length = binaryEncoder.Close();
|
||||
Assert.AreEqual(length, memoryStream.Position);
|
||||
var result = memoryStream.ToArray();
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual(length, result.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = binaryEncoder.CloseAndReturnBuffer();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int BinaryEncoder_StreamLeaveOpen(MemoryStream memoryStream, bool testResult = false)
|
||||
{
|
||||
int length1;
|
||||
int length2;
|
||||
m_memoryStream.Position = 0;
|
||||
using (IEncoder encoder = new BinaryEncoder(stream, m_context, true))
|
||||
using (var encoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(encoder);
|
||||
length1 = encoder.Close();
|
||||
}
|
||||
using (IEncoder encoder = new BinaryEncoder(stream, m_context, true))
|
||||
using (var encoder = new BinaryEncoder(memoryStream, m_context, true))
|
||||
{
|
||||
TestEncoding(encoder);
|
||||
length2 = encoder.Close();
|
||||
}
|
||||
Assert.AreEqual(length1 * 2, length2);
|
||||
var result = Encoding.UTF8.GetString(stream.ToArray());
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual(length2, stream.Position);
|
||||
}
|
||||
|
||||
private void TestEncoding(IEncoder encoder)
|
||||
{
|
||||
int payLoadSize = PayLoadSize;
|
||||
encoder.WriteByte("Byte", 0);
|
||||
while (--payLoadSize > 0)
|
||||
if (testResult)
|
||||
{
|
||||
encoder.WriteBoolean("Boolean", true);
|
||||
encoder.WriteUInt64("UInt64", 1234566890);
|
||||
encoder.WriteString("String", "The quick brown fox...");
|
||||
encoder.WriteNodeId("NodeId", s_nodeId);
|
||||
encoder.WriteInt32Array("Array", s_list);
|
||||
Assert.AreEqual(length1 * 2, length2);
|
||||
var result = Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual(length2, memoryStream.Position);
|
||||
}
|
||||
return length1 + length2;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Test Setup
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
// for validating benchmark tests
|
||||
m_context = new ServiceMessageContext();
|
||||
m_memoryStream = new MemoryStream();
|
||||
m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager();
|
||||
m_recyclableMemoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager);
|
||||
m_bufferManager = new BufferManager(nameof(BinaryEncoder), kBufferSize);
|
||||
m_arraySegmentStream = new ArraySegmentStream(m_bufferManager, kBufferSize, 0, kBufferSize);
|
||||
}
|
||||
public new void OneTimeSetUp() => base.OneTimeSetUp();
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
m_context = null;
|
||||
m_memoryStream.Dispose();
|
||||
m_memoryStream = null;
|
||||
m_recyclableMemoryStream.Dispose();
|
||||
m_recyclableMemoryStream = null;
|
||||
m_memoryManager = null;
|
||||
m_bufferManager = null;
|
||||
m_arraySegmentStream.Dispose();
|
||||
m_arraySegmentStream = null;
|
||||
}
|
||||
public new void OneTimeTearDown() => base.OneTimeTearDown();
|
||||
#endregion
|
||||
|
||||
#region Benchmark Setup
|
||||
@@ -210,44 +248,13 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
/// Set up some variables for benchmarks.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
// for validating benchmark tests
|
||||
m_context = new ServiceMessageContext();
|
||||
m_memoryStream = new MemoryStream();
|
||||
m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager();
|
||||
m_recyclableMemoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager);
|
||||
m_bufferManager = new BufferManager(nameof(BinaryEncoder), kBufferSize);
|
||||
m_arraySegmentStream = new ArraySegmentStream(m_bufferManager, kBufferSize, 0, kBufferSize);
|
||||
}
|
||||
public new void GlobalSetup() => base.GlobalSetup();
|
||||
|
||||
/// <summary>
|
||||
/// Tear down benchmark variables.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
m_context = null;
|
||||
m_memoryStream.Dispose();
|
||||
m_memoryStream = null;
|
||||
m_recyclableMemoryStream.Dispose();
|
||||
m_recyclableMemoryStream = null;
|
||||
m_memoryManager = null;
|
||||
m_bufferManager = null;
|
||||
m_arraySegmentStream.Dispose();
|
||||
m_arraySegmentStream = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private static NodeId s_nodeId = new NodeId(1234);
|
||||
private static IList<Int32> s_list = new List<Int32>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private IServiceMessageContext m_context;
|
||||
private MemoryStream m_memoryStream;
|
||||
private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager;
|
||||
private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream;
|
||||
private BufferManager m_bufferManager;
|
||||
private ArraySegmentStream m_arraySegmentStream;
|
||||
public new void GlobalCleanup() => base.GlobalCleanup();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
[Category("EncodeableTypes")]
|
||||
public void ActivateEncodeableType(
|
||||
EncodingType encoderType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
Type systemType
|
||||
)
|
||||
{
|
||||
@@ -68,13 +69,14 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
Assert.AreNotEqual(testObject.TypeId, testObject.BinaryEncodingId);
|
||||
Assert.AreNotEqual(testObject.TypeId, testObject.XmlEncodingId);
|
||||
Assert.AreNotEqual(testObject.BinaryEncodingId, testObject.XmlEncodingId);
|
||||
EncodeDecode(encoderType, BuiltInType.ExtensionObject, new ExtensionObject(testObject.TypeId, testObject));
|
||||
EncodeDecode(encoderType, BuiltInType.ExtensionObject, memoryStreamType, new ExtensionObject(testObject.TypeId, testObject));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Category("EncodeableTypes")]
|
||||
public void ActivateEncodeableTypeArray(
|
||||
EncodingType encoderType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
Type systemType
|
||||
)
|
||||
{
|
||||
@@ -95,7 +97,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
BuiltInType builtInType = BuiltInType.Variant;
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, systemType))
|
||||
{
|
||||
@@ -140,6 +142,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
[Category("EncodeableTypes")]
|
||||
public void ActivateEncodeableTypeMatrix(
|
||||
EncodingType encoderType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
bool encodeAsMatrix,
|
||||
Type systemType
|
||||
)
|
||||
@@ -167,7 +170,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
Matrix matrix = new Matrix(array, builtInType, dimensions);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, systemType))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
#pragma warning disable CA5394 // Do not use insecure randomness
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.IO;
|
||||
using Opc.Ua.Bindings;
|
||||
|
||||
namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
/// <summary>
|
||||
/// Common code for encoder/decoder benchmarks.
|
||||
/// </summary>
|
||||
public class EncoderBenchmarks
|
||||
{
|
||||
public const int StreamBufferSize = 4096;
|
||||
public const int DataValueCount = 10;
|
||||
|
||||
public EncoderBenchmarks()
|
||||
{
|
||||
m_random = new Random(0x62541);
|
||||
m_nodeId = new NodeId((uint)m_random.Next(50000));
|
||||
m_list = new List<int>();
|
||||
for (int i = 0; i < DataValueCount; i++)
|
||||
{
|
||||
m_list.Add(m_random.Next());
|
||||
}
|
||||
m_values = new List<DataValue>();
|
||||
DateTime now = new DateTime(2024, 03, 01, 06, 05, 59, DateTimeKind.Utc);
|
||||
now += TimeSpan.FromTicks(456789);
|
||||
for (int i = 0; i < DataValueCount; i++)
|
||||
{
|
||||
m_values.Add(new DataValue(new Variant((m_random.NextDouble() - 0.5) * 1000.0), m_random.NextDouble() > 0.1 ? StatusCodes.Good : StatusCodes.BadDataLost, now, now));
|
||||
}
|
||||
}
|
||||
|
||||
[Params(64, 1024)]
|
||||
public int PayLoadSize { get; set; } = 64;
|
||||
|
||||
#region Private Methods
|
||||
protected void TestEncoding(IEncoder encoder)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
int payLoadSize = PayLoadSize;
|
||||
encoder.WriteInt32("PayloadSize", payLoadSize);
|
||||
while (payLoadSize-- > 0)
|
||||
{
|
||||
encoder.WriteBoolean("Boolean", true);
|
||||
encoder.WriteByte("Byte", 123);
|
||||
encoder.WriteUInt16("UInt16", 1234);
|
||||
encoder.WriteUInt32("UInt32", 123456);
|
||||
encoder.WriteUInt64("UInt64", 1234566890);
|
||||
encoder.WriteSByte("Int8", -123);
|
||||
encoder.WriteInt16("Int16", -1234);
|
||||
encoder.WriteInt32("Int32", -123456);
|
||||
encoder.WriteInt64("Int64", -1234566890);
|
||||
encoder.WriteFloat("Float", 123.456f);
|
||||
encoder.WriteDouble("Double", 123456.789);
|
||||
encoder.WriteDateTime("DateTime", now);
|
||||
encoder.WriteString("String", "The quick brown fox jumps over the lazy dog.");
|
||||
encoder.WriteNodeId("NodeId", m_nodeId);
|
||||
encoder.WriteNodeId("ExpandedNodeId", m_nodeId);
|
||||
encoder.WriteInt32Array("Array", m_list);
|
||||
encoder.WriteDataValueArray("DataValues", m_values);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestDecoding(IDecoder decoder)
|
||||
{
|
||||
var payLoadSize = decoder.ReadInt32("PayloadSize");
|
||||
while (payLoadSize-- > 0)
|
||||
{
|
||||
_ = decoder.ReadBoolean("Boolean");
|
||||
_ = decoder.ReadByte("Byte");
|
||||
_ = decoder.ReadUInt16("UInt16");
|
||||
_ = decoder.ReadUInt32("UInt32");
|
||||
_ = decoder.ReadUInt64("UInt64");
|
||||
_ = decoder.ReadSByte("Int8");
|
||||
_ = decoder.ReadInt16("Int16");
|
||||
_ = decoder.ReadInt32("Int32");
|
||||
_ = decoder.ReadInt64("Int64");
|
||||
_ = decoder.ReadFloat("Float");
|
||||
_ = decoder.ReadDouble("Double");
|
||||
_ = decoder.ReadDateTime("DateTime");
|
||||
_ = decoder.ReadString("String");
|
||||
_ = decoder.ReadNodeId("NodeId");
|
||||
_ = decoder.ReadNodeId("ExpandedNodeId");
|
||||
_ = decoder.ReadInt32Array("Array");
|
||||
_ = decoder.ReadDataValueArray("DataValues");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Test Setup
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
// for validating benchmark tests
|
||||
m_context = new ServiceMessageContext();
|
||||
m_memoryManager = new RecyclableMemoryStreamManager(new RecyclableMemoryStreamManager.Options { BlockSize = StreamBufferSize });
|
||||
m_bufferManager = new BufferManager(nameof(BinaryEncoder), StreamBufferSize);
|
||||
}
|
||||
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
m_context = null;
|
||||
m_memoryManager = null;
|
||||
m_bufferManager = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Benchmark Setup
|
||||
/// <summary>
|
||||
/// Set up some variables for benchmarks.
|
||||
/// </summary>
|
||||
public void GlobalSetup()
|
||||
{
|
||||
// for validating benchmark tests
|
||||
m_context = new ServiceMessageContext();
|
||||
m_memoryManager = new RecyclableMemoryStreamManager(new RecyclableMemoryStreamManager.Options { BlockSize = StreamBufferSize });
|
||||
m_bufferManager = new BufferManager(nameof(BinaryEncoder), StreamBufferSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tear down benchmark variables.
|
||||
/// </summary>
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
m_context = null;
|
||||
m_memoryManager = null;
|
||||
m_bufferManager = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Protected Fields
|
||||
protected Random m_random;
|
||||
protected NodeId m_nodeId = new NodeId(1234);
|
||||
protected List<Int32> m_list;
|
||||
protected List<DataValue> m_values;
|
||||
protected IServiceMessageContext m_context;
|
||||
protected RecyclableMemoryStreamManager m_memoryManager;
|
||||
protected BufferManager m_bufferManager;
|
||||
#endregion
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Helper class to test ArraySegmentStream without Span support.
|
||||
/// </summary>
|
||||
public class ArraySegmentStreamNoSpan : ArraySegmentStream
|
||||
{
|
||||
public ArraySegmentStreamNoSpan(BufferManager bufferManager)
|
||||
: base(bufferManager)
|
||||
{
|
||||
}
|
||||
|
||||
public ArraySegmentStreamNoSpan(BufferCollection buffers)
|
||||
: base(buffers)
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
return base.ReadMemoryStream(buffer);
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
base.WriteMemoryStream(buffer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -37,13 +37,25 @@ using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
using Microsoft.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Bindings;
|
||||
using Opc.Ua.Test;
|
||||
|
||||
namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported memory stream types.
|
||||
/// </summary>
|
||||
public enum MemoryStreamType
|
||||
{
|
||||
MemoryStream,
|
||||
ArraySegmentStream,
|
||||
RecyclableMemoryStream
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the encoder tests.
|
||||
/// </summary>
|
||||
@@ -55,12 +67,16 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
protected const int kRandomStart = 4840;
|
||||
protected const int kRandomRepeats = 100;
|
||||
protected const int kMaxArrayLength = 1024 * 64;
|
||||
protected const int kTestBlockSize = 0x1000;
|
||||
protected const string kApplicationUri = "uri:localhost:opcfoundation.org:EncoderCommon";
|
||||
protected RandomSource RandomSource { get; private set; }
|
||||
protected DataGenerator DataGenerator { get; private set; }
|
||||
protected IServiceMessageContext Context { get; private set; }
|
||||
protected NamespaceTable NameSpaceUris { get; private set; }
|
||||
protected StringTable ServerUris { get; private set; }
|
||||
protected BufferManager BufferManager { get; private set; }
|
||||
protected RecyclableMemoryStreamManager RecyclableMemoryManager { get; private set; }
|
||||
|
||||
|
||||
#region Test Setup
|
||||
[OneTimeSetUp]
|
||||
@@ -74,6 +90,8 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
NameSpaceUris.GetIndexOrAppend(kApplicationUri);
|
||||
NameSpaceUris.GetIndexOrAppend(Namespaces.OpcUaGds);
|
||||
ServerUris = new StringTable();
|
||||
BufferManager = new BufferManager(nameof(EncoderCommon), kTestBlockSize);
|
||||
RecyclableMemoryManager = new RecyclableMemoryStreamManager(new RecyclableMemoryStreamManager.Options { BlockSize = kTestBlockSize });
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
@@ -135,6 +153,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
protected string EncodeDataValue(
|
||||
EncodingType encoderType,
|
||||
BuiltInType builtInType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
object data,
|
||||
bool useReversibleEncoding = true
|
||||
)
|
||||
@@ -146,7 +165,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine("Expected:");
|
||||
TestContext.Out.WriteLine(expected);
|
||||
Assert.IsNotNull(expected, "Expected DataValue is Null, " + encodeInfo);
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, typeof(DataValue), useReversibleEncoding))
|
||||
{
|
||||
@@ -164,6 +183,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
protected void EncodeDecodeDataValue(
|
||||
EncodingType encoderType,
|
||||
BuiltInType builtInType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
object data
|
||||
)
|
||||
{
|
||||
@@ -176,7 +196,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(expected);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, typeof(DataValue)))
|
||||
{
|
||||
@@ -217,6 +237,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
protected void EncodeDecode(
|
||||
EncodingType encoderType,
|
||||
BuiltInType builtInType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
object expected
|
||||
)
|
||||
{
|
||||
@@ -227,7 +248,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(expected);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, type))
|
||||
{
|
||||
@@ -272,6 +293,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
/// </summary>
|
||||
protected void EncodeJsonVerifyResult(
|
||||
BuiltInType builtInType,
|
||||
MemoryStreamType memoryStreamType,
|
||||
object data,
|
||||
bool useReversibleEncoding,
|
||||
string expected,
|
||||
@@ -300,7 +322,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
bool includeDefaultNumbers = isNumber ? includeDefaults : true;
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(memoryStreamType))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(EncodingType.Json, Context, encoderStream, typeof(DataValue),
|
||||
useReversibleEncoding, topLevelIsArray, includeDefaultValues, includeDefaultNumbers))
|
||||
@@ -379,16 +401,18 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
using (var stringWriter = new StringWriter())
|
||||
using (var stringReader = new StringReader(json))
|
||||
{
|
||||
var jsonReader = new JsonTextReader(stringReader);
|
||||
var jsonWriter = new JsonTextWriter(stringWriter) {
|
||||
using (var jsonReader = new JsonTextReader(stringReader))
|
||||
using (var jsonWriter = new JsonTextWriter(stringWriter) {
|
||||
FloatFormatHandling = FloatFormatHandling.String,
|
||||
Formatting = Newtonsoft.Json.Formatting.Indented,
|
||||
Culture = System.Globalization.CultureInfo.InvariantCulture
|
||||
};
|
||||
jsonWriter.WriteToken(jsonReader);
|
||||
string formattedJson = stringWriter.ToString();
|
||||
TestContext.Out.WriteLine(formattedJson);
|
||||
return formattedJson;
|
||||
})
|
||||
{
|
||||
jsonWriter.WriteToken(jsonReader);
|
||||
string formattedJson = stringWriter.ToString();
|
||||
TestContext.Out.WriteLine(formattedJson);
|
||||
return formattedJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -399,6 +423,27 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
return json;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns various implementations of a memory stream.
|
||||
/// </summary>
|
||||
/// <param name="memoryStreamType"></param>
|
||||
/// <returns>A MemoryStream</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
protected MemoryStream CreateEncoderMemoryStream(MemoryStreamType memoryStreamType)
|
||||
{
|
||||
switch (memoryStreamType)
|
||||
{
|
||||
case MemoryStreamType.MemoryStream:
|
||||
return new MemoryStream(kTestBlockSize);
|
||||
case MemoryStreamType.ArraySegmentStream:
|
||||
return new ArraySegmentStream(BufferManager);
|
||||
case MemoryStreamType.RecyclableMemoryStream:
|
||||
return new RecyclableMemoryStream(RecyclableMemoryManager);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(memoryStreamType), memoryStreamType, "Invalid MemoryStreamType specified.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encoder factory for all encoding types.
|
||||
/// </summary>
|
||||
@@ -418,13 +463,13 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
case EncodingType.Binary:
|
||||
Assume.That(useReversibleEncoding, "Binary encoding only supports reversible option.");
|
||||
return new BinaryEncoder(stream, context, false);
|
||||
return new BinaryEncoder(stream, context, true);
|
||||
case EncodingType.Xml:
|
||||
Assume.That(useReversibleEncoding, "Xml encoding only supports reversible option.");
|
||||
var xmlWriter = XmlWriter.Create(stream);
|
||||
return new XmlEncoder(systemType, xmlWriter, context);
|
||||
case EncodingType.Json:
|
||||
return new JsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream) {
|
||||
return new JsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream, true) {
|
||||
IncludeDefaultValues = includeDefaultValues,
|
||||
IncludeDefaultNumberValues = includeDefaultNumbers
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
)
|
||||
{
|
||||
object defaultValue = TypeInfo.GetDefaultValue(builtInType);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, defaultValue);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, MemoryStreamType.MemoryStream, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,7 +76,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
Assume.That(builtInType != BuiltInType.DiagnosticInfo);
|
||||
object randomData = DataGenerator.GetRandom(builtInType);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, randomData);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, MemoryStreamType.ArraySegmentStream, randomData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +120,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
break;
|
||||
}
|
||||
};
|
||||
EncodeDecode(encoderType, builtInType, randomData);
|
||||
EncodeDecode(encoderType, builtInType, MemoryStreamType.ArraySegmentStream, randomData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,7 +140,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
// or encoding of extension objects fails.
|
||||
randomData = ExtensionObject.Null;
|
||||
}
|
||||
EncodeDecode(encoderType, builtInType, randomData);
|
||||
EncodeDecode(encoderType, builtInType, MemoryStreamType.RecyclableMemoryStream, randomData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +156,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
Array boundaryValues = DataGenerator.GetRandomArray(builtInType, true, 10, true);
|
||||
foreach (var boundaryValue in boundaryValues)
|
||||
{
|
||||
EncodeDecode(encoderType, builtInType, boundaryValue);
|
||||
EncodeDecode(encoderType, builtInType, MemoryStreamType.MemoryStream, boundaryValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
// ensure different sized arrays contain different data set
|
||||
SetRandomSeed(arrayLength);
|
||||
object randomData = DataGenerator.GetRandomArray(builtInType, useBoundaryValues, arrayLength, true);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, randomData);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, MemoryStreamType.ArraySegmentStream, randomData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -191,7 +191,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
)
|
||||
{
|
||||
object randomData = DataGenerator.GetRandomArray(builtInType, false, 0, true);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, randomData);
|
||||
EncodeDecodeDataValue(encoderType, builtInType, MemoryStreamType.RecyclableMemoryStream, randomData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -206,7 +206,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
SetRepeatedRandomSeed();
|
||||
object randomData = DataGenerator.GetRandom(BuiltInType.Variant);
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, randomData);
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, MemoryStreamType.MemoryStream, randomData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,11 +224,11 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
Assert.Throws(
|
||||
typeof(ServiceResultException),
|
||||
() => EncodeDataValue(EncodingType.Json, builtInType, randomData, false)
|
||||
() => EncodeDataValue(EncodingType.Json, builtInType, MemoryStreamType.ArraySegmentStream, randomData, false)
|
||||
);
|
||||
return;
|
||||
}
|
||||
string json = EncodeDataValue(EncodingType.Json, builtInType, randomData, false);
|
||||
string json = EncodeDataValue(EncodingType.Json, builtInType, MemoryStreamType.MemoryStream, randomData, false);
|
||||
PrettifyAndValidateJson(json);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
SetRandomSeed(arrayLength);
|
||||
object randomData = DataGenerator.GetRandomArray(builtInType, useBoundaryValues, arrayLength, true);
|
||||
string json = EncodeDataValue(EncodingType.Json, builtInType, randomData, false);
|
||||
string json = EncodeDataValue(EncodingType.Json, builtInType, MemoryStreamType.RecyclableMemoryStream, randomData, false);
|
||||
PrettifyAndValidateJson(json);
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
)
|
||||
{
|
||||
object randomData = DataGenerator.GetRandomArray(builtInType, false, 0, true);
|
||||
string json = EncodeDataValue(EncodingType.Json, builtInType, randomData, false);
|
||||
string json = EncodeDataValue(EncodingType.Json, builtInType, MemoryStreamType.MemoryStream, randomData, false);
|
||||
PrettifyAndValidateJson(json);
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
//new Variant(new TestEnumType[] { TestEnumType.One, TestEnumType.Two, TestEnumType.Hundred }),
|
||||
new Variant(new Int32[] { 2, 3, 10 }, new TypeInfo(BuiltInType.Enumeration, 1))
|
||||
};
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, variant);
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, MemoryStreamType.ArraySegmentStream, variant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -302,7 +302,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
int arrayDimension = RandomSource.NextInt32(99) + 1;
|
||||
Array randomData = DataGenerator.GetRandomArray(builtInType, false, arrayDimension, true);
|
||||
var variant = new Variant(randomData, new TypeInfo(builtInType, 1));
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, variant);
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, MemoryStreamType.RecyclableMemoryStream, variant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -327,7 +327,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(randomData);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(MemoryStreamType.MemoryStream))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, type, true, false))
|
||||
{
|
||||
@@ -383,7 +383,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
int elements = ElementsFromDimension(dimensions);
|
||||
Array randomData = DataGenerator.GetRandomArray(builtInType, false, elements, true);
|
||||
var variant = new Variant(new Matrix(randomData, builtInType, dimensions));
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, variant);
|
||||
EncodeDecodeDataValue(encoderType, BuiltInType.Variant, MemoryStreamType.RecyclableMemoryStream, variant);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -403,7 +403,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
int elements = ElementsFromDimension(dimensions);
|
||||
Array randomData = DataGenerator.GetRandomArray(builtInType, false, elements, true);
|
||||
var variant = new Variant(new Matrix(randomData, builtInType, dimensions));
|
||||
string json = EncodeDataValue(EncodingType.Json, BuiltInType.Variant, variant, false);
|
||||
string json = EncodeDataValue(EncodingType.Json, BuiltInType.Variant, MemoryStreamType.ArraySegmentStream, variant, false);
|
||||
_ = PrettifyAndValidateJson(json);
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(matrix);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(MemoryStreamType.MemoryStream))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, type))
|
||||
{
|
||||
@@ -504,7 +504,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(expected);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(MemoryStreamType.MemoryStream))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, typeof(DataValue)))
|
||||
{
|
||||
@@ -595,7 +595,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(expected);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(MemoryStreamType.ArraySegmentStream))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, typeof(DataValue)))
|
||||
{
|
||||
@@ -656,8 +656,8 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
[Theory]
|
||||
[Category("Matrix")]
|
||||
public void EncodeMatrixInArrayOverflow(
|
||||
EncodingType encoderType,
|
||||
BuiltInType builtInType
|
||||
EncodingType encoderType,
|
||||
BuiltInType builtInType
|
||||
)
|
||||
{
|
||||
Assume.That(builtInType != BuiltInType.Null);
|
||||
@@ -687,7 +687,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
TestContext.Out.WriteLine(matrix);
|
||||
|
||||
byte[] buffer;
|
||||
using (var encoderStream = new MemoryStream())
|
||||
using (var encoderStream = CreateEncoderMemoryStream(MemoryStreamType.RecyclableMemoryStream))
|
||||
{
|
||||
using (IEncoder encoder = CreateEncoder(encoderType, Context, encoderStream, type))
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
[TestFixture, Category("JsonEncoder")]
|
||||
[SetCulture("en-us"), SetUICulture("en-us")]
|
||||
[NonParallelizable]
|
||||
[MemoryDiagnoser]
|
||||
[DisassemblyDiagnoser]
|
||||
public class JsonEncoderDateTimeBenchmark
|
||||
{
|
||||
[Params(0, 4, 7)]
|
||||
public int DateTimeOmittedZeros { get; set; } = 0;
|
||||
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void DateTimeEncodeToString()
|
||||
{
|
||||
_ = m_dateTime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[Test]
|
||||
public void ConvertToUniversalTime()
|
||||
{
|
||||
_ = JsonEncoder.ConvertUniversalTimeToString(m_dateTime);
|
||||
}
|
||||
|
||||
#region Test Setup
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
// for validating benchmark tests
|
||||
m_dateTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Benchmark Setup
|
||||
/// <summary>
|
||||
/// Set up some variables for benchmarks.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
// for validating benchmark tests
|
||||
switch (DateTimeOmittedZeros)
|
||||
{
|
||||
case 4: m_dateTime = new DateTime(2011, 11, 11, 11, 11, 11, 999, DateTimeKind.Utc); break;
|
||||
case 7: m_dateTime = new DateTime(2011, 11, 11, 11, 11, 11, DateTimeKind.Utc); break;
|
||||
default:
|
||||
do
|
||||
{
|
||||
m_dateTime = DateTime.UtcNow;
|
||||
} while (m_dateTime.Ticks % 10 == 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tear down benchmark variables.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private DateTime m_dateTime;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using Microsoft.IO;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
[TestFixture, Category("JsonEncoder")]
|
||||
[SetCulture("en-us"), SetUICulture("en-us")]
|
||||
[NonParallelizable]
|
||||
[MemoryDiagnoser]
|
||||
[DisassemblyDiagnoser(printSource: true)]
|
||||
public class JsonEncoderEscapeStringBenchmarks
|
||||
{
|
||||
public const int InnerLoops = 100;
|
||||
[DatapointSource]
|
||||
[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||
public int StringVariantIndex { get; set; } = 1;
|
||||
|
||||
[DatapointSource]
|
||||
// for benchmarking with different escaped strings
|
||||
public static readonly string[] EscapeTestStrings =
|
||||
{
|
||||
// The use case without escape characters, plain text
|
||||
"The quick brown fox jumps over the lazy dog.",
|
||||
// The use case with many control characters escaped, 1 char spaces
|
||||
"\" \n \r \t \b \f \\ ",
|
||||
// The use case with many control characters escaped, 2 char spaces
|
||||
" \" \n \r \t \b \f \\ ",
|
||||
// The use case with many control characters escaped, 3 char spaces
|
||||
" \" \n \r \t \b \f \\ ",
|
||||
// The use case with many control characters escaped, 5 char spaces
|
||||
" \" \n \r \t \b \f \\ ",
|
||||
// The use case with many binary characters escaped, 1 char spaces
|
||||
"\0 \x01 \x02 \x03 \x04 ",
|
||||
// The use case with many binary characters escaped, 2 char spaces
|
||||
" \0 \x01 \x02 \x03 \x04 ",
|
||||
// The use case with many binary characters escaped, 3 char spaces
|
||||
" \0 \x01 \x02 \x03 \x04 ",
|
||||
// The use case with many binary characters escaped, 5 char spaces
|
||||
" \0 \x01 \x02 \x03 \x04 ",
|
||||
// The use case with all escape characters and a long string
|
||||
"Ascii characters, special characters \n \b & control characters \0 \x04 ␀ ␁ ␂ ␃ ␄. This is a test.",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding of the previous implementation.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public void EscapeStringLegacy()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapedStringLegacy(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmark encoding of the previous implementation with snall improvement for binary encoding.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringLegacyPlus()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapedStringLegacyPlus(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using StringBuilder.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringStringBuilder()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeString(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using ReadOnlySpan.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringSpan()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeStringSpan(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using ReadOnlySpan and char write.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringSpanChars()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeStringSpanChars(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using ReadOnlySpan and char write.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringSpanCharsInline()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeStringSpanCharsInline(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using ReadOnlySpan and char write with const arrays.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringSpanCharsInlineConst()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeStringSpanCharsInlineConst(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using ReadOnlySpan and IndexOf.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringSpanIndex()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeStringSpanIndex(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new implementation using ReadOnlySpan and Dictionary.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void EscapeStringSpanDict()
|
||||
{
|
||||
m_memoryStream.Position = 0;
|
||||
int repeats = InnerLoops;
|
||||
while (repeats-- > 0)
|
||||
{
|
||||
EscapeStringSpanDict(m_testString);
|
||||
}
|
||||
m_streamWriter.Flush();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[TestCase("No Escape chars", 0)]
|
||||
[TestCase("control chars escaped, 1 char space", 1)]
|
||||
[TestCase("control chars escaped, 2 char spaces", 2)]
|
||||
[TestCase("control chars escaped, 3 char spaces", 3)]
|
||||
[TestCase("control chars escaped, 5 char spaces", 4)]
|
||||
[TestCase("binary chars escaped, 1 char space", 5)]
|
||||
[TestCase("binary chars escaped, 2 char spaces", 6)]
|
||||
[TestCase("binary chars escaped, 3 char spaces", 7)]
|
||||
[TestCase("binary chars escaped, 5 char spaces", 8)]
|
||||
[TestCase("all escape chars and long string", 9)]
|
||||
public void EscapeStringValidation(string name, int index)
|
||||
{
|
||||
m_testString = EscapeTestStrings[index];
|
||||
TestContext.Out.WriteLine(m_testString);
|
||||
var testArray = m_testString.ToCharArray();
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringLegacy();
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultLegacy = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultLegacy));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringLegacyPlus();
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultLegacyPlus = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultLegacyPlus));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringStringBuilder();
|
||||
m_streamWriter.Flush();
|
||||
byte[] result = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(result));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringSpan(m_testString);
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultSpan = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpan));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringSpanChars(m_testString);
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultSpanChars = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanChars));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringSpanCharsInline(m_testString);
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultSpanCharsInline = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanCharsInline));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringSpanCharsInlineConst(m_testString);
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultSpanCharsInlineConst = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanCharsInlineConst));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringSpanIndex(m_testString);
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultSpanIndex = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanIndex));
|
||||
|
||||
m_memoryStream.Position = 0;
|
||||
EscapeStringSpanDict(m_testString);
|
||||
m_streamWriter.Flush();
|
||||
byte[] resultSpanDict = m_memoryStream.ToArray();
|
||||
TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanDict));
|
||||
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, result));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultLegacyPlus));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpan));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanChars));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanCharsInline));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanCharsInlineConst));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanIndex));
|
||||
Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanDict));
|
||||
}
|
||||
|
||||
#region Test Setup
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
m_memoryManager = new RecyclableMemoryStreamManager();
|
||||
m_memoryStream = new RecyclableMemoryStream(m_memoryManager);
|
||||
m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
m_streamWriter?.Dispose();
|
||||
m_streamWriter = null;
|
||||
m_memoryStream?.Dispose();
|
||||
m_memoryStream = null;
|
||||
m_memoryManager = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Benchmark Setup
|
||||
/// <summary>4
|
||||
/// Set up some variables for benchmarks.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
m_memoryManager = new RecyclableMemoryStreamManager();
|
||||
m_memoryStream = new RecyclableMemoryStream(m_memoryManager);
|
||||
m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false);
|
||||
m_testString = EscapeTestStrings[StringVariantIndex - 1];
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
m_streamWriter?.Dispose();
|
||||
m_streamWriter = null;
|
||||
m_memoryStream?.Dispose();
|
||||
m_memoryStream = null;
|
||||
m_memoryManager = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Version used previously in JsonEncoder.
|
||||
/// </summary>
|
||||
private void EscapedStringLegacy(string value)
|
||||
{
|
||||
foreach (char ch in value)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (int ii = 0; ii < m_specialChars.Length; ii++)
|
||||
{
|
||||
if (m_specialChars[ii] == ch)
|
||||
{
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write(m_substitution[ii]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (ch < 32)
|
||||
{
|
||||
m_streamWriter.Write("\\u");
|
||||
m_streamWriter.Write("{0:X4}", (int)ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_streamWriter.Write(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Version used previously in JsonEncoder plus improvement of binary encoding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For the underlying stream writer it is faster to write two chars than a 2 char string.
|
||||
/// </remarks>
|
||||
private void EscapedStringLegacyPlus(string value)
|
||||
{
|
||||
foreach (char ch in value)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (int ii = 0; ii < m_specialChars.Length; ii++)
|
||||
{
|
||||
if (m_specialChars[ii] == ch)
|
||||
{
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write(m_substitution[ii]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (ch < 32)
|
||||
{
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write('u');
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
continue;
|
||||
}
|
||||
|
||||
m_streamWriter.Write(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Using a span to escape the string, write strings to stream writer if possible.
|
||||
/// </summary>
|
||||
private void EscapeStringSpan(string value)
|
||||
{
|
||||
ReadOnlySpan<char> charSpan = value.AsSpan();
|
||||
int lastOffset = 0;
|
||||
|
||||
for (int i = 0; i < charSpan.Length; i++)
|
||||
{
|
||||
bool found = false;
|
||||
char ch = charSpan[i];
|
||||
|
||||
for (int ii = 0; ii < m_specialChars.Length; ii++)
|
||||
{
|
||||
if (m_specialChars[ii] == ch)
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write(m_substitutionStrings[ii]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && ch < 32)
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write("\\u");
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
if (lastOffset == 0)
|
||||
{
|
||||
m_streamWriter.Write(value);
|
||||
}
|
||||
else if (lastOffset < charSpan.Length)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use span to escape the string, write only chars to stream writer.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
private void EscapeStringSpanChars(string value)
|
||||
{
|
||||
ReadOnlySpan<char> charSpan = value.AsSpan();
|
||||
|
||||
int lastOffset = 0;
|
||||
for (int i = 0; i < charSpan.Length; i++)
|
||||
{
|
||||
bool found = false;
|
||||
char ch = charSpan[i];
|
||||
|
||||
for (int ii = 0; ii < m_specialChars.Length; ii++)
|
||||
{
|
||||
if (m_specialChars[ii] == ch)
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write(m_substitution[ii]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && ch < 32)
|
||||
{
|
||||
if (lastOffset < i - 1)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
while (lastOffset < i)
|
||||
{
|
||||
m_streamWriter.Write(charSpan[lastOffset++]);
|
||||
}
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write('u');
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOffset == 0)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan);
|
||||
#else
|
||||
m_streamWriter.Write(value);
|
||||
#endif
|
||||
}
|
||||
else if (lastOffset < charSpan.Length)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteSpan(ref int lastOffset, ReadOnlySpan<char> valueSpan, int index)
|
||||
{
|
||||
if (lastOffset < index - 2)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
while (lastOffset < index)
|
||||
{
|
||||
m_streamWriter.Write(valueSpan[lastOffset++]);
|
||||
}
|
||||
}
|
||||
lastOffset = index + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write only chars to stream writer, inline the write sequence for readability.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
private void EscapeStringSpanCharsInline(string value)
|
||||
{
|
||||
ReadOnlySpan<char> charSpan = value.AsSpan();
|
||||
int lastOffset = 0;
|
||||
|
||||
for (int i = 0; i < charSpan.Length; i++)
|
||||
{
|
||||
bool found = false;
|
||||
char ch = charSpan[i];
|
||||
|
||||
for (int ii = 0; ii < m_specialChars.Length; ii++)
|
||||
{
|
||||
if (m_specialChars[ii] == ch)
|
||||
{
|
||||
WriteSpan(ref lastOffset, charSpan, i);
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write(m_substitution[ii]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && ch < 32)
|
||||
{
|
||||
WriteSpan(ref lastOffset, charSpan, i);
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write('u');
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOffset == 0)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan);
|
||||
#else
|
||||
m_streamWriter.Write(value);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteSpan(ref lastOffset, charSpan, charSpan.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// create version of EscapeStringSpanCharsInline that references cosnt arrays
|
||||
private void EscapeStringSpanCharsInlineConst(string value)
|
||||
{
|
||||
ReadOnlySpan<char> charSpan = value.AsSpan();
|
||||
int lastOffset = 0;
|
||||
|
||||
for (int i = 0; i < charSpan.Length; i++)
|
||||
{
|
||||
bool found = false;
|
||||
char ch = charSpan[i];
|
||||
|
||||
for (int ii = 0; ii < m_specialCharsConst.Length; ii++)
|
||||
{
|
||||
if (m_specialCharsConst[ii] == ch)
|
||||
{
|
||||
WriteSpan(ref lastOffset, charSpan, i);
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write(m_substitutionConst[ii]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && ch < 32)
|
||||
{
|
||||
WriteSpan(ref lastOffset, charSpan, i);
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write('u');
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOffset == 0)
|
||||
{
|
||||
m_streamWriter.Write(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteSpan(ref lastOffset, charSpan, charSpan.Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void EscapeStringSpanIndex(string value)
|
||||
{
|
||||
ReadOnlySpan<char> charSpan = value.AsSpan();
|
||||
|
||||
int lastOffset = 0;
|
||||
for (int i = 0; i < charSpan.Length; i++)
|
||||
{
|
||||
char ch = charSpan[i];
|
||||
|
||||
int index = m_specialString.IndexOf(ch);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write(m_substitutionStrings[index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch < 32)
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write('u');
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
if (lastOffset == 0)
|
||||
{
|
||||
m_streamWriter.Write(value);
|
||||
}
|
||||
else if (lastOffset < charSpan.Length)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void EscapeStringSpanDict(string value)
|
||||
{
|
||||
ReadOnlySpan<char> charSpan = value.AsSpan();
|
||||
|
||||
int lastOffset = 0;
|
||||
for (int i = 0; i < charSpan.Length; i++)
|
||||
{
|
||||
char ch = charSpan[i];
|
||||
|
||||
if (m_replace.TryGetValue(ch, out string escapeSequence))
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write(escapeSequence);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch < 32)
|
||||
{
|
||||
if (lastOffset < i)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
lastOffset = i + 1;
|
||||
m_streamWriter.Write('\\');
|
||||
m_streamWriter.Write('u');
|
||||
m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
if (lastOffset == 0)
|
||||
{
|
||||
m_streamWriter.Write(value);
|
||||
}
|
||||
else if (lastOffset < charSpan.Length)
|
||||
{
|
||||
#if NETCOREAPP2_1_OR_GREATER
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset));
|
||||
#else
|
||||
m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void EscapeString(string value)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder(value.Length * 2);
|
||||
|
||||
foreach (char ch in value)
|
||||
{
|
||||
if (m_replace.TryGetValue(ch, out string escapeSequence))
|
||||
{
|
||||
stringBuilder.Append(escapeSequence);
|
||||
}
|
||||
else if (ch < 32)
|
||||
{
|
||||
stringBuilder.Append("\\u");
|
||||
stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(ch);
|
||||
}
|
||||
}
|
||||
m_streamWriter.Write(stringBuilder);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private static string m_testString;
|
||||
private RecyclableMemoryStreamManager m_memoryManager;
|
||||
private RecyclableMemoryStream m_memoryStream;
|
||||
private StreamWriter m_streamWriter;
|
||||
private int m_streamSize = 1024;
|
||||
private static readonly string m_specialString = "\"\\\n\r\t\b\f";
|
||||
|
||||
// Declare static readonly characters for the special characters
|
||||
private static readonly char sro_quotation = '\"';
|
||||
private static readonly char sro_backslash = '\\';
|
||||
private static readonly char sro_newline = '\n';
|
||||
private static readonly char sro_return = '\r';
|
||||
private static readonly char sro_tab = '\t';
|
||||
private static readonly char sro_backspace = '\b';
|
||||
private static readonly char sro_formfeed = '\f';
|
||||
private static readonly char[] m_specialChars = new char[] { sro_quotation, sro_backslash, sro_newline, sro_return, sro_tab, sro_backspace, sro_formfeed };
|
||||
|
||||
// Declare static readonly characters for the substitution characters
|
||||
private static readonly char sro_quotationSub = '\"';
|
||||
private static readonly char sro_backslashSub = '\\';
|
||||
private static readonly char sro_newlineSub = 'n';
|
||||
private static readonly char sro_returnSub = 'r';
|
||||
private static readonly char sro_tabSub = 't';
|
||||
private static readonly char sro_backspaceSub = 'b';
|
||||
private static readonly char sro_formfeedSub = 'f';
|
||||
private static readonly char[] m_substitution = new char[] { sro_quotationSub, sro_backslashSub, sro_newlineSub, sro_returnSub, sro_tabSub, sro_backspaceSub, sro_formfeedSub };
|
||||
|
||||
// Special characters as const
|
||||
private const char s_quotation = '\"';
|
||||
private const char s_backslash = '\\';
|
||||
private const char s_newline = '\n';
|
||||
private const char s_return = '\r';
|
||||
private const char s_tab = '\t';
|
||||
private const char s_backspace = '\b';
|
||||
private const char s_formfeed = '\f';
|
||||
|
||||
private static readonly char[] m_specialCharsConst = new char[] { s_quotation, s_backslash, s_newline, s_return, s_tab, s_backspace, s_formfeed };
|
||||
|
||||
// Substitution as const
|
||||
private const char s_quotationSub = '\"';
|
||||
private const char s_backslashSub = '\\';
|
||||
private const char s_newlineSub = 'n';
|
||||
private const char s_returnSub = 'r';
|
||||
private const char s_tabSub = 't';
|
||||
private const char s_backspaceSub = 'b';
|
||||
private const char s_formfeedSub = 'f';
|
||||
|
||||
private static readonly char[] m_substitutionConst = new char[] { s_quotationSub, s_backslashSub, s_newlineSub, s_returnSub, s_tabSub, s_backspaceSub, s_formfeedSub };
|
||||
|
||||
private static readonly string[] m_substitutionStrings = new string[] { "\\\"", "\\\\", "\\n", "\\r", "\\t", "\\b", "\\f" };
|
||||
private static readonly Dictionary<char, string> m_replace = new Dictionary<char, string>
|
||||
{
|
||||
{ '\"', "\\\"" },
|
||||
{ '\\', "\\\\" },
|
||||
{ '\n', "\\n" },
|
||||
{ '\r', "\\r" },
|
||||
{ '\t', "\\t" },
|
||||
{ '\b', "\\b" },
|
||||
{ '\f', "\\f" }
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.IO;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Bindings;
|
||||
|
||||
namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
{
|
||||
@@ -365,14 +367,54 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a constructor with external Stream,
|
||||
/// Use a MemoryStream constructor with external Stream,
|
||||
/// keep the stream open for more encodings.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorStream()
|
||||
public void ConstructorMemoryStream()
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
ConstructorStream(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a ArraySegmentStream constructor with external Stream,
|
||||
/// keep the stream open for more encodings.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorArraySegmentStream()
|
||||
{
|
||||
using (var memoryStream = new ArraySegmentStream(BufferManager))
|
||||
{
|
||||
ConstructorStream(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a RecylableMemoryStream constructor with external Stream,
|
||||
/// keep the stream open for more encodings.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorRecyclableMemoryStream()
|
||||
{
|
||||
var recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(new RecyclableMemoryStreamManager.Options {
|
||||
BlockSize = BufferManager.MaxSuggestedBufferSize,
|
||||
});
|
||||
using (var memoryStream = new RecyclableMemoryStream(recyclableMemoryStreamManager))
|
||||
{
|
||||
ConstructorStream(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a constructor with external Stream,
|
||||
/// keep the stream open for more encodings.
|
||||
/// </summary>
|
||||
public void ConstructorStream(MemoryStream memoryStream)
|
||||
{
|
||||
var context = new ServiceMessageContext();
|
||||
var memoryStream = new MemoryStream();
|
||||
using (var jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
@@ -384,7 +426,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
|
||||
// recycle the StreamWriter, ensure the result is equal
|
||||
memoryStream.Position = 0;
|
||||
using (var jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
using (IJsonEncoder jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
}
|
||||
@@ -411,14 +453,141 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
Assert.Throws<ArgumentException>(() => _ = new StreamWriter(memoryStream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a constructor with external ArraySegmentStream,
|
||||
/// keep the stream open for more encodings.
|
||||
/// Alternate use of sequence.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorArraySegmentStreamSequence()
|
||||
{
|
||||
var context = new ServiceMessageContext();
|
||||
using (var memoryStream = new ArraySegmentStream(BufferManager))
|
||||
{
|
||||
using (var jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
}
|
||||
|
||||
// get the buffers and save the result
|
||||
#if NET5_0_OR_GREATER
|
||||
string result1;
|
||||
using (var sequence = memoryStream.GetSequence(nameof(ConstructorStream)))
|
||||
{
|
||||
result1 = Encoding.UTF8.GetString(sequence.Sequence);
|
||||
Assert.IsNotEmpty(result1);
|
||||
TestContext.Out.WriteLine("Result1:");
|
||||
_ = PrettifyAndValidateJson(result1);
|
||||
}
|
||||
#else
|
||||
var result1 = Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
Assert.IsNotEmpty(result1);
|
||||
TestContext.Out.WriteLine("Result1:");
|
||||
_ = PrettifyAndValidateJson(result1);
|
||||
#endif
|
||||
|
||||
// recycle the memory stream, ensure the result is equal
|
||||
memoryStream.Position = 0;
|
||||
using (var jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
}
|
||||
var result2 = Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
Assert.IsNotEmpty(result2);
|
||||
TestContext.Out.WriteLine("Result2:");
|
||||
_ = PrettifyAndValidateJson(result2);
|
||||
Assert.AreEqual(result1, result2);
|
||||
|
||||
// recycle the StreamWriter, ensure the result is equal,
|
||||
// use reflection to return result in external stream
|
||||
memoryStream.Position = 0;
|
||||
using (IJsonEncoder jsonEncoder = new JsonEncoder(context, true, false, memoryStream, false))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
var result3 = jsonEncoder.CloseAndReturnText();
|
||||
Assert.IsNotEmpty(result3);
|
||||
TestContext.Out.WriteLine("Result3:");
|
||||
_ = PrettifyAndValidateJson(result3);
|
||||
Assert.AreEqual(result1, result3);
|
||||
}
|
||||
|
||||
// ensure the memory stream was closed
|
||||
Assert.Throws<ArgumentException>(() => _ = new StreamWriter(memoryStream));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a constructor with external RecyclableMemoryStream,
|
||||
/// keep the stream open for more encodings.
|
||||
/// Alternate use of sequence.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorRecyclableMemoryStreamSequence()
|
||||
{
|
||||
var context = new ServiceMessageContext();
|
||||
using (var memoryStream = new RecyclableMemoryStream(RecyclableMemoryManager))
|
||||
{
|
||||
using (var jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
}
|
||||
|
||||
// get the buffers and save the result
|
||||
#if NET5_0_OR_GREATER
|
||||
string result1;
|
||||
{
|
||||
var sequence = memoryStream.GetReadOnlySequence();
|
||||
result1 = Encoding.UTF8.GetString(sequence);
|
||||
Assert.IsNotEmpty(result1);
|
||||
TestContext.Out.WriteLine("Result1:");
|
||||
_ = PrettifyAndValidateJson(result1);
|
||||
}
|
||||
#else
|
||||
var result1 = Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
Assert.IsNotEmpty(result1);
|
||||
TestContext.Out.WriteLine("Result1:");
|
||||
_ = PrettifyAndValidateJson(result1);
|
||||
#endif
|
||||
|
||||
// recycle the memory stream, ensure the result is equal
|
||||
memoryStream.Position = 0;
|
||||
using (var jsonEncoder = new JsonEncoder(context, true, false, memoryStream, true))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
}
|
||||
var result2 = Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
Assert.IsNotEmpty(result2);
|
||||
TestContext.Out.WriteLine("Result2:");
|
||||
_ = PrettifyAndValidateJson(result2);
|
||||
Assert.AreEqual(result1, result2);
|
||||
|
||||
// recycle the StreamWriter, ensure the result is equal,
|
||||
// use reflection to return result in external stream
|
||||
memoryStream.Position = 0;
|
||||
using (IJsonEncoder jsonEncoder = new JsonEncoder(context, true, false, memoryStream, false))
|
||||
{
|
||||
TestEncoding(jsonEncoder);
|
||||
var result3 = jsonEncoder.CloseAndReturnText();
|
||||
Assert.IsNotEmpty(result3);
|
||||
TestContext.Out.WriteLine("Result3:");
|
||||
_ = PrettifyAndValidateJson(result3);
|
||||
Assert.AreEqual(result1, result3);
|
||||
}
|
||||
|
||||
// ensure the memory stream was closed
|
||||
Assert.Throws<ArgumentException>(() => _ = new StreamWriter(memoryStream));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify reversible Json encoding.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeRev(JsonValidationData jsonValidationData)
|
||||
public void JsonEncodeRev(JsonValidationData jsonValidationData, MemoryStreamType memoryStreamType)
|
||||
{
|
||||
EncodeJsonVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
memoryStreamType,
|
||||
jsonValidationData.Instance,
|
||||
true,
|
||||
jsonValidationData.ExpectedReversible,
|
||||
@@ -430,10 +599,11 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
|
||||
/// Verify non reversible Json encoding.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
public void JsonEncodeNonRev(JsonValidationData jsonValidationData)
|
||||
public void JsonEncodeNonRev(JsonValidationData jsonValidationData, MemoryStreamType memoryStreamType)
|
||||
{
|
||||
EncodeJsonVerifyResult(
|
||||
jsonValidationData.BuiltInType,
|
||||
memoryStreamType,
|
||||
jsonValidationData.Instance,
|
||||
false,
|
||||
jsonValidationData.ExpectedNonReversible ?? jsonValidationData.ExpectedReversible,
|
||||
|
||||
+6
-1
@@ -21,9 +21,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageIcon>images/logo.jpg</PackageIcon>
|
||||
|
||||
Reference in New Issue
Block a user