详解S7源码3-COTP and TPKT
连接 S7COMM简介 https://www.anquanke.com/post/id/186099
OSI layer Protocol
Application Layer S7 communication
Presentation Layer S7 communication(COTP)
Session Layer S7 communication(TPKT)
Transport Layer ISO-on-TCP (RFC 1006)
Network Layer IP
Data Link Layer Ethernet
Physical Layer Ethernet
1,TPKT(默认端口号102)
tpkt主要为
version | 1byte | 0x03 | 3 |
resrved | 1byte | 0x00 | 0 |
length | 2byte | 0x16 | 22 |
length为包括这4字节+Payload在内的字节数.(Payload)为data包装后数据.
1,TPKT类,返回TPKT信息和数据包.根据length-4求得data.就是Payload的数据.(解封).
internal class TPKT { public byte Version; public byte Reserved1; public int Length; public byte[] Data; /// <summary> /// Reads a TPKT from the socket /// </summary> /// <param name="stream">The stream to read from</param> /// <returns>TPKT Instance</returns> public static TPKT Read(Stream stream) { var buf = new byte[4]; int len = stream.Read(buf, 0, 4); if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); 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]; len = stream.Read(pkt.Data, 0, pkt.Length - 4); if (len < pkt.Length - 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); } return pkt; } /// <summary> /// Reads a TPKT from the socket Async /// </summary> /// <param name="stream">The stream to read from</param> /// <returns>Task TPKT Instace</returns> public static async Task<TPKT> ReadAsync(Stream stream) { var buf = new byte[4]; int len = await stream.ReadAsync(buf, 0, 4); if (len < 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); 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]; len = await stream.ReadAsync(pkt.Data, 0, pkt.Length - 4); if (len < pkt.Length - 4) throw new TPKTInvalidException("TPKT is incomplete / invalid"); } return pkt; } public override string ToString() { return string.Format("Version: {0} Length: {1} Data: {2}", Version, Length, BitConverter.ToString(Data) ); } }
2,TPDU-----Transport protocol data unit---一个基本数据传输单元(COTP)
第一个byte--标识为length---不包括本身.标识
第二给byte---标识为PDUType--分为以下几种
0x0d | 连接确认 | 0x05 | 拒绝 |
0x0e | 连接请求 | 0xf0 | 数据包 |
0x08 | 断开请求 | ||
0x0c | 断开确认 |
当其为0xf0的时候:
第三个byte---flags:其位7标志是否为最后的TPDU,位0-6标识块NUMBER---TPDU NUMBER.
从stream---->TPKT---->TPDU---->再从TPDU中读取数据TSDU.(减去4个字节的TPKT包裹+headerLength).
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="stream">The socket to read from</param> /// <returns>COTP DPDU instance</returns> public static TPDU Read(Stream stream) { var tpkt = TPKT.Read(stream); if (tpkt.Length > 0) return new TPDU(tpkt); return null; } /// <summary> /// Reads COTP TPDU (Transport protocol data unit) from the network stream /// See: https://tools.ietf.org/html/rfc905 /// </summary> /// <param name="stream">The socket to read from</param> /// <returns>COTP DPDU instance</returns> public static async Task<TPDU> ReadAsync(Stream stream) { var tpkt = await TPKT.ReadAsync(stream); 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) ); } }
3,TSDU---服务数据单元---定义了将连续的TPDU转换为一个TSDU单元.
public class TSDU { /// <summary> /// Reads the full COTP TSDU (Transport service data unit) /// See: https://tools.ietf.org/html/rfc905 /// </summary> /// <param name="stream">The stream to read from</param> /// <returns>Data in TSDU</returns> public static byte[] Read(Stream stream) { var segment = TPDU.Read(stream); 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(stream); output.Write(segment.Data, (int)output.Position, segment.Data.Length); } return output.GetBuffer().Take((int)output.Position).ToArray(); } /// <summary> /// Reads the full COTP TSDU (Transport service data unit) /// See: https://tools.ietf.org/html/rfc905 /// </summary> /// <param name="stream">The stream to read from</param> /// <returns>Data in TSDU</returns> public static async Task<byte[]> ReadAsync(Stream stream) { var segment = await TPDU.ReadAsync(stream); if (segment == null) return null; var output = new MemoryStream(segment.Data.Length); output.Write(segment.Data, 0, segment.Data.Length); while (!segment.LastDataUnit) { segment = await TPDU.ReadAsync(stream); output.Write(segment.Data, (int)output.Position, segment.Data.Length); } return output.GetBuffer().Take((int)output.Position).ToArray(); } }