Merge pull request #363 from MCPC10/WString

Added WString support + fixed max size in S7String
This commit is contained in:
Michael Croes
2021-01-27 18:55:12 +01:00
committed by GitHub
7 changed files with 226 additions and 9 deletions

View File

@@ -100,7 +100,7 @@ namespace S7.Net.UnitTest.TypeTests
}
[TestMethod]
public void WriteAbcWithStringLargetThanReservedLength()
public void WriteAbcWithStringLargerThanReservedLength()
{
Assert.ThrowsException<ArgumentException>(() => S7String.ToByteArray("Abc", 2));
}

View File

@@ -0,0 +1,134 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
using System;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class S7WStringTests
{
[TestMethod]
public void ReadEmptyStringWithZeroLength()
{
AssertFromByteArrayEquals("", 0, 0 , 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneCharLength()
{
AssertFromByteArrayEquals("", 0, 1, 0, 0, 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneCharGarbage()
{
AssertFromByteArrayEquals("", 0, 1, 0, 0, 0x00, 0x41);
}
[TestMethod]
public void ReadMalformedStringTooShort()
{
Assert.ThrowsException<PlcException>(() => AssertFromByteArrayEquals("", 0, 1));
}
[TestMethod]
public void ReadMalformedStringSizeLargerThanCapacity()
{
Assert.ThrowsException<PlcException>(() => S7WString.FromByteArray(new byte[] { 0, 3, 0, 5, 0, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41}));
}
[TestMethod]
public void ReadMalformedStringCapacityTooLarge()
{
Assert.ThrowsException<ArgumentException>(() => AssertToByteArrayAndBackEquals("", 20000, 0));
}
[TestMethod]
public void ReadA()
{
AssertFromByteArrayEquals("A", 0, 1, 0, 1, 0x00, 0x41);
}
[TestMethod]
public void ReadAbc()
{
AssertFromByteArrayEquals("Abc", 0, 3, 0, 3, 0x00, 0x41, 0x00, 0x62, 0x00, 0x63);
}
[TestMethod]
public void WriteNullWithReservedLengthZero()
{
Assert.ThrowsException<ArgumentNullException>(() => AssertToByteArrayAndBackEquals(null, 0, 0, 0, 0, 0));
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthZero()
{
AssertToByteArrayAndBackEquals("", 0, 0, 0, 0, 0);
}
[TestMethod]
public void WriteAWithReservedLengthZero()
{
AssertToByteArrayAndBackEquals("", 0, 0, 0, 0, 0);
}
[TestMethod]
public void WriteNullWithReservedLengthOne()
{
Assert.ThrowsException<ArgumentNullException>(() => AssertToByteArrayAndBackEquals(null, 1, 0, 1 , 0, 0));
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthOne()
{
AssertToByteArrayAndBackEquals("", 1, 0, 1, 0, 0, 0, 0);
}
[TestMethod]
public void WriteAWithReservedLengthOne()
{
AssertToByteArrayAndBackEquals("A", 1, 0, 1, 0, 1, 0x00, 0x41);
}
[TestMethod]
public void WriteAWithReservedLengthTwo()
{
AssertToByteArrayAndBackEquals("A", 2, 0, 2, 0, 1, 0x00, 0x41, 0, 0);
}
[TestMethod]
public void WriteAbcWithStringLargerThanReservedLength()
{
Assert.ThrowsException<ArgumentException>(() => S7WString.ToByteArray("Abc", 2));
}
[TestMethod]
public void WriteAbcWithReservedLengthThree()
{
AssertToByteArrayAndBackEquals("Abc", 3, 0, 3, 0, 3, 0x00, 0x41, 0x00, 0x62, 0x00, 0x63);
}
[TestMethod]
public void WriteAbcWithReservedLengthFour()
{
AssertToByteArrayAndBackEquals("Abc", 4, 0, 4, 0, 3, 0x00, 0x41, 0x00, 0x62, 0x00, 0x63, 0 , 0);
}
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{
var convertedString = S7WString.FromByteArray(bytes);
Assert.AreEqual(expected, convertedString);
}
private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected)
{
var convertedData = S7WString.ToByteArray(value, reservedLength);
CollectionAssert.AreEqual(expected, convertedData);
var convertedBack = S7WString.FromByteArray(convertedData);
Assert.AreEqual(value, convertedBack);
}
}
}

View File

@@ -178,6 +178,11 @@
/// </summary>
S7String,
/// <summary>
/// S7 WString variable type (variable)
/// </summary>
S7WString,
/// <summary>
/// Timer variable type
/// </summary>

View File

@@ -125,6 +125,8 @@ namespace S7.Net
return Types.String.FromByteArray(bytes);
case VarType.S7String:
return S7String.FromByteArray(bytes);
case VarType.S7WString:
return S7WString.FromByteArray(bytes);
case VarType.Timer:
if (varCount == 1)

View File

@@ -17,10 +17,14 @@ namespace S7.Net.Protocol
{
throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}");
}
if (dataItem.Value is string s)
return dataItem.VarType == VarType.S7String
? S7String.ToByteArray(s, dataItem.Count)
: Types.String.ToByteArray(s, dataItem.Count);
return dataItem.VarType switch
{
VarType.S7String => S7String.ToByteArray(s, dataItem.Count),
VarType.S7WString => S7WString.ToByteArray(s, dataItem.Count),
_ => Types.String.ToByteArray(s, dataItem.Count)
};
return SerializeValue(dataItem.Value);
}
@@ -46,7 +50,7 @@ namespace S7.Net.Protocol
case "Double":
return Types.LReal.ToByteArray((double)value);
case "DateTime":
return Types.DateTime.ToByteArray((System.DateTime) value);
return Types.DateTime.ToByteArray((System.DateTime)value);
case "Byte[]":
return (byte[])value;
case "Int16[]":
@@ -64,10 +68,10 @@ namespace S7.Net.Protocol
case "String":
// Hack: This is backwards compatible with the old code, but functionally it's broken
// if the consumer does not pay attention to string length.
var stringVal = (string) value;
var stringVal = (string)value;
return Types.String.ToByteArray(stringVal, stringVal.Length);
case "DateTime[]":
return Types.DateTime.ToByteArray((System.DateTime[]) value);
return Types.DateTime.ToByteArray((System.DateTime[])value);
case "DateTimeLong[]":
return Types.DateTimeLong.ToByteArray((System.DateTime[])value);
default:

View File

@@ -45,7 +45,7 @@ namespace S7.Net.Types
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in bytes) allocated in PLC for string excluding header.</param>
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
{
@@ -54,7 +54,7 @@ namespace S7.Net.Types
throw new ArgumentNullException(nameof(value));
}
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
if (reservedLength > 254) throw new ArgumentException($"The maximum string length supported is 254.");
var bytes = Encoding.ASCII.GetBytes(value);
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");

72
S7.Net/Types/S7WString.cs Normal file
View File

@@ -0,0 +1,72 @@
using System;
using System.Text;
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert from S7 wstrings to C# strings
/// An S7 WString has a preceding 4 byte header containing its capacity and length
/// </summary>
public static class S7WString
{
/// <summary>
/// Converts S7 bytes to a string
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string FromByteArray(byte[] bytes)
{
if (bytes.Length < 4)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 WString / too short");
}
int size = (bytes[0] << 8) | bytes[1];
int length = (bytes[2] << 8) | bytes[3];
if (length > size)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 WString / length larger than capacity");
}
try
{
return Encoding.BigEndianUnicode.GetString(bytes, 4, length * 2);
}
catch (Exception e)
{
throw new PlcException(ErrorCode.ReadData,
$"Failed to parse {VarType.S7WString} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.",
e);
}
}
/// <summary>
/// Converts a <see cref="T:string"/> to S7 wstring with 4-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 4.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (reservedLength > 16382) throw new ArgumentException("The maximum string length supported is 16382.");
var buffer = new byte[4 + reservedLength * 2];
buffer[0] = (byte)((reservedLength >> 8) & 0xFF);
buffer[1] = (byte)(reservedLength & 0xFF);
buffer[2] = (byte)((value.Length >> 8) & 0xFF);
buffer[3] = (byte)(value.Length & 0xFF);
var stringLength = Encoding.BigEndianUnicode.GetBytes(value, 0, value.Length, buffer, 4) / 2;
if (stringLength > reservedLength) throw new ArgumentException($"The provided string length ({stringLength} is larger than the specified reserved length ({reservedLength}).");
return buffer;
}
}
}