From 12e180ea2db8f60367b75b4eaf5aeaf277e2fef9 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Wed, 26 Jul 2023 23:14:38 +0200 Subject: [PATCH 01/12] build: Don't warn on missing xmldoc While definitely desirable, at least temporarily disabled in order to find other warnings. --- S7.Net/S7.Net.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj index bfe62f93..8faec791 100644 --- a/S7.Net/S7.Net.csproj +++ b/S7.Net/S7.Net.csproj @@ -21,6 +21,7 @@ true snupkg true + CS1591 From 8ad25033d5aa7145275491f14f10070cdfcc802c Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Wed, 26 Jul 2023 23:38:32 +0200 Subject: [PATCH 02/12] chore: Fix xmldoc warnings --- S7.Net/COTP.cs | 2 ++ S7.Net/PLCHelpers.cs | 5 +++-- S7.Net/PlcAsynchronous.cs | 2 +- S7.Net/PlcSynchronous.cs | 1 - S7.Net/StreamExtensions.cs | 1 + S7.Net/TPKT.cs | 1 + S7.Net/Types/Class.cs | 8 +++++++- S7.Net/Types/DateTime.cs | 2 +- 8 files changed, 16 insertions(+), 6 deletions(-) diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs index 3e5abbbb..4f8b1cd4 100644 --- a/S7.Net/COTP.cs +++ b/S7.Net/COTP.cs @@ -55,6 +55,7 @@ public TPDU(TPKT tPKT) /// See: https://tools.ietf.org/html/rfc905 /// /// The socket to read from + /// A cancellation token that can be used to cancel the asynchronous operation. /// COTP DPDU instance public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) { @@ -89,6 +90,7 @@ public class TSDU /// See: https://tools.ietf.org/html/rfc905 /// /// The stream to read from + /// A cancellation token that can be used to cancel the asynchronous operation. /// Data in TSDU public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) { diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index c0fbd78f..ef26e875 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -12,8 +12,8 @@ public partial class Plc /// /// Creates the header to read bytes from the PLC /// - /// - /// + /// The stream to write to. + /// The number of items to read. private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1) { //header size = 19 bytes @@ -32,6 +32,7 @@ private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount /// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType), /// the address of the memory, the address of the byte and the bytes count. /// + /// The stream to write the read data request to. /// MemoryType (DB, Timer, Counter, etc.) /// Address of the memory to be read /// Start address of the byte diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 36fef8c6..77cd0c8b 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -428,7 +428,6 @@ public async Task WriteAsync(DataType dataType, int db, int startByteAdr, object /// /// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. - /// If the write was not successful, check or . /// /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. /// Value to be written to the PLC @@ -507,6 +506,7 @@ public async Task WriteAsync(params DataItem[] dataItems) /// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc. /// Start byte address. If you want to read DB1.DBW200, this is 200. /// Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion. + /// A cancellation token that can be used to cancel the asynchronous operation. /// A task that represents the asynchronous write operation. private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory value, CancellationToken cancellationToken) { diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 4a3aaadb..afd122af 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -289,7 +289,6 @@ public void Write(DataType dataType, int db, int startByteAdr, object value, int /// /// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. - /// If the write was not successful, check or . /// /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. /// Value to be written to the PLC diff --git a/S7.Net/StreamExtensions.cs b/S7.Net/StreamExtensions.cs index 749b9150..504e4dc1 100644 --- a/S7.Net/StreamExtensions.cs +++ b/S7.Net/StreamExtensions.cs @@ -39,6 +39,7 @@ public static int ReadExact(this Stream stream, byte[] buffer, int offset, int c /// the buffer to read into /// the offset in the buffer to read into /// the amount of bytes to read into the buffer + /// A cancellation token that can be used to cancel the asynchronous operation. /// returns the amount of read bytes public static async Task ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) { diff --git a/S7.Net/TPKT.cs b/S7.Net/TPKT.cs index a311dcec..f24b4c06 100644 --- a/S7.Net/TPKT.cs +++ b/S7.Net/TPKT.cs @@ -29,6 +29,7 @@ private TPKT(byte version, byte reserved1, int length, byte[] data) /// Reads a TPKT from the socket Async /// /// The stream to read from + /// A cancellation token that can be used to cancel the asynchronous operation. /// Task TPKT Instace public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) { diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 819b6261..fa5eb06b 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -76,6 +76,8 @@ private static double GetIncreasedNumberOfBytes(double numBytes, Type type, Prop /// Gets the size of the class in bytes. /// /// An instance of the class + /// The offset of the current field. + /// if this property belongs to a class being serialized as member of the class requested for serialization; otherwise, . /// the number of bytes public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false) { @@ -213,6 +215,8 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i /// /// The object to fill in the given array of bytes /// The array of bytes + /// The offset for the current field. + /// if this class is the type of a member of the class to be serialized; otherwise, . public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false) { if (bytes == null) @@ -320,7 +324,9 @@ private static double SetBytesFromProperty(object propertyValue, PropertyInfo? p /// /// Creates a byte array depending on the struct type. /// - /// The struct object + /// The struct object. + /// The target byte array. + /// The offset for the current field. /// A byte array or null if fails. public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0) { diff --git a/S7.Net/Types/DateTime.cs b/S7.Net/Types/DateTime.cs index 9cafa676..a685a210 100644 --- a/S7.Net/Types/DateTime.cs +++ b/S7.Net/Types/DateTime.cs @@ -141,7 +141,7 @@ byte EncodeBcd(int value) /// Converts an array of values to a byte array. /// /// The DateTime values to convert. - /// A byte array containing the S7 date time representations of . + /// A byte array containing the S7 date time representations of . /// Thrown when any value of /// is before /// or after . From 3d0dd693ba666f0a9520a4df485be505917b2d8b Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Wed, 26 Jul 2023 23:47:32 +0200 Subject: [PATCH 03/12] fix: Fix nullability warnings in Class.ToBytes --- S7.Net/Types/Class.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index fa5eb06b..0b2b34b8 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -333,19 +333,21 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = var properties = GetAccessableProperties(sourceClass.GetType()); foreach (var property in properties) { + var value = property.GetValue(sourceClass, null) ?? + throw new ArgumentException($"Property {property.Name} on sourceClass can't be null.", nameof(sourceClass)); + if (property.PropertyType.IsArray) { - Array array = (Array)property.GetValue(sourceClass, null); + Array array = (Array) value; IncrementToEven(ref numBytes); - Type elementType = property.PropertyType.GetElementType(); for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { - numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes); + numBytes = SetBytesFromProperty(array.GetValue(i)!, property, bytes, numBytes); } } else { - numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes); + numBytes = SetBytesFromProperty(value, property, bytes, numBytes); } } return numBytes; From c3f86c32a2a2ac1d55583e42de7975149354403c Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Wed, 26 Jul 2023 23:52:28 +0200 Subject: [PATCH 04/12] fix: Fix nullability warnings in Class.FromBytes --- S7.Net/Types/Class.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 0b2b34b8..c333d835 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -227,9 +227,11 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes { if (property.PropertyType.IsArray) { - Array array = (Array)property.GetValue(sourceClass, null); + Array array = (Array?) property.GetValue(sourceClass, null) ?? + throw new ArgumentException($"Property {property.Name} on sourceClass must be an array instance.", nameof(sourceClass)); + IncrementToEven(ref numBytes); - Type elementType = property.PropertyType.GetElementType(); + Type elementType = property.PropertyType.GetElementType()!; for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { array.SetValue( From 0bb7c5351a2384cda33a023d93213fef6aa56347 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Wed, 26 Jul 2023 23:59:21 +0200 Subject: [PATCH 05/12] ci: Update actions to Node 16 compatible versions --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bc7b024b..8116e8b7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -75,7 +75,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Snap7 Linux if: ${{ matrix.os == 'ubuntu-20.04' }} @@ -90,14 +90,14 @@ jobs: brew install snap7 - name: Setup Dotnet - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: | 6.x 7.x - name: Nuget Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.nuget/packages # Look to see if there is a cache hit for the corresponding requirements file From 4aca9e4e53457fbaf5ac357d524f62bf4d3bda1c Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Thu, 27 Jul 2023 00:11:05 +0200 Subject: [PATCH 06/12] fix: Fix remaining nullability warnings in Class --- S7.Net/Types/Class.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index c333d835..be84c2b9 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -64,7 +64,8 @@ private static double GetIncreasedNumberOfBytes(double numBytes, Type type, Prop numBytes += attribute.ReservedLengthInBytes; break; default: - var propertyClass = Activator.CreateInstance(type); + var propertyClass = Activator.CreateInstance(type) ?? + throw new ArgumentException($"Failed to create instance of type {type}.", nameof(type)); numBytes = GetClassSize(propertyClass, numBytes, true); break; } @@ -86,8 +87,10 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i { if (property.PropertyType.IsArray) { - Type elementType = property.PropertyType.GetElementType(); - Array array = (Array)property.GetValue(instance, null); + Type elementType = property.PropertyType.GetElementType()!; + Array array = (Array?) property.GetValue(instance, null) ?? + throw new ArgumentException($"Property {property.Name} on {instance} must have a non-null value to get it's size.", nameof(instance)); + if (array.Length <= 0) { throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero."); @@ -201,7 +204,9 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += sData.Length; break; default: - var propClass = Activator.CreateInstance(propertyType); + var propClass = Activator.CreateInstance(propertyType) ?? + throw new ArgumentException($"Failed to create instance of type {propertyType}.", nameof(propertyType)); + numBytes = FromBytes(propClass, bytes, numBytes); value = propClass; break; From 71f7f8b400075d0783e37b00a6e936d5499528e1 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Thu, 27 Jul 2023 00:16:40 +0200 Subject: [PATCH 07/12] fix: Fix nullability warning in String.ToByteArray --- S7.Net/Types/String.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/S7.Net/Types/String.cs b/S7.Net/Types/String.cs index 39176352..b0ccc190 100644 --- a/S7.Net/Types/String.cs +++ b/S7.Net/Types/String.cs @@ -12,13 +12,15 @@ public class String /// The amount of bytes reserved for the in the PLC. public static byte[] ToByteArray(string value, int reservedLength) { - var length = value?.Length; - if (length > reservedLength) length = reservedLength; var bytes = new byte[reservedLength]; + if (value == null) return bytes; + + var length = value.Length; + if (length == 0) return bytes; - if (length == null || length == 0) return bytes; + if (length > reservedLength) length = reservedLength; - System.Text.Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 0); + System.Text.Encoding.ASCII.GetBytes(value, 0, length, bytes, 0); return bytes; } From b27e1c90831d29d98e9365c64c3a9d5177d6ba28 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 28 Jul 2023 23:50:53 +0200 Subject: [PATCH 08/12] build: Set LangVersion to latest --- S7.Net/S7.Net.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj index 8faec791..eeed409e 100644 --- a/S7.Net/S7.Net.csproj +++ b/S7.Net/S7.Net.csproj @@ -15,7 +15,7 @@ git PLC Siemens Communication S7 Derek Heiser 2015 - 8.0 + latest Enable portable true From b61ac3291368b4f8db43a5573ee05e60f52c7c25 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 28 Jul 2023 23:52:57 +0200 Subject: [PATCH 09/12] fix: Permit nulls in string ToByteArray conversions --- S7.Net/Types/S7String.cs | 2 +- S7.Net/Types/S7WString.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/S7.Net/Types/S7String.cs b/S7.Net/Types/S7String.cs index 46c4808a..290aa533 100644 --- a/S7.Net/Types/S7String.cs +++ b/S7.Net/Types/S7String.cs @@ -58,7 +58,7 @@ public static string FromByteArray(byte[] bytes) /// The string to convert to byte array. /// The length (in characters) allocated in PLC for the string. /// A containing the string header and string value with a maximum length of + 2. - public static byte[] ToByteArray(string value, int reservedLength) + public static byte[] ToByteArray(string? value, int reservedLength) { if (value is null) { diff --git a/S7.Net/Types/S7WString.cs b/S7.Net/Types/S7WString.cs index 8d8aabfc..001c7ef3 100644 --- a/S7.Net/Types/S7WString.cs +++ b/S7.Net/Types/S7WString.cs @@ -48,7 +48,7 @@ public static string FromByteArray(byte[] bytes) /// The string to convert to byte array. /// The length (in characters) allocated in PLC for the string. /// A containing the string header and string value with a maximum length of + 4. - public static byte[] ToByteArray(string value, int reservedLength) + public static byte[] ToByteArray(string? value, int reservedLength) { if (value is null) { From c5023c10e412903781fbb6d3aed16a85b3a26dde Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 28 Jul 2023 23:54:15 +0200 Subject: [PATCH 10/12] style: Cleanup line endings in S7String --- S7.Net/Types/S7String.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/S7.Net/Types/S7String.cs b/S7.Net/Types/S7String.cs index 290aa533..d45c5349 100644 --- a/S7.Net/Types/S7String.cs +++ b/S7.Net/Types/S7String.cs @@ -8,17 +8,17 @@ namespace S7.Net.Types /// An S7 String has a preceeding 2 byte header containing its capacity and length /// public static class S7String - { - private static Encoding stringEncoding = Encoding.ASCII; - + { + private static Encoding stringEncoding = Encoding.ASCII; + /// /// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default) /// - /// StringEncoding must not be null - public static Encoding StringEncoding - { - get => stringEncoding; - set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding)); + /// StringEncoding must not be null + public static Encoding StringEncoding + { + get => stringEncoding; + set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding)); } /// From 6e103cea63588102a5a7cfdfde732ab4bcbd3aa0 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 28 Jul 2023 23:55:12 +0200 Subject: [PATCH 11/12] fix: Fix warnings in Struct --- S7.Net/Types/Struct.cs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/S7.Net/Types/Struct.cs b/S7.Net/Types/Struct.cs index 1e955086..6f29447d 100644 --- a/S7.Net/Types/Struct.cs +++ b/S7.Net/Types/Struct.cs @@ -98,8 +98,8 @@ public static int GetStructSize(Type structType) int bytePos = 0; int bitPos = 0; double numBytes = 0.0; - object structValue = Activator.CreateInstance(structType); - + object structValue = Activator.CreateInstance(structType) ?? + throw new ArgumentException($"Failed to create an instance of the type {structType}.", nameof(structType)); var infos = structValue.GetType() #if NETSTANDARD1_3 @@ -254,6 +254,14 @@ public static byte[] ToBytes(object structValue) foreach (var info in infos) { + static TValue GetValueOrThrow(FieldInfo fi, object structValue) where TValue : struct + { + var value = fi.GetValue(structValue) as TValue? ?? + throw new ArgumentException($"Failed to convert value of field {fi.Name} of {structValue} to type {typeof(TValue)}"); + + return value; + } + bytes2 = null; switch (info.FieldType.Name) { @@ -261,7 +269,7 @@ public static byte[] ToBytes(object structValue) // get the value bytePos = (int)Math.Floor(numBytes); bitPos = (int)((numBytes - (double)bytePos) / 0.125); - if ((bool)info.GetValue(structValue)) + if (GetValueOrThrow(info, structValue)) bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true else bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false @@ -270,26 +278,26 @@ public static byte[] ToBytes(object structValue) case "Byte": numBytes = (int)Math.Ceiling(numBytes); bytePos = (int)numBytes; - bytes[bytePos] = (byte)info.GetValue(structValue); + bytes[bytePos] = GetValueOrThrow(info, structValue); numBytes++; break; case "Int16": - bytes2 = Int.ToByteArray((Int16)info.GetValue(structValue)); + bytes2 = Int.ToByteArray(GetValueOrThrow(info, structValue)); break; case "UInt16": - bytes2 = Word.ToByteArray((UInt16)info.GetValue(structValue)); + bytes2 = Word.ToByteArray(GetValueOrThrow(info, structValue)); break; case "Int32": - bytes2 = DInt.ToByteArray((Int32)info.GetValue(structValue)); + bytes2 = DInt.ToByteArray(GetValueOrThrow(info, structValue)); break; case "UInt32": - bytes2 = DWord.ToByteArray((UInt32)info.GetValue(structValue)); + bytes2 = DWord.ToByteArray(GetValueOrThrow(info, structValue)); break; case "Single": - bytes2 = Real.ToByteArray((float)info.GetValue(structValue)); + bytes2 = Real.ToByteArray(GetValueOrThrow(info, structValue)); break; case "Double": - bytes2 = LReal.ToByteArray((double)info.GetValue(structValue)); + bytes2 = LReal.ToByteArray(GetValueOrThrow(info, structValue)); break; case "String": S7StringAttribute? attribute = info.GetCustomAttributes().SingleOrDefault(); @@ -298,8 +306,8 @@ public static byte[] ToBytes(object structValue) bytes2 = attribute.Type switch { - S7StringType.S7String => S7String.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength), - S7StringType.S7WString => S7WString.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength), + S7StringType.S7String => S7String.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength), + S7StringType.S7WString => S7WString.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength), _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute") }; break; From e26860b0c02df7c995e317261db4d85685617223 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 28 Jul 2023 23:57:55 +0200 Subject: [PATCH 12/12] build: Extend NoWarn - Amend existing NoWarn if set - Ignore out of support target framework warning --- S7.Net/S7.Net.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj index eeed409e..a5643f5d 100644 --- a/S7.Net/S7.Net.csproj +++ b/S7.Net/S7.Net.csproj @@ -21,7 +21,7 @@ true snupkg true - CS1591 + $(NoWarn);CS1591;NETSDK1138