Cleanup OpenAsync function.

- Separate into Connect and EstablishConnection step.
- Remove redundant null checks for returned data.
-  Only assing PLC stream object once we fully established a connection, and Close otherwise.
- Replace sync implementation with Async call.
This commit is contained in:
Serge Camille
2020-09-12 11:14:41 +02:00
parent af39659944
commit 106e9912ab
5 changed files with 57 additions and 78 deletions

View File

@@ -11,6 +11,11 @@ namespace S7.Net
/// </summary>
internal class COTP
{
public enum PduType : byte
{
Data = 0xf0,
ConnectionConfirmed = 0xd0
}
/// <summary>
/// Describes a COTP TPDU (Transport protocol data unit)
/// </summary>
@@ -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;

View File

@@ -85,7 +85,7 @@ namespace S7.Net
{
try
{
Connect();
ConnectAsync().Wait();
return true;
}
catch

View File

@@ -24,26 +24,50 @@ namespace S7.Net
/// <returns>A task that represents the asynchronous open operation.</returns>
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<NetworkStream> 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.");
}
}

View File

@@ -17,34 +17,9 @@ namespace S7.Net
/// </summary>
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);
}
}
/// <summary>
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.

View File

@@ -10,15 +10,15 @@ namespace S7.Net.Types
/// <summary>
/// Converts a S7 Int (2 bytes) to short (Int16)
/// </summary>
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));
}