diff --git a/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj b/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj index 41eb5496..26ad03a1 100644 --- a/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj +++ b/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj @@ -179,6 +179,7 @@ + diff --git a/Lawo.EmberPlusSharp/S101/DeframingStream.cs b/Lawo.EmberPlusSharp/S101/DeframingStream.cs index 330ddd83..3c48a72a 100644 --- a/Lawo.EmberPlusSharp/S101/DeframingStream.cs +++ b/Lawo.EmberPlusSharp/S101/DeframingStream.cs @@ -6,6 +6,7 @@ namespace Lawo.EmberPlusSharp.S101 { + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -19,6 +20,7 @@ namespace Lawo.EmberPlusSharp.S101 /// internal sealed class DeframingStream : BufferStream { + private readonly Action outOfFrameByteReceived; private State state; private ushort crc = 0xFFFF; private readonly Queue decodedQueue = new Queue(); @@ -55,8 +57,9 @@ public sealed override async Task ReadAsync( //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Initializes a new instance of the class. - internal DeframingStream(ReadBuffer readBuffer) : base(readBuffer, null) + internal DeframingStream(ReadBuffer readBuffer, Action outOfFrameByteReceived) : base(readBuffer, null) { + this.outOfFrameByteReceived = outOfFrameByteReceived; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -68,13 +71,15 @@ private bool ReadByte(ReadBuffer readBuffer, byte[] buffer, ref int index) switch (this.state) { case State.BeforeFrame: - if (currentByte != Frame.BeginOfFrame) + if (currentByte == Frame.BeginOfFrame) { - this.state = State.AfterFrame; - throw new S101Exception("Unexpected byte while looking for BOF."); + this.state = State.InFrame; + } + else + { + this.outOfFrameByteReceived(currentByte); } - this.state = State.InFrame; break; case State.InFrame: if (currentByte < Frame.InvalidStart) diff --git a/Lawo.EmberPlusSharp/S101/EventInfo.cs b/Lawo.EmberPlusSharp/S101/EventInfo.cs index a946728e..40ec7d87 100644 --- a/Lawo.EmberPlusSharp/S101/EventInfo.cs +++ b/Lawo.EmberPlusSharp/S101/EventInfo.cs @@ -14,22 +14,11 @@ namespace Lawo.EmberPlusSharp.S101 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "There's no point in comparing instances of this type.")] public struct EventInfo { - private readonly DateTime? timeUtc; - private readonly int? number; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// Gets the time when the event was logged. - public DateTime? TimeUtc - { - get { return this.timeUtc; } - } + public DateTime? TimeUtc { get; } /// Gets the number of the event. - public int? Number - { - get { return this.number; } - } + public int? Number { get; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -39,8 +28,8 @@ internal EventInfo(DateTime? timeUtc) : this(timeUtc, null) internal EventInfo(DateTime? timeUtc, int? number) { - this.timeUtc = timeUtc; - this.number = number; + this.TimeUtc = timeUtc; + this.Number = number; } } } diff --git a/Lawo.EmberPlusSharp/S101/MessageDecodingStream.cs b/Lawo.EmberPlusSharp/S101/MessageDecodingStream.cs index 0f73cac5..c0a3e63a 100644 --- a/Lawo.EmberPlusSharp/S101/MessageDecodingStream.cs +++ b/Lawo.EmberPlusSharp/S101/MessageDecodingStream.cs @@ -6,11 +6,12 @@ namespace Lawo.EmberPlusSharp.S101 { + using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; - using Lawo.IO; + using IO; /// Transparently decodes a single S101 message. /// @@ -29,6 +30,7 @@ internal sealed class MessageDecodingStream : NonSeekableStream private DeframingStream deframingStream; private readonly ReadBuffer deframedBuffer; private readonly byte[] discardBuffer; + private readonly Action outOfFrameByteReceived; private S101Message message; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -70,9 +72,12 @@ public sealed override Task ReadAsync( //////////////////////////////////////////////////////////////////////////////////////////////////////////////// internal static async Task CreateAsync( - ReadBuffer rawBuffer, byte[] discardBuffer, CancellationToken cancellationToken) + ReadBuffer rawBuffer, + byte[] discardBuffer, + Action outOfFrameByteReceived, + CancellationToken cancellationToken) { - var result = new MessageDecodingStream(rawBuffer, discardBuffer); + var result = new MessageDecodingStream(rawBuffer, discardBuffer, outOfFrameByteReceived); var newMessage = await S101Message.ReadFromAsync(result.deframedBuffer, cancellationToken); if ((newMessage != null) && newMessage.CanHaveMultiplePackets && @@ -93,10 +98,10 @@ internal S101Message Message //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private MessageDecodingStream(ReadBuffer rawBuffer, byte[] discardBuffer) + private MessageDecodingStream(ReadBuffer rawBuffer, byte[] discardBuffer, Action outOfFrameByteReceived) { this.rawBuffer = rawBuffer; - this.deframingStream = new DeframingStream(this.rawBuffer); + this.deframingStream = new DeframingStream(this.rawBuffer, outOfFrameByteReceived); // This buffer is kept small in size, because a new one is allocated for each message. // This has the effect that only the bytes of reads <= MessageHeaderMaxLength bytes are actually copied into @@ -109,6 +114,7 @@ private MessageDecodingStream(ReadBuffer rawBuffer, byte[] discardBuffer) this.deframedBuffer = new ReadBuffer((ReadAsyncCallback)this.ReadDeframedAsync, Constants.MessageHeaderMaxLength); this.discardBuffer = discardBuffer; + this.outOfFrameByteReceived = outOfFrameByteReceived; } private async Task ReadFromCurrentPacketAsync( @@ -121,7 +127,7 @@ private async Task ReadFromCurrentPacketAsync( this.message.CanHaveMultiplePackets && ((this.message.PacketFlags & PacketFlags.LastPacket) == 0)) { this.deframingStream.Dispose(); - this.deframingStream = new DeframingStream(this.rawBuffer); + this.deframingStream = new DeframingStream(this.rawBuffer, this.outOfFrameByteReceived); this.ValidateMessage(await S101Message.ReadFromAsync(this.deframedBuffer, cancellationToken)); } diff --git a/Lawo.EmberPlusSharp/S101/MessageEncodingStream.cs b/Lawo.EmberPlusSharp/S101/MessageEncodingStream.cs index a8fe1da3..cddfef82 100644 --- a/Lawo.EmberPlusSharp/S101/MessageEncodingStream.cs +++ b/Lawo.EmberPlusSharp/S101/MessageEncodingStream.cs @@ -42,12 +42,11 @@ public sealed override async Task DisposeAsync(CancellationToken cancellationTok { if (this.message.CanHaveMultiplePackets) { - await this.CreateFramingStreamAsync( + await this.DisposeAndCreateFramingStreamAsync( PacketFlags.EmptyPacket | PacketFlags.LastPacket, cancellationToken); } - await this.unframedBuffer.FlushAsync(cancellationToken); - await this.framingStream.DisposeAsync(cancellationToken); + await this.DisposeFramingStream(cancellationToken); await this.rawBuffer.FlushAsync(cancellationToken); await base.DisposeAsync(cancellationToken); } @@ -73,7 +72,7 @@ public sealed override async Task WriteAsync( { if (this.framingStream.TotalCount >= MaxFrameLength) { - await this.CreateFramingStreamAsync(PacketFlags.None, cancellationToken); + await this.DisposeAndCreateFramingStreamAsync(PacketFlags.None, cancellationToken); } var countToWrite = Math.Min(count, MaxFrameLength - this.framingStream.TotalCount); @@ -92,6 +91,14 @@ public sealed override async Task FlushAsync(CancellationToken cancellationToken //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + internal async Task WriteOutOfFrameByteAsync(byte value, CancellationToken cancellationToken) + { + await this.DisposeFramingStream(cancellationToken); + await this.rawBuffer.ReserveAsync(1, cancellationToken); + this.rawBuffer[this.rawBuffer.Count++] = value; + await this.CreateFramingStream(PacketFlags.None, cancellationToken); + } + internal static async Task CreateAsync( WriteBuffer rawBuffer, S101Message message, CancellationToken cancellationToken) { @@ -114,10 +121,20 @@ private MessageEncodingStream(WriteBuffer rawBuffer, FramingStream framingStream this.message = message; } - private async Task CreateFramingStreamAsync(PacketFlags packetFlags, CancellationToken cancellationToken) + private async Task DisposeAndCreateFramingStreamAsync(PacketFlags packetFlags, CancellationToken cancellationToken) + { + await DisposeFramingStream(cancellationToken); + await CreateFramingStream(packetFlags, cancellationToken); + } + + private async Task DisposeFramingStream(CancellationToken cancellationToken) { await this.unframedBuffer.FlushAsync(cancellationToken); await this.framingStream.DisposeAsync(cancellationToken); + } + + private async Task CreateFramingStream(PacketFlags packetFlags, CancellationToken cancellationToken) + { this.framingStream = await FramingStream.CreateAsync(this.rawBuffer, cancellationToken); this.message.PacketFlags = packetFlags; await this.message.WriteToAsync(this.unframedBuffer, cancellationToken); diff --git a/Lawo.EmberPlusSharp/S101/OutOfFrameByteReceivedEventArgs.cs b/Lawo.EmberPlusSharp/S101/OutOfFrameByteReceivedEventArgs.cs new file mode 100644 index 00000000..0f600fbd --- /dev/null +++ b/Lawo.EmberPlusSharp/S101/OutOfFrameByteReceivedEventArgs.cs @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2012-2015 Lawo AG (http://www.lawo.com). +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Lawo.EmberPlusSharp.S101 +{ + using System; + + /// Provides the data for the and + /// events. + /// + public sealed class OutOfFrameByteReceivedEventArgs : EventArgs + { + private readonly byte value; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /// Gets the message. + public byte Value + { + get { return this.value; } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + internal OutOfFrameByteReceivedEventArgs(byte value) + { + this.value = value; + } + } +} \ No newline at end of file diff --git a/Lawo.EmberPlusSharp/S101/S101Client.cs b/Lawo.EmberPlusSharp/S101/S101Client.cs index 4b7949b3..8926ebed 100644 --- a/Lawo.EmberPlusSharp/S101/S101Client.cs +++ b/Lawo.EmberPlusSharp/S101/S101Client.cs @@ -225,6 +225,14 @@ public Task SendMessageAsync(S101Message message, byte[] payload) return this.SendMessageAsyncCore(message, payload); } + /// Sends as an out-of-frame byte. + /// The byte to write. + /// equals 0xFE. + public Task SendOutOfFrameByte(byte value) + { + return this.writer.WriteOutOfFrameByte(value, this.source.Token); + } + /// See . /// Cancels all communication currently in progress and calls on the /// connection object passed to the constructor. @@ -238,6 +246,9 @@ public void Dispose() /// command. public event EventHandler EmberDataReceived; + /// Occurs when an out-of-frame byte has been received. + public event EventHandler OutOfFrameByteReceived; + /// Occurs when the connection to the provider has been lost. /// /// This event is raised in the following situations: @@ -275,6 +286,7 @@ private async void ReadLoop(IDisposable connection, S101Reader reader) this.source.Token.Register(() => disposed.SetResult(true)); await this.EnqueueLogOperation(() => this.logger.LogEvent("StartingReadLoop")); + reader.OutOfFrameByteReceived += this.OnOutOfFrameByteReceived; Exception exception; bool remote = false; @@ -304,6 +316,7 @@ private async void ReadLoop(IDisposable connection, S101Reader reader) } finally { + reader.OutOfFrameByteReceived -= this.OnOutOfFrameByteReceived; this.DisposeCore(false); if (connection != null) @@ -437,6 +450,11 @@ await this.SendMessageAsyncCore( } } + private void OnOutOfFrameByteReceived(object sender, OutOfFrameByteReceivedEventArgs e) + { + this.OnEvent(this.OutOfFrameByteReceived, e); + } + private void OnEvent(EventHandler handler, TEventArgs args) where TEventArgs : EventArgs { if (handler != null) diff --git a/Lawo.EmberPlusSharp/S101/S101Reader.cs b/Lawo.EmberPlusSharp/S101/S101Reader.cs index 169ea8a3..26967b6a 100644 --- a/Lawo.EmberPlusSharp/S101/S101Reader.cs +++ b/Lawo.EmberPlusSharp/S101/S101Reader.cs @@ -29,7 +29,8 @@ public sealed class S101Reader //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Initializes a new instance of the class by calling - /// S101Reader(, 8192). + /// S101Reader(, 8192). + /// [CLSCompliant(false)] public S101Reader(ReadAsyncCallback readAsync) : this(readAsync, Constants.PhysicalStreamBufferSize) { @@ -143,6 +144,9 @@ public bool IsAnotherMessageAvailable } } + /// Occurs when an out-of-frame byte has been received. + public event EventHandler OutOfFrameByteReceived; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// private async Task DisposeCoreAsync(CancellationToken cancellationToken) @@ -171,10 +175,20 @@ private async Task ReadCoreAsync(CancellationToken cancellationToken) } this.stream = await MessageDecodingStream.CreateAsync( - this.readBuffer, this.discardBuffer, cancellationToken); + this.readBuffer, this.discardBuffer, this.OnOutOfFrameByteReceived, cancellationToken); return this.stream.Message != null; } + private void OnOutOfFrameByteReceived(byte value) + { + var handler = this.OutOfFrameByteReceived; + + if (handler != null) + { + handler(this, new OutOfFrameByteReceivedEventArgs(value)); + } + } + private void AssertNotDisposed() { if (this.disposed) diff --git a/Lawo.EmberPlusSharp/S101/S101Writer.cs b/Lawo.EmberPlusSharp/S101/S101Writer.cs index 26e99c6f..5e56aad4 100644 --- a/Lawo.EmberPlusSharp/S101/S101Writer.cs +++ b/Lawo.EmberPlusSharp/S101/S101Writer.cs @@ -7,6 +7,7 @@ namespace Lawo.EmberPlusSharp.S101 { using System; + using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -90,6 +91,15 @@ public Task WriteMessageAsync(S101Message message, Cancellati return this.taskSingleton.Execute(() => this.WriteMessageCoreAsync(message, cancellationToken)); } + /// Writes as an out-of-frame byte. + /// The byte to write. + /// The token to monitor for cancellation requests. + /// equals 0xFE. + public Task WriteOutOfFrameByte(byte value, CancellationToken cancellationToken) + { + return this.taskSingleton.Execute(() => this.WriteOutOfFrameByteCore(value, cancellationToken)); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// private async Task DisposeCoreAsync(CancellationToken cancellationToken) @@ -135,6 +145,29 @@ private async Task WriteMessageCoreAsync( return this.stream; } + private async Task WriteOutOfFrameByteCore(byte value, CancellationToken cancellationToken) + { + this.AssertNotDisposed(); + + if (value == Frame.BeginOfFrame) + { + var message = string.Format( + CultureInfo.InvariantCulture, "A value not equal to {0:X2} is required.", Frame.BeginOfFrame); + throw new ArgumentException(message, "value"); + } + + if ((this.stream == null) || !this.stream.CanWrite) + { + await this.writeBuffer.ReserveAsync(1, cancellationToken); + this.writeBuffer[this.writeBuffer.Count++] = value; + await this.writeBuffer.FlushAsync(cancellationToken); + } + else + { + await this.stream.WriteOutOfFrameByteAsync(value, cancellationToken); + } + } + private void AssertNotDisposed() { if (this.disposed) diff --git a/Lawo.EmberPlusSharpTest/S101/S101ClientTest.cs b/Lawo.EmberPlusSharpTest/S101/S101ClientTest.cs index 837f3e14..601a1200 100644 --- a/Lawo.EmberPlusSharpTest/S101/S101ClientTest.cs +++ b/Lawo.EmberPlusSharpTest/S101/S101ClientTest.cs @@ -8,6 +8,7 @@ namespace Lawo.EmberPlusSharp.S101 { using System; using System.IO; + using System.Linq; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -102,20 +103,33 @@ public void EmberDataTest() var data = new byte[this.Random.Next(512, 16384)]; this.Random.NextBytes(data); - var received = new TaskCompletionSource(); - EventHandler handler = + var emberDataReceived = new TaskCompletionSource(); + EventHandler emberDataHandler = (s, e) => { Assert.AreEqual(slot, e.Message.Slot); Assert.IsInstanceOfType(e.Message.Command, typeof(EmberData)); CollectionAssert.AreEqual(data, e.GetPayload()); - received.SetResult(true); + emberDataReceived.SetResult(true); }; - provider.EmberDataReceived += handler; + var outOfFrameByte = GetRandomByteExcept(0xFE); + var outOfFrameByteReceived = new TaskCompletionSource(); + EventHandler outOfFrameByteHandler = + (s, e) => + { + Assert.AreEqual(outOfFrameByte, e.Value); + outOfFrameByteReceived.SetResult(true); + }; + + provider.EmberDataReceived += emberDataHandler; + provider.OutOfFrameByteReceived += outOfFrameByteHandler; await consumer.SendMessageAsync(new S101Message(slot, EmberDataCommand), data); - await received.Task; - provider.EmberDataReceived -= handler; + await emberDataReceived.Task; + await consumer.SendOutOfFrameByte(outOfFrameByte); + await outOfFrameByteReceived.Task; + provider.OutOfFrameByteReceived -= outOfFrameByteHandler; + provider.EmberDataReceived -= emberDataHandler; }, () => ConnectAsync(-1, null), () => WaitForConnectionAsync(null))); @@ -193,6 +207,7 @@ await AssertThrowAsync( await AssertThrowAsync( () => client.SendMessageAsync(null)); await AssertThrowAsync(() => client.SendMessageAsync(EmberDataMessage)); + await AssertThrowAsync(() => client.SendOutOfFrameByte(0xFE)); client.Dispose(); await AssertThrowAsync( @@ -223,5 +238,18 @@ public void VersionTest() null, null)); } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private byte GetRandomByteExcept(params byte[] exceptions) + { + byte result; + + while (exceptions.Contains(result = (byte)this.Random.Next(byte.MinValue, byte.MaxValue + 1))) + { + } + + return result; + } } } diff --git a/Lawo.EmberPlusSharpTest/S101/S101LoggerTest.cs b/Lawo.EmberPlusSharpTest/S101/S101LoggerTest.cs index 3c114809..4b66209f 100644 --- a/Lawo.EmberPlusSharpTest/S101/S101LoggerTest.cs +++ b/Lawo.EmberPlusSharpTest/S101/S101LoggerTest.cs @@ -10,9 +10,10 @@ namespace Lawo.EmberPlusSharp.S101 using System.Globalization; using System.IO; using System.Xml; - using Lawo.EmberPlusSharp.Glow; - using Lawo.UnitTesting; + using Ember; + using Glow; using Microsoft.VisualStudio.TestTools.UnitTesting; + using UnitTesting; /// Tests . [TestClass] @@ -28,7 +29,8 @@ public void MainTest() AssertThrow( () => new S101Logger(null, writer).Dispose(), () => new S101Logger(GlowTypes.Instance, (TextWriter)null).Dispose(), - () => new S101Logger(GlowTypes.Instance, (XmlWriter)null).Dispose()); + () => new S101Logger(GlowTypes.Instance, (XmlWriter)null).Dispose(), + () => new S101Logger((IEmberConverter)null, XmlWriter.Create(writer)).Dispose()); using (var logger = new S101Logger(GlowTypes.Instance, writer)) { diff --git a/Lawo.EmberPlusSharpTest/S101/S101ReaderTest.cs b/Lawo.EmberPlusSharpTest/S101/S101ReaderTest.cs index deee13e7..f2582648 100644 --- a/Lawo.EmberPlusSharpTest/S101/S101ReaderTest.cs +++ b/Lawo.EmberPlusSharpTest/S101/S101ReaderTest.cs @@ -85,8 +85,6 @@ public void ExceptionTest() () => reader.Message.ToString(), () => reader.Payload.ToString()); } - await AssertS101Exception("Unexpected byte while looking for BOF.", GetRandomByteExcept(0xFE)); - await AssertS101Exception("CRC failed.", 0xFE, 0xFF); await AssertS101Exception("Invalid byte in frame.", 0xFE, 0xFE); @@ -160,17 +158,6 @@ public void MessageTest() //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private byte GetRandomByteExcept(params byte[] exceptions) - { - byte result; - - while (exceptions.Contains(result = (byte)this.Random.Next(byte.MinValue, byte.MaxValue + 1))) - { - } - - return result; - } - private static async Task AssertDecode(byte[] bytes, byte[] expectedPayload = null, byte slot = 0x00) where TCommand : S101Command { diff --git a/Lawo.EmberPlusSharpTest/S101/S101WriterTest.cs b/Lawo.EmberPlusSharpTest/S101/S101WriterTest.cs index a3bf969e..0505e300 100644 --- a/Lawo.EmberPlusSharpTest/S101/S101WriterTest.cs +++ b/Lawo.EmberPlusSharpTest/S101/S101WriterTest.cs @@ -10,13 +10,13 @@ namespace Lawo.EmberPlusSharp.S101 using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; + using System.Linq; using System.Threading; using System.Threading.Tasks; - using Lawo.IO; - using Lawo.Threading.Tasks; - using Lawo.UnitTesting; + using IO; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Threading.Tasks; /// Tests . [TestClass] @@ -46,6 +46,48 @@ public void WriteTest() }); } + /// Tests . + [TestCategory("Unattended")] + [TestMethod] + public void OutOfFrameByteTest() + { + AsyncPump.Run( + async () => + { + using (var asyncStream = new MemoryStream()) + { + var writer = new S101Writer(asyncStream.WriteAsync); + + try + { + await writer.WriteMessageAsync( + new S101Message(0x00, new KeepAliveRequest()), CancellationToken.None); + await writer.WriteOutOfFrameByte((byte)this.Random.Next(0xFE), CancellationToken.None); + await writer.WriteMessageAsync( + new S101Message(0x00, new KeepAliveResponse()), CancellationToken.None); + + using (var encodingStream = await writer.WriteMessageAsync(EmberDataMessage, CancellationToken.None)) + { + var prefix = new byte[] { 0x42 }; + var postfix = new byte[] { 0x43 }; + await encodingStream.WriteAsync(prefix, 0, prefix.Length, CancellationToken.None); + await writer.WriteOutOfFrameByte(0xEE, CancellationToken.None); + await encodingStream.WriteAsync(postfix, 0, postfix.Length, CancellationToken.None); + await encodingStream.DisposeAsync(CancellationToken.None); + } + } + finally + { + await writer.DisposeAsync(CancellationToken.None); + } + + var stringBytes = + asyncStream.ToArray().Select(b => b.ToString("X2", CultureInfo.InvariantCulture)); + var result = string.Join(", ", stringBytes).ToUpperInvariant(); + } + }); + } + /// Tests whether random written payload is read back the same. [TestCategory("Unattended")] [TestMethod]