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) + ); + } + } +}