From 2204ab360cfe3a25da19ef65e2efa90afddd4900 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Wed, 11 Jul 2018 09:47:43 +0200 Subject: [PATCH] Fix write stringex (#162) * Add StringEx.ToByteArray(...) * Add Serialization.SerializeDataItem(DataItem) Supports StringEx VarType or offloads to SerializeValue method. * Use SerializeDataItem in S7WriteMultiple * Assume string length without header in StringEx.ToByteArray VarTypeToByteLength already assumed that StringEx declared count for the number of characters without the header, this now matches that behavior. * Add unit tests for StringEx conversions * Fix incorrect value passed to Encoding.GetBytes The length must actually be within string limits. --- S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/TypeTests/StringExTests.cs | 115 +++++++++++++++++++++ S7.Net/Protocol/S7WriteMultiple.cs | 2 +- S7.Net/Protocol/Serialization.cs | 10 ++ S7.Net/Types/StringEx.cs | 28 ++++- 5 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 S7.Net.UnitTest/TypeTests/StringExTests.cs diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index bfaa48e..d65a804 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -78,6 +78,7 @@ + diff --git a/S7.Net.UnitTest/TypeTests/StringExTests.cs b/S7.Net.UnitTest/TypeTests/StringExTests.cs new file mode 100644 index 0000000..164ba4f --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/StringExTests.cs @@ -0,0 +1,115 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using S7.Net.Types; + +namespace S7.Net.UnitTest.TypeTests +{ + [TestClass] + public class StringExTests + { + [TestMethod] + public void ReadEmptyStringWithZeroByteLength() + { + AssertFromByteArrayEquals("", 0, 0); + } + + [TestMethod] + public void ReadEmptyStringWithOneByteLength() + { + AssertFromByteArrayEquals("", 1, 0, 0); + } + + [TestMethod] + public void ReadEmptyStringWithOneByteGarbage() + { + AssertFromByteArrayEquals("", 1, 0, (byte) 'a'); + } + + [TestMethod] + public void ReadA() + { + AssertFromByteArrayEquals("A", 1, 1, (byte) 'A'); + } + + [TestMethod] + public void ReadAbc() + { + AssertFromByteArrayEquals("Abc", 1, 3, (byte) 'A', (byte) 'b', (byte) 'c'); + } + + [TestMethod] + public void WriteNullWithReservedLengthZero() + { + AssertToByteArrayEquals(null, 0, 0, 0); + } + + [TestMethod] + public void WriteEmptyStringWithReservedLengthZero() + { + AssertToByteArrayEquals("", 0, 0, 0); + } + + [TestMethod] + public void WriteAWithReservedLengthZero() + { + AssertToByteArrayEquals("A", 0, 0, 0); + } + + [TestMethod] + public void WriteNullWithReservedLengthOne() + { + AssertToByteArrayEquals(null, 1, 1, 0); + } + + [TestMethod] + public void WriteEmptyStringWithReservedLengthOne() + { + AssertToByteArrayEquals("", 1, 1, 0); + } + + [TestMethod] + public void WriteAWithReservedLengthOne() + { + AssertToByteArrayEquals("A", 1, 1, 1, (byte) 'A'); + } + + [TestMethod] + public void WriteAWithReservedLengthTwo() + { + AssertToByteArrayEquals("A", 2, 2, 1, (byte) 'A'); + } + + [TestMethod] + public void WriteAbcWithReservedLengthOne() + { + AssertToByteArrayEquals("Abc", 1, 1, 1, (byte) 'A'); + } + + [TestMethod] + public void WriteAbcWithReservedLengthTwo() + { + AssertToByteArrayEquals("Abc", 2, 2, 2, (byte) 'A', (byte) 'b'); + } + + [TestMethod] + public void WriteAbcWithReservedLengthThree() + { + AssertToByteArrayEquals("Abc", 3, 3, 3, (byte) 'A', (byte) 'b', (byte) 'c'); + } + + [TestMethod] + public void WriteAbcWithReservedLengthFour() + { + AssertToByteArrayEquals("Abc", 4, 4, 3, (byte) 'A', (byte) 'b', (byte) 'c'); + } + + private static void AssertFromByteArrayEquals(string expected, params byte[] bytes) + { + Assert.AreEqual(expected, StringEx.FromByteArray(bytes)); + } + + private static void AssertToByteArrayEquals(string value, int reservedLength, params byte[] expected) + { + CollectionAssert.AreEqual(expected, StringEx.ToByteArray(value, reservedLength)); + } + } +} diff --git a/S7.Net/Protocol/S7WriteMultiple.cs b/S7.Net/Protocol/S7WriteMultiple.cs index f4b2457..f5f32bd 100644 --- a/S7.Net/Protocol/S7WriteMultiple.cs +++ b/S7.Net/Protocol/S7WriteMultiple.cs @@ -23,7 +23,7 @@ namespace S7.Net.Protocol foreach (var item in dataItems) { message.Add(Parameter.Template); - var value = Serialization.SerializeValue(item.Value); + var value = Serialization.SerializeDataItem(item); var wordLen = item.Value is bool ? 1 : 2; message[paramOffset + Parameter.Offsets.WordLength] = (byte) wordLen; diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs index acda50b..8e3c4da 100644 --- a/S7.Net/Protocol/Serialization.cs +++ b/S7.Net/Protocol/Serialization.cs @@ -11,6 +11,16 @@ namespace S7.Net.Protocol return (ushort)((buf[index] << 8) + buf[index]); } + public static byte[] SerializeDataItem(DataItem dataItem) + { + if (dataItem.Value is string s) + return dataItem.VarType == VarType.StringEx + ? StringEx.ToByteArray(s, dataItem.Count) + : Types.String.ToByteArray(s); + + return SerializeValue(dataItem.Value); + } + public static byte[] SerializeValue(object value) { switch (value.GetType().Name) diff --git a/S7.Net/Types/StringEx.cs b/S7.Net/Types/StringEx.cs index e22282b..1aec9ab 100644 --- a/S7.Net/Types/StringEx.cs +++ b/S7.Net/Types/StringEx.cs @@ -1,4 +1,7 @@ -namespace S7.Net.Types +using System; +using System.Text; + +namespace S7.Net.Types { /// /// Contains the methods to convert from S7 strings to C# strings @@ -21,6 +24,27 @@ return System.Text.Encoding.ASCII.GetString(bytes, 2, length); } - + + /// + /// Converts a to S7 string with 2-byte header. + /// + /// The string to convert to byte array. + /// The length (in bytes) allocated in PLC for string excluding header. + /// A containing the string header and string value with a maximum length of + 2. + public static byte[] ToByteArray(string value, int reservedLength) + { + if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}."); + + var length = value?.Length; + if (length > reservedLength) length = reservedLength; + + var bytes = new byte[(length ?? 0) + 2]; + bytes[0] = (byte) reservedLength; + + if (value == null) return bytes; + + bytes[1] = (byte) Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 2); + return bytes; + } } }