diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs index 2451893..d681026 100644 --- a/S7.Net/COTP.cs +++ b/S7.Net/COTP.cs @@ -11,6 +11,11 @@ namespace S7.Net /// internal class COTP { + public enum PduType : byte + { + Data = 0xf0, + ConnectionConfirmed = 0xd0 + } /// /// Describes a COTP TPDU (Transport protocol data unit) /// @@ -18,7 +23,7 @@ namespace S7.Net { public TPKT TPkt { get; } public byte HeaderLength; - public byte PDUType; + public PduType PDUType; public int TPDUNumber; public byte[] Data; public bool LastDataUnit; @@ -30,8 +35,8 @@ namespace S7.Net HeaderLength = tPKT.Data[0]; // Header length excluding this length byte if (HeaderLength >= 2) { - PDUType = tPKT.Data[1]; - if (PDUType == 0xf0) //DT Data + PDUType = (PduType)tPKT.Data[1]; + if (PDUType == PduType.Data) //DT Data { var flags = tPKT.Data[2]; TPDUNumber = flags & 0x7F; diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index e4a7f7f..4feb76f 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -85,7 +85,7 @@ namespace S7.Net { try { - Connect(); + ConnectAsync().Wait(); return true; } catch diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 1de0ac8..f934160 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -24,26 +24,50 @@ namespace S7.Net /// A task that represents the asynchronous open operation. public async Task OpenAsync(CancellationToken cancellationToken = default) { - await ConnectAsync(); - cancellationToken.ThrowIfCancellationRequested(); - var stream = GetStreamIfAvailable(); - - await stream.WriteAsync(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22); - var response = await COTP.TPDU.ReadAsync(stream, cancellationToken); - if (response == null) + var stream = await ConnectAsync(); + try { - throw new Exception("Error reading Connection Confirm. Malformed TPDU packet"); + cancellationToken.ThrowIfCancellationRequested(); + await EstablishConnection(stream, cancellationToken); + _stream = stream; } - if (response.PDUType != 0xd0) //Connect Confirm + catch(Exception) { - throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d); + stream.Close(); } + } - await stream.WriteAsync(GetS7ConnectionSetup(), 0, 25); + private async Task ConnectAsync() + { + tcpClient = new TcpClient(); + ConfigureConnection(); + await tcpClient.ConnectAsync(IP, Port); + return tcpClient.GetStream(); + } + + private async Task EstablishConnection(NetworkStream stream, CancellationToken cancellationToken) + { + await RequestConnection(stream, cancellationToken); + await SetupConnection(stream, cancellationToken); + } + + private async Task RequestConnection(NetworkStream stream, CancellationToken cancellationToken) + { + var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot); + await stream.WriteAsync(requestData, 0, requestData.Length); + var response = await COTP.TPDU.ReadAsync(stream, cancellationToken); + if (response.PDUType != COTP.PduType.ConnectionConfirmed) + { + throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d); + } + } + + private async Task SetupConnection(NetworkStream stream, CancellationToken cancellationToken) + { + var setupData = GetS7ConnectionSetup(); + await stream.WriteAsync(setupData, 0, setupData.Length); var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); - if (s7data == null) - throw new WrongNumberOfBytesException("No data received in response to Communication Setup"); if (s7data.Length < 2) throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); @@ -54,15 +78,12 @@ namespace S7.Net if (s7data.Length < 20) throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); - MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]); - } - - private async Task ConnectAsync() - { - tcpClient = new TcpClient(); - ConfigureConnection(); - await tcpClient.ConnectAsync(IP, Port); - _stream = tcpClient.GetStream(); + // TODO: check if this should not rather be UInt16. + MaxPDUSize = Types.Int.FromByteArray(s7data, 18); + if (MaxPDUSize <= 0 ) + { + throw new PlcException(ErrorCode.ConnectionError, "Communication Setup resulted in non-positive PDU size."); + } } diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 8cf5f89..a243571 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -17,34 +17,9 @@ namespace S7.Net /// public void Open() { - Connect(); - 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 - { - throw new InvalidDataException("Error reading Connection Confirm", response.TPkt.Data, 1, 0x0d); - } - - stream.Write(GetS7ConnectionSetup(), 0, 25); - - var s7data = COTP.TSDU.Read(stream); - if (s7data == null) - throw new WrongNumberOfBytesException("No data received in response to Communication Setup"); - if (s7data.Length < 2) - throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); - - //Check for S7 Ack Data - if (s7data[1] != 0x03) - throw new InvalidDataException("Error reading Communication Setup response", s7data, 1, 0x03); - - if (s7data.Length < 20) - throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); - - MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]); + OpenAsync().Wait(); } catch (Exception exc) { @@ -53,28 +28,6 @@ namespace S7.Net } } - private void Connect() - { - try - { - tcpClient = new TcpClient(); - ConfigureConnection(); - tcpClient.Connect(IP, Port); - _stream = tcpClient.GetStream(); - } - catch (SocketException sex) - { - // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx - throw new PlcException( - sex.SocketErrorCode == SocketError.TimedOut - ? ErrorCode.IPAddressNotAvailable - : ErrorCode.ConnectionError, sex); - } - catch (Exception ex) - { - throw new PlcException(ErrorCode.ConnectionError, ex); - } - } /// /// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests. diff --git a/S7.Net/Types/Int.cs b/S7.Net/Types/Int.cs index 5489691..d16d0fd 100644 --- a/S7.Net/Types/Int.cs +++ b/S7.Net/Types/Int.cs @@ -10,15 +10,15 @@ namespace S7.Net.Types /// /// Converts a S7 Int (2 bytes) to short (Int16) /// - public static short FromByteArray(byte[] bytes) + public static short FromByteArray(byte[] bytes, int offset = 0) { - if (bytes.Length != 2) + if (bytes.Length < offset + 2) { - throw new ArgumentException("Wrong number of bytes. Bytes array must contain 2 bytes."); + throw new ArgumentException($"Wrong number of bytes. Bytes array must contain at least {offset+2} bytes."); } // bytes[0] -> HighByte // bytes[1] -> LowByte - return (short)((int)(bytes[1]) | ((int)(bytes[0]) << 8)); + return (short)(bytes[offset + 1] | (bytes[offset + 0] << 8)); }