Skip to content

Commit

Permalink
Read TPKT/COTP packets / Read MaxPDU size from PLC
Browse files Browse the repository at this point in the history
Read responses from the PLS using classes for TPKT and COPT. This
makes the communication more robust. It will now handle empty COTP
packets that SoftPLS and WinAC based PLCs send out. I use RFC names for
functions and classes.

Change logic to use COTP and S7Comm reponse codes instead of
relying on packet sizes.

Read Max PDU size from connection setup. Ref S7NetPlus#21
I did some test on using this limit. But i met a wall when testing
against snap7 so i decided to drop changes to read/write size. I have
done some tests against WinAC cpu and it seems to handle bigger pdu's if
negotiated in the connection setup. This might just be a SNAP7 bug.
  • Loading branch information
thoj committed Apr 16, 2018
1 parent aca6874 commit 92eac4c
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 21 deletions.
99 changes: 99 additions & 0 deletions S7.Net/COTP.cs
@@ -0,0 +1,99 @@
using System;
using System.IO;
using System.Net.Sockets;

namespace S7.Net
{

/// <summary>
/// COTP Protocol functions and types
/// </summary>
internal class COTP
{
/// <summary>
/// Describes a COTP TPDU (Transport protocol data unit)
/// </summary>
public class TPDU
{
public byte HeaderLength;
public byte PDUType;
public int TPDUNumber;
public byte[] Data;
public bool LastDataUnit;

public TPDU(TPKT tPKT)
{
var br = new BinaryReader(new MemoryStream(tPKT.Data));
HeaderLength = br.ReadByte();
if (HeaderLength >= 2)
{
PDUType = br.ReadByte();
if (PDUType == 0xf0) //DT Data
{
var flags = br.ReadByte();
TPDUNumber = flags & 0x7F;
LastDataUnit = (flags & 0x80) > 0;
Data = br.ReadBytes(tPKT.Length - HeaderLength - 4); //4 = TPKT Size
return;
}
//TODO: Handle other PDUTypes
}
Data = new byte[0];
}

/// <summary>
/// Reads COTP TPDU (Transport protocol data unit) from the network stream
/// See: https://tools.ietf.org/html/rfc905
/// </summary>
/// <param name="socket">The socket to read from</param>
/// <returns>COTP DPDU instance</returns>
public static TPDU Read(Socket socket)
{
var tpkt = TPKT.Read(socket);
Console.WriteLine(tpkt);
if (tpkt.Length > 0) return new TPDU(tpkt);
return null;
}

public override string ToString()
{
return string.Format("Length: {0} PDUType: {1} TPDUNumber: {2} Last: {3} Segment Data: {4}",
HeaderLength,
PDUType,
TPDUNumber,
LastDataUnit,
BitConverter.ToString(Data)
);
}

}

/// <summary>
/// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs
/// </summary>
public class TSDU
{
/// <summary>
/// Reads the full COTP TSDU (Transport service data unit)
/// See: https://tools.ietf.org/html/rfc905
/// </summary>
/// <returns>Data in TSDU</returns>
public static byte[] Read(Socket socket)
{
var segment = TPDU.Read(socket);

if (segment == null) return null;

var output = new MemoryStream(segment.Data.Length);
output.Write(segment.Data, 0, segment.Data.Length);

while (!segment.LastDataUnit)
{
segment = TPDU.Read(socket);
output.Write(segment.Data, (int)output.Position, segment.Data.Length);
}
return output.GetBuffer();
}
}
}
}
60 changes: 39 additions & 21 deletions S7.Net/PLC.cs
Expand Up @@ -38,6 +38,11 @@ public class Plc : IDisposable
/// Slot of the CPU of the PLC
/// </summary>
public Int16 Slot { get; private set; }

/// <summary>
/// Max PDU size this cpu supports
/// </summary>
public Int16 MaxPDUSize { get; set; }

/// <summary>
/// Returns true if a connection to the PLC can be established
Expand Down Expand Up @@ -116,6 +121,7 @@ public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
IP = ip;
Rack = rack;
Slot = slot;
MaxPDUSize = 240;
}

private ErrorCode Connect(Socket socket)
Expand Down Expand Up @@ -232,10 +238,15 @@ public ErrorCode Open()
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
}

byte[] bsend2 = { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3, 1, 0 };
//S7ComSetup
byte[] bsend2 = { 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3,
7, 80 //Try 1920 PDU Size. Same as libnodave.
};

_mSocket.Send(bsend2, 25, SocketFlags.None);

if (_mSocket.Receive(bReceive, 27, SocketFlags.None) != 27)
var s7data = COTP.TSDU.Read(_mSocket);
if (s7data == null || s7data[1] != 0x03) //Check for S7 Ack Data
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
}
Expand Down Expand Up @@ -273,7 +284,11 @@ public void Close()
public void ReadMultipleVars(List<DataItem> dataItems)
{
int cntBytes = dataItems.Sum(dataItem => VarTypeToByteLength(dataItem.VarType, dataItem.Count));



//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
if (dataItems.Count > 20)
throw new Exception("Too many vars requested");
if (cntBytes > 222)
Expand All @@ -293,20 +308,19 @@ public void ReadMultipleVars(List<DataItem> dataItems)

_mSocket.Send(package.array, package.array.Length, SocketFlags.None);

byte[] bReceive = new byte[512];
int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
if (bReceive[21] != 0xff)
var s7data = COTP.TSDU.Read(_mSocket);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());

int offset = 25;
int offset = 18;
foreach (var dataItem in dataItems)
{
int byteCnt = VarTypeToByteLength(dataItem.VarType, dataItem.Count);
byte[] bytes = new byte[byteCnt];

for (int i = 0; i < byteCnt; i++)
{
bytes[i] = bReceive[i + offset];
bytes[i] = s7data[i + offset];
}

offset += byteCnt + 4;
Expand Down Expand Up @@ -341,7 +355,11 @@ public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count)
int index = startByteAdr;
while (count > 0)
{
var maxToRead = (int)Math.Min(count, 200);

//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToRead = (int)Math.Min(count, 200);
byte[] bytes = ReadBytesWithASingleRequest(dataType, db, index, maxToRead);
if (bytes == null)
return resultBytes.ToArray();
Expand Down Expand Up @@ -618,7 +636,10 @@ public ErrorCode WriteBytes(DataType dataType, int db, int startByteAdr, byte[]
int localIndex = 0;
int count = value.Length;
while (count > 0)
{
{
//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToWrite = (int)Math.Min(count, 200);
ErrorCode lastError = WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Skip(localIndex).Take(maxToWrite).ToArray());
if (lastError != ErrorCode.NoError)
Expand Down Expand Up @@ -1048,13 +1069,12 @@ private byte[] ReadBytesWithASingleRequest(DataType dataType, int db, int startB

_mSocket.Send(package.array, package.array.Length, SocketFlags.None);

byte[] bReceive = new byte[512];
int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
if (bReceive[21] != 0xff)
var s7data = COTP.TSDU.Read(_mSocket);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());

for (int cnt = 0; cnt < count; cnt++)
bytes[cnt] = bReceive[cnt + 25];
bytes[cnt] = s7data[cnt + 18];

return bytes;
}
Expand Down Expand Up @@ -1114,8 +1134,8 @@ private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int st

_mSocket.Send(package.array, package.array.Length, SocketFlags.None);

int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
if (bReceive[21] != 0xff)
var s7data = COTP.TSDU.Read(_mSocket);
if (s7data == null || s7data[14] != 0xff)
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
}
Expand Down Expand Up @@ -1164,11 +1184,9 @@ private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int star

_mSocket.Send(package.array, package.array.Length, SocketFlags.None);

int numReceived = _mSocket.Receive(bReceive, 512, SocketFlags.None);
if (bReceive[21] != 0xff)
{
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());
}
var s7data = COTP.TSDU.Read(_mSocket);
if (s7data == null || s7data[14] != 0xff)
throw new Exception(ErrorCode.WrongNumberReceivedBytes.ToString());

return ErrorCode.NoError;
}
Expand Down
2 changes: 2 additions & 0 deletions S7.Net/S7.Net.csproj
Expand Up @@ -77,9 +77,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Conversion.cs" />
<Compile Include="COTP.cs" />
<Compile Include="Enums.cs" />
<Compile Include="PLC.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TPKT.cs" />
<Compile Include="Types\Bit.cs" />
<Compile Include="Types\Boolean.cs" />
<Compile Include="Types\Byte.cs" />
Expand Down
49 changes: 49 additions & 0 deletions S7.Net/TPKT.cs
@@ -0,0 +1,49 @@
using System;
using System.Net.Sockets;

namespace S7.Net
{

/// <summary>
/// Describes a TPKT Packet
/// </summary>
internal class TPKT
{
public byte Version;
public byte Reserved1;
public int Length;
public byte[] Data;

/// <summary>
/// Reds a TPKT from the socket
/// </summary>
/// <param name="socket">The socket to read from</param>
/// <returns>TPKT Instace</returns>
public static TPKT Read(Socket socket)
{
var buf = new byte[4];
socket.Receive(buf, 4, SocketFlags.None);
var pkt = new TPKT
{
Version = buf[0],
Reserved1 = buf[1],
Length = buf[2] * 256 + buf[3] //BigEndian
};
if (pkt.Length > 0)
{
pkt.Data = new byte[pkt.Length - 4];
socket.Receive(pkt.Data, pkt.Length - 4, SocketFlags.None);
}
return pkt;
}

public override string ToString()
{
return string.Format("Version: {0} Length: {1} Data: {2}",
Version,
Length,
BitConverter.ToString(Data)
);
}
}
}

0 comments on commit 92eac4c

Please sign in to comment.