diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 86d7f22..38f12af 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -591,15 +591,13 @@ namespace S7.Net.UnitTest [TestMethod] - [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected() { using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = new TestClass(); - var res = await notConnectedPlc.ReadClassAsync(tc, DB2); - Assert.Fail(); + await Assert.ThrowsExceptionAsync(async () => await notConnectedPlc.ReadClassAsync(tc, DB2)); } } @@ -637,13 +635,12 @@ namespace S7.Net.UnitTest } [TestMethod] - [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadClassWithGenericReturnsNullIfPlcIsNotConnected() { using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); - TestClass tc = await notConnectedPlc.ReadClassAsync(DB2); + await Assert.ThrowsExceptionAsync(async () => await notConnectedPlc.ReadClassAsync(DB2)); } } @@ -678,13 +675,12 @@ namespace S7.Net.UnitTest } [TestMethod] - [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadClassWithGenericAndClassFactoryThrowsExceptionPlcIsNotConnected() { using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); - TestClass tc = await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2); + await Assert.ThrowsExceptionAsync(async () => await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2)); } } @@ -711,13 +707,12 @@ namespace S7.Net.UnitTest } [TestMethod] - [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected() { using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); - object tsObj = await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2); + await Assert.ThrowsExceptionAsync(async () => await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2)); } } @@ -754,13 +749,12 @@ namespace S7.Net.UnitTest } [TestMethod] - [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructWithGenericThrowsExceptionIfPlcIsNotConnected() { using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); - object tsObj = await notConnectedPlc.ReadStructAsync(DB2); + await Assert.ThrowsExceptionAsync(async () => await notConnectedPlc.ReadStructAsync(DB2)); } } diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs index 7604c0d..8f60423 100644 --- a/S7.Net/COTP.cs +++ b/S7.Net/COTP.cs @@ -54,8 +54,11 @@ namespace S7.Net public static TPDU Read(Stream stream) { var tpkt = TPKT.Read(stream); - if (tpkt.Length > 0) return new TPDU(tpkt); - return null; + if (tpkt.Length == 0) + { + throw new TPDUInvalidException("No protocol data received"); + } + return new TPDU(tpkt); } /// @@ -67,8 +70,11 @@ namespace S7.Net public static async Task ReadAsync(Stream stream) { var tpkt = await TPKT.ReadAsync(stream); - if (tpkt.Length > 0) return new TPDU(tpkt); - return null; + if (tpkt.Length == 0) + { + throw new TPDUInvalidException("No protocol data received"); + } + return new TPDU(tpkt); } public override string ToString() @@ -98,7 +104,6 @@ namespace S7.Net public static byte[] Read(Stream stream) { var segment = TPDU.Read(stream); - if (segment == null) return null; var buffer = new byte[segment.Data.Length]; var output = new MemoryStream(buffer); @@ -125,7 +130,6 @@ namespace S7.Net public static async Task ReadAsync(Stream stream) { var segment = await TPDU.ReadAsync(stream); - if (segment == null) return null; var buffer = new byte[segment.Data.Length]; var output = new MemoryStream(buffer); diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 8d98d4f..f11e568 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -15,8 +15,8 @@ namespace S7.Net private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060; //TCP connection to device - private TcpClient tcpClient; - private NetworkStream stream; + private TcpClient? tcpClient; + private NetworkStream? _stream; private int readTimeout = System.Threading.Timeout.Infinite; private int writeTimeout = System.Threading.Timeout.Infinite; @@ -24,27 +24,27 @@ namespace S7.Net /// /// IP address of the PLC /// - public string IP { get; private set; } + public string IP { get; } /// /// PORT Number of the PLC, default is 102 /// - public int Port { get; private set; } + public int Port { get; } /// /// CPU type of the PLC /// - public CpuType CPU { get; private set; } + public CpuType CPU { get; } /// /// Rack of the PLC /// - public Int16 Rack { get; private set; } + public Int16 Rack { get; } /// /// Slot of the CPU of the PLC /// - public Int16 Slot { get; private set; } + public Int16 Slot { get; } /// /// Max PDU size this cpu supports diff --git a/S7.Net/PLCExceptions.cs b/S7.Net/PLCExceptions.cs index c06823b..2b1034d 100644 --- a/S7.Net/PLCExceptions.cs +++ b/S7.Net/PLCExceptions.cs @@ -89,4 +89,25 @@ namespace S7.Net } #endif } + + internal class TPDUInvalidException : Exception + { + public TPDUInvalidException() : base() + { + } + + public TPDUInvalidException(string message) : base(message) + { + } + + public TPDUInvalidException(string message, Exception innerException) : base(message, innerException) + { + } + +#if NET_FULL + protected TPDUInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } } diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 602c6a6..2286264 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -82,7 +82,7 @@ namespace S7.Net /// /// /// - private object ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0) + private object? ParseBytes(VarType varType, byte[] bytes, int varCount, byte bitAdr = 0) { if (bytes == null || bytes.Length == 0) return null; diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 9e82c8d..601a3a4 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net.Sockets; using System.Threading.Tasks; using S7.Net.Protocol; +using System.IO; namespace S7.Net { @@ -20,9 +21,14 @@ namespace S7.Net public async Task OpenAsync() { await ConnectAsync(); + var stream = GetStreamIfAvailable(); await stream.WriteAsync(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22); var response = await COTP.TPDU.ReadAsync(stream); + if (response == null) + { + throw new Exception("Error reading Connection Confirm. Malformed TPDU packet"); + } if (response.PDUType != 0xd0) //Connect Confirm { throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d); @@ -46,9 +52,10 @@ namespace S7.Net tcpClient = new TcpClient(); ConfigureConnection(); await tcpClient.ConnectAsync(IP, Port); - stream = tcpClient.GetStream(); + _stream = tcpClient.GetStream(); } + /// /// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. /// If the read was not successful, check LastErrorCode or LastErrorString. @@ -87,7 +94,7 @@ namespace S7.Net /// Type of the variable/s that you are reading /// Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter. /// - public async Task ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0) + public async Task ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0) { int cntBytes = VarTypeToByteLength(varType, varCount); byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes); @@ -100,7 +107,7 @@ namespace S7.Net /// /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. /// Returns an object that contains the value. This object must be cast accordingly. - public async Task ReadAsync(string variable) + public async Task ReadAsync(string variable) { var adr = new PLCAddress(variable); return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber); @@ -113,7 +120,7 @@ namespace S7.Net /// Address of the DB. /// Start byte address. If you want to read DB1.DBW200, this is 200. /// Returns a struct that must be cast. - public async Task ReadStructAsync(Type structType, int db, int startByteAdr = 0) + public async Task ReadStructAsync(Type structType, int db, int startByteAdr = 0) { int numBytes = Types.Struct.GetStructSize(structType); // now read the package @@ -168,7 +175,7 @@ namespace S7.Net /// Index of the DB; es.: 1 is for DB1 /// Start byte address. If you want to read DB1.DBW200, this is 200. /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned - public async Task ReadClassAsync(int db, int startByteAdr = 0) where T : class + public async Task ReadClassAsync(int db, int startByteAdr = 0) where T : class { return await ReadClassAsync(() => Activator.CreateInstance(), db, startByteAdr); } @@ -182,7 +189,7 @@ namespace S7.Net /// Index of the DB; es.: 1 is for DB1 /// Start byte address. If you want to read DB1.DBW200, this is 200. /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned - public async Task ReadClassAsync(Func classFactory, int db, int startByteAdr = 0) where T : class + public async Task ReadClassAsync(Func classFactory, int db, int startByteAdr = 0) where T : class { var instance = classFactory(); var res = await ReadClassAsync(instance, db, startByteAdr); @@ -208,7 +215,9 @@ namespace S7.Net //Snap7 seems to choke on PDU sizes above 256 even if snap7 //replies with bigger PDU size in connection setup. AssertPduSizeForRead(dataItems); - + + var stream = GetStreamIfAvailable(); + try { // first create the header @@ -376,6 +385,8 @@ namespace S7.Net private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count) { + var stream = GetStreamIfAvailable(); + byte[] bytes = new byte[count]; // first create the header @@ -406,6 +417,8 @@ namespace S7.Net { AssertPduSizeForWrite(dataItems); + var stream = GetStreamIfAvailable(); + var message = new ByteArray(); var length = S7WriteMultiple.CreateRequest(message, dataItems); await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false); @@ -424,6 +437,8 @@ namespace S7.Net /// A task that represents the asynchronous write operation. private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value) { + var stream = GetStreamIfAvailable(); + byte[] bReceive = new byte[513]; int varCount = 0; @@ -469,6 +484,8 @@ namespace S7.Net private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) { + var stream = GetStreamIfAvailable(); + byte[] bReceive = new byte[513]; int varCount = 0; @@ -510,5 +527,14 @@ namespace S7.Net throw new PlcException(ErrorCode.WriteData, exc); } } + + private Stream GetStreamIfAvailable() + { + if (_stream == null) + { + throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected"); + } + return _stream; + } } } diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index c101b3e..74b02bc 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -19,6 +19,7 @@ namespace S7.Net try { + var stream = GetStreamIfAvailable(); stream.Write(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22); var response = COTP.TPDU.Read(stream); if (response.PDUType != 0xd0) //Connect Confirm @@ -52,7 +53,7 @@ namespace S7.Net tcpClient = new TcpClient(); ConfigureConnection(); tcpClient.Connect(IP, Port); - stream = tcpClient.GetStream(); + _stream = tcpClient.GetStream(); } catch (SocketException sex) { @@ -106,7 +107,7 @@ namespace S7.Net /// Type of the variable/s that you are reading /// Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter. /// - public object Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0) + public object? Read(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0) { int cntBytes = VarTypeToByteLength(varType, varCount); byte[] bytes = ReadBytes(dataType, db, startByteAdr, cntBytes); @@ -119,8 +120,8 @@ namespace S7.Net /// If the read was not successful, check LastErrorCode or LastErrorString. /// /// Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc. - /// Returns an object that contains the value. This object must be cast accordingly. - public object Read(string variable) + /// Returns an object that contains the value. This object must be cast accordingly. If no data has been read, null will be returned + public object? Read(string variable) { var adr = new PLCAddress(variable); return Read(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber); @@ -132,8 +133,8 @@ namespace S7.Net /// Type of the struct to be readed (es.: TypeOf(MyStruct)). /// Address of the DB. /// Start byte address. If you want to read DB1.DBW200, this is 200. - /// Returns a struct that must be cast. - public object ReadStruct(Type structType, int db, int startByteAdr = 0) + /// Returns a struct that must be cast. If no data has been read, null will be returned + public object? ReadStruct(Type structType, int db, int startByteAdr = 0) { int numBytes = Struct.GetStructSize(structType); // now read the package @@ -188,7 +189,7 @@ namespace S7.Net /// Index of the DB; es.: 1 is for DB1 /// Start byte address. If you want to read DB1.DBW200, this is 200. /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned - public T ReadClass(int db, int startByteAdr = 0) where T : class + public T? ReadClass(int db, int startByteAdr = 0) where T : class { return ReadClass(() => Activator.CreateInstance(), db, startByteAdr); } @@ -202,7 +203,7 @@ namespace S7.Net /// Index of the DB; es.: 1 is for DB1 /// Start byte address. If you want to read DB1.DBW200, this is 200. /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned - public T ReadClass(Func classFactory, int db, int startByteAdr = 0) where T : class + public T? ReadClass(Func classFactory, int db, int startByteAdr = 0) where T : class { var instance = classFactory(); int readBytes = ReadClass(instance, db, startByteAdr); @@ -340,6 +341,7 @@ namespace S7.Net private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, int count) { + var stream = GetStreamIfAvailable(); byte[] bytes = new byte[count]; try { @@ -375,6 +377,8 @@ namespace S7.Net { AssertPduSizeForWrite(dataItems); + var stream = GetStreamIfAvailable(); + var message = new ByteArray(); var length = S7WriteMultiple.CreateRequest(message, dataItems); stream.Write(message.Array, 0, length); @@ -385,6 +389,7 @@ namespace S7.Net private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value) { + var stream = GetStreamIfAvailable(); int varCount = 0; try { @@ -429,6 +434,7 @@ namespace S7.Net private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) { + var stream = GetStreamIfAvailable(); int varCount = 0; try @@ -484,6 +490,8 @@ namespace S7.Net //replies with bigger PDU size in connection setup. AssertPduSizeForRead(dataItems); + var stream = GetStreamIfAvailable(); + try { // first create the header diff --git a/S7.Net/Protocol/S7WriteMultiple.cs b/S7.Net/Protocol/S7WriteMultiple.cs index 2b92ef5..2738a34 100644 --- a/S7.Net/Protocol/S7WriteMultiple.cs +++ b/S7.Net/Protocol/S7WriteMultiple.cs @@ -91,7 +91,7 @@ namespace S7.Net.Protocol IList itemResults = new ArraySegment(message, 14, dataItems.Length); - List errors = null; + List? errors = null; for (int i = 0; i < dataItems.Length; i++) { diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj index 007c374..94f659b 100644 --- a/S7.Net/S7.Net.csproj +++ b/S7.Net/S7.Net.csproj @@ -15,6 +15,8 @@ git PLC Siemens Communication S7 Derek Heiser 2015 + 8.0 + Enable portable true true diff --git a/S7.Net/TPKT.cs b/S7.Net/TPKT.cs index a3af212..2b988d3 100644 --- a/S7.Net/TPKT.cs +++ b/S7.Net/TPKT.cs @@ -10,10 +10,19 @@ namespace S7.Net /// internal class TPKT { + + public byte Version; public byte Reserved1; public int Length; public byte[] Data; + private TPKT(byte version, byte reserved1, int length, byte[] data) + { + Version = version; + Reserved1 = reserved1; + Length = length; + Data = data; + } /// /// Reads a TPKT from the socket @@ -24,21 +33,28 @@ namespace S7.Net { var buf = new byte[4]; int len = stream.Read(buf, 0, 4); - if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); - var pkt = new TPKT + if (len < 4) throw new TPKTInvalidException("TPKT header incomplete / invalid"); + var version = buf[0]; + var reserved1 = buf[1]; + var length = buf[2] * 256 + buf[3]; //BigEndian + + if (length == 0) { - Version = buf[0], - Reserved1 = buf[1], - Length = buf[2] * 256 + buf[3] //BigEndian - }; - if (pkt.Length > 0) - { - pkt.Data = new byte[pkt.Length - 4]; - len = stream.Read(pkt.Data, 0, pkt.Length - 4); - if (len < pkt.Length - 4) - throw new TPKTInvalidException("TPKT is incomplete / invalid"); + throw new TPKTInvalidException("TPKT payload length is zero"); } - return pkt; + + var data = new byte[length - 4]; + len = stream.Read(data, 0, length - 4); + if (len < length - 4) + throw new TPKTInvalidException("TPKT payload incomplete / invalid"); + + return new TPKT + ( + version: version, + reserved1: reserved1, + length: length, + data: data + ); } /// @@ -50,20 +66,28 @@ namespace S7.Net { var buf = new byte[4]; int len = await stream.ReadAsync(buf, 0, 4); - if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); - var pkt = new TPKT + if (len < 4) throw new TPKTInvalidException("TPKT header incomplete / invalid"); + var version = buf[0]; + var reserved1 = buf[1]; + var length = buf[2] * 256 + buf[3]; //BigEndian + + if (length == 0) { - Version = buf[0], - Reserved1 = buf[1], - Length = buf[2] * 256 + buf[3] //BigEndian - }; - if (pkt.Length > 0) - { - pkt.Data = new byte[pkt.Length - 4]; - len = await stream.ReadAsync(pkt.Data, 0, pkt.Length - 4); - if (len < pkt.Length - 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); + throw new TPKTInvalidException("TPKT payload length is zero"); } - return pkt; + + var data = new byte[length - 4]; + len = await stream.ReadAsync(data, 0, length - 4); + if (len < length - 4) + throw new TPKTInvalidException("TPKT payload incomplete / invalid"); + + return new TPKT + ( + version: version, + reserved1: reserved1, + length: length, + data: data + ); } public override string ToString() diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 1052808..ab76f8d 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -106,9 +106,9 @@ namespace S7.Net.Types return numBytes; } - private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) + private static object? GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) { - object value = null; + object? value = null; switch (propertyType.Name) { @@ -245,7 +245,7 @@ namespace S7.Net.Types { int bytePos = 0; int bitPos = 0; - byte[] bytes2 = null; + byte[]? bytes2 = null; switch (propertyValue.GetType().Name) { diff --git a/S7.Net/Types/DataItem.cs b/S7.Net/Types/DataItem.cs index ca338c8..c944ffa 100644 --- a/S7.Net/Types/DataItem.cs +++ b/S7.Net/Types/DataItem.cs @@ -40,7 +40,7 @@ namespace S7.Net.Types /// /// Contains the value of the memory area after the read has been executed /// - public object Value { get; set; } + public object? Value { get; set; } /// /// Create an instance of DataItem @@ -83,7 +83,14 @@ namespace S7.Net.Types var dataItem = FromAddress(address); dataItem.Value = value; - if (typeof(T).IsArray) dataItem.Count = ((Array) dataItem.Value).Length; + if (typeof(T).IsArray) + { + var array = ((Array?)dataItem.Value); + if ( array != null) + { + dataItem.Count = array.Length; + } + } return dataItem; } diff --git a/S7.Net/Types/Struct.cs b/S7.Net/Types/Struct.cs index 3d569e5..733800a 100644 --- a/S7.Net/Types/Struct.cs +++ b/S7.Net/Types/Struct.cs @@ -70,7 +70,7 @@ namespace S7.Net.Types /// The struct type /// The array of bytes /// The object depending on the struct type or null if fails(array-length != struct-length - public static object FromBytes(Type structType, byte[] bytes) + public static object? FromBytes(Type structType, byte[] bytes) { if (bytes == null) return null; @@ -198,7 +198,7 @@ namespace S7.Net.Types int size = Struct.GetStructSize(type); byte[] bytes = new byte[size]; - byte[] bytes2 = null; + byte[]? bytes2 = null; int bytePos = 0; int bitPos = 0;