diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs
new file mode 100644
index 0000000..434bd06
--- /dev/null
+++ b/S7.Net/COTP.cs
@@ -0,0 +1,97 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+
+namespace S7.Net
+{
+
+ ///
+ /// COTP Protocol functions and types
+ ///
+ internal class COTP
+ {
+ ///
+ /// Describes a COTP TPDU (Transport protocol data unit)
+ ///
+ 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];
+ }
+
+ ///
+ /// Reads COTP TPDU (Transport protocol data unit) from the network stream
+ /// See: https://tools.ietf.org/html/rfc905
+ ///
+ /// The socket to read from
+ /// COTP DPDU instance
+ public static TPDU Read(Socket socket)
+ {
+ var tpkt = TPKT.Read(socket);
+ 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)
+ );
+ }
+
+ }
+
+ ///
+ /// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs
+ ///
+ public class TSDU
+ {
+ ///
+ /// Reads the full COTP TSDU (Transport service data unit)
+ /// See: https://tools.ietf.org/html/rfc905
+ ///
+ /// Data in TSDU
+ 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();
+ }
+ }
+ }
+}
diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs
index 662d9b3..d7a10aa 100644
--- a/S7.Net/PLC.cs
+++ b/S7.Net/PLC.cs
@@ -38,6 +38,11 @@ namespace S7.Net
/// Slot of the CPU of the PLC
///
public Int16 Slot { get; private set; }
+
+ ///
+ /// Max PDU size this cpu supports
+ ///
+ public Int16 MaxPDUSize { get; set; }
///
/// Returns true if a connection to the PLC can be established
@@ -116,6 +121,7 @@ namespace S7.Net
IP = ip;
Rack = rack;
Slot = slot;
+ MaxPDUSize = 240;
}
private ErrorCode Connect(Socket socket)
@@ -157,7 +163,6 @@ namespace S7.Net
/// Returns ErrorCode.NoError if the connection was successful, otherwise check the ErrorCode
public ErrorCode Open()
{
- byte[] bReceive = new byte[256];
_mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_mSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);
@@ -168,9 +173,25 @@ namespace S7.Net
return LastErrorCode;
}
- try
+ try
{
- byte[] bSend1 = { 3, 0, 0, 22, 17, 224, 0, 0, 0, 46, 0, 193, 2, 1, 0, 194, 2, 3, 0, 192, 1, 9 };
+ byte[] bSend1 = {
+ 3, 0, 0, 22, //TPKT
+ 17, //COTP Header Length
+ 224, //Connect Request
+ 0, 0, //Destination Reference
+ 0, 46, //Source Reference
+ 0, //Flags
+ 193, //Parameter Code (src-tasp)
+ 2, //Parameter Length
+ 1, 0, //Source TASP
+ 194, //Parameter Code (dst-tasp)
+ 2, //Parameter Length
+ 3, 0, //Destination TASP
+ 192, //Parameter Code (tpdu-size)
+ 1, //Parameter Length
+ 9 //TPDU Size (2^9 = 512)
+ };
switch (CPU) {
case CpuType.S7200:
@@ -226,28 +247,29 @@ namespace S7.Net
return ErrorCode.WrongCPU_Type;
}
+ //COTP Setup
_mSocket.Send(bSend1, 22, SocketFlags.None);
- if (_mSocket.Receive(bReceive, 22, SocketFlags.None) != 22)
- {
- 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,
+ 7, 80 //Try 1920 PDU Size. Same as libnodave.
+ };
- 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 };
_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());
- }
+ }
+ MaxPDUSize = (short)(s7data[18] * 256 + s7data[19]);
+
+ return ErrorCode.NoError;
}
- catch(Exception exc)
+ catch (Exception exc)
{
LastErrorCode = ErrorCode.ConnectionError;
LastErrorString = string.Format("Couldn't establish the connection to {0}.\nMessage: {1}", IP, exc.Message);
return ErrorCode.ConnectionError;
}
-
- return ErrorCode.NoError;
}
///
@@ -273,12 +295,14 @@ namespace S7.Net
public void ReadMultipleVars(List 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)
throw new Exception("Too many bytes requested"); // TODO: proper TDU check + split in multiple requests
-
try
{
// first create the header
@@ -293,25 +317,20 @@ namespace S7.Net
_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];
- }
-
+ dataItem.Value = ParseBytes(
+ dataItem.VarType,
+ s7data.Skip(offset).Take(byteCnt).ToArray(),
+ dataItem.Count
+ );
offset += byteCnt + 4;
-
- dataItem.Value = ParseBytes(dataItem.VarType, bytes, dataItem.Count);
}
}
catch (SocketException socketException)
@@ -341,7 +360,8 @@ namespace S7.Net
int index = startByteAdr;
while (count > 0)
{
- var maxToRead = (int)Math.Min(count, 200);
+ //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
+ var maxToRead = (int)Math.Min(count, MaxPDUSize-18);
byte[] bytes = ReadBytesWithASingleRequest(dataType, db, index, maxToRead);
if (bytes == null)
return resultBytes.ToArray();
@@ -618,7 +638,10 @@ namespace S7.Net
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)
@@ -1032,8 +1055,6 @@ namespace S7.Net
private byte[] ReadBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, int count)
{
- byte[] bytes = new byte[count];
-
try
{
// first create the header
@@ -1045,15 +1066,11 @@ namespace S7.Net
_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];
-
- return bytes;
+ return s7data.Skip(18).Take(count).ToArray();
}
catch (SocketException socketException)
{
@@ -1080,9 +1097,7 @@ namespace S7.Net
/// NoError if it was successful, or the error is specified
private ErrorCode WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value)
{
- byte[] bReceive = new byte[513];
int varCount = 0;
-
try
{
varCount = value.Length;
@@ -1111,8 +1126,8 @@ namespace S7.Net
_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());
}
@@ -1129,7 +1144,6 @@ namespace S7.Net
private ErrorCode WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
- byte[] bReceive = new byte[513];
int varCount = 0;
try
@@ -1161,11 +1175,9 @@ namespace S7.Net
_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;
}
diff --git a/S7.Net/S7.Net.csproj b/S7.Net/S7.Net.csproj
index ac2ac19..bb2abab 100644
--- a/S7.Net/S7.Net.csproj
+++ b/S7.Net/S7.Net.csproj
@@ -77,9 +77,11 @@
+
+
diff --git a/S7.Net/TPKT.cs b/S7.Net/TPKT.cs
new file mode 100644
index 0000000..3ca9fd5
--- /dev/null
+++ b/S7.Net/TPKT.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Net.Sockets;
+
+namespace S7.Net
+{
+
+ ///
+ /// Describes a TPKT Packet
+ ///
+ internal class TPKT
+ {
+ public byte Version;
+ public byte Reserved1;
+ public int Length;
+ public byte[] Data;
+
+ ///
+ /// Reds a TPKT from the socket
+ ///
+ /// The socket to read from
+ /// TPKT Instace
+ 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)
+ );
+ }
+ }
+}