Skip to content

Commit

Permalink
成功解析请求和确认指令
Browse files Browse the repository at this point in the history
  • Loading branch information
nnhy committed Dec 28, 2023
1 parent 504f1aa commit ed1fe43
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 109 deletions.
256 changes: 171 additions & 85 deletions NewLife.Siemens/Protocols/COTP.cs
Original file line number Diff line number Diff line change
@@ -1,124 +1,210 @@
using NewLife.Siemens.Common;
using NewLife.Data;
using NewLife.Serialization;
using NewLife.Siemens.Common;

namespace NewLife.Siemens.Protocols;

/// <summary>面向连接的传输协议(Connection-Oriented Transport Protocol)</summary>
public class COTP
public class COTP : IAccessor
{
/// <summary>PDU类型</summary>
public enum PduType : Byte
{
/// <summary>数据帧</summary>
Data = 0xf0,
#region 属性
/// <summary>包类型</summary>
public PduType Type { get; set; }

/// <summary>CR连接请求帧</summary>
ConnectionRequest = 0xe0,
/// <summary>目标的引用,可以认为是用来唯一标识目标</summary>
public UInt16 Destination { get; set; }

/// <summary>CC连接确认帧</summary>
ConnectionConfirmed = 0xd0
}
/// <summary>源的引用</summary>
public UInt16 Source { get; set; }

/// <summary>
/// Describes a COTP TPDU (Transport protocol data unit)
/// </summary>
public class TPDU
{
///// <summary>TPKT头</summary>
//public TPKT TPkt { get; }
/// <summary>选项</summary>
public Byte Option { get; set; }

///// <summary>头部长度</summary>
//public Byte HeaderLength { get; set; }
/// <summary>参数集合</summary>
public IList<TLV> Parameters { get; set; }

/// <summary>包类型</summary>
public PduType PDUType { get; set; }
/// <summary>编码</summary>
public Int32 Number { get; set; }

/// <summary>编码</summary>
public Int32 Number { get; set; }
/// <summary>是否最后数据单元</summary>
public Boolean LastDataUnit { get; set; }

/// <summary>数据</summary>
public Byte[] Data { get; set; }
/// <summary>数据</summary>
public Packet Data { get; set; }
#endregion

/// <summary>是否最后数据单元</summary>
public Boolean LastDataUnit { get; set; }
#region 读写
/// <summary>解析数据</summary>
/// <param name="data"></param>
/// <returns></returns>
public Boolean Read(Packet data) => (this as IAccessor).Read(null, data);

/// <summary>实例化</summary>
/// <param name="tPKT"></param>
public TPDU(TPKT tPKT)
{
//TPkt = tPKT;
/// <summary>序列化</summary>
/// <returns></returns>
public Byte[] ToArray()
{
var ms = new MemoryStream();
(this as IAccessor).Write(ms, null);

var len = tPKT.Data[0]; // Header length excluding this length byte
if (len >= 2)
{
PDUType = (PduType)tPKT.Data[1];
return ms.ToArray();
}

Boolean IAccessor.Read(Stream stream, Object context)
{
var pk = context as Packet;
stream ??= pk?.GetStream();
var reader = new Binary { Stream = stream, IsLittleEndian = false };

// 解析不同数据帧
if (PDUType == PduType.Data) //DT Data
// 头部长度。当前字节之后就是头部,然后是数据
var len = reader.ReadByte();
Type = (PduType)reader.ReadByte();

// 解析不同数据帧
switch (Type)
{
case PduType.Data:
{
var flags = tPKT.Data[2];
var flags = reader.ReadByte();
Number = flags & 0x7F;
LastDataUnit = (flags & 0x80) > 0;
Data = new Byte[tPKT.Data.Length - len - 1]; // substract header length byte + header length.
Array.Copy(tPKT.Data, len + 1, Data, 0, Data.Length);

return;
if (pk != null)
Data = pk.Slice(4, pk.Total - 1 - len);
else
Data = stream.ReadBytes(-1);
}
//TODO: Handle other PDUTypes
}
Data = new Byte[0];
break;
case PduType.ConnectionRequest:
case PduType.ConnectionConfirmed:
default:
{
Destination = reader.ReadUInt16();
Source = reader.ReadUInt16();
Option = reader.ReadByte();

if (stream.Position < stream.Length)
{
var list = new List<TLV>();
while (stream.Position + 3 <= stream.Length)
{
var tlv = new TLV
{
Type = reader.ReadByte(),
Length = reader.ReadByte()
};
var buf = reader.ReadBytes(tlv.Length);
tlv.Value = tlv.Length switch
{
1 => buf[0],
2 => buf.ToUInt16(0, false),
4 => buf.ToUInt32(0, false),
_ => buf,
};
list.Add(tlv);
}
Parameters = list;
}
}
break;
}

/// <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>
/// <param name="cancellationToken"></param>
/// <returns>COTP DPDU instance</returns>
public static async Task<TPDU> ReadAsync(Stream stream, CancellationToken cancellationToken)
return true;
}

Boolean IAccessor.Write(Stream stream, Object context)
{
switch (Type)
{
var tpkt = await TPKT.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (tpkt.Length == 0) throw new TPDUInvalidException("No protocol data received");
case PduType.Data:
{
var len = 2;
stream.WriteByte((Byte)len);
stream.WriteByte((Byte)Type);

var flags = (Byte)(Number & 0x7F);
if (LastDataUnit) flags |= 0x80;
stream.WriteByte(flags);

return new TPDU(tpkt);
Data?.CopyTo(stream);
}
break;
case PduType.ConnectionRequest:
{
var len = 2;
stream.WriteByte((Byte)len);
stream.WriteByte((Byte)Type);

var flags = (Byte)(Number & 0x7F);
if (LastDataUnit) flags |= 0x80;
stream.WriteByte(flags);

Data?.CopyTo(stream);
}
break;
case PduType.ConnectionConfirmed:
{
var len = 2;
stream.WriteByte((Byte)len);
stream.WriteByte((Byte)Type);

var flags = (Byte)(Number & 0x7F);
if (LastDataUnit) flags |= 0x80;
stream.WriteByte(flags);

Data?.CopyTo(stream);
}
break;
default:
break;
}

/// <summary>已重载。</summary>
/// <returns></returns>
public override String ToString() => $"[{PDUType}] TPDUNumber: {Number} Last: {LastDataUnit} Segment Data: {BitConverter.ToString(Data)}";
return true;
}
#endregion

/// <summary>
/// Describes a COTP TSDU (Transport service data unit). One TSDU consist of 1 ore more TPDUs
/// Reads COTP TPDU (Transport protocol data unit) from the network stream
/// See: https://tools.ietf.org/html/rfc905
/// </summary>
public class TSDU
/// <param name="stream">The socket to read from</param>
/// <param name="cancellationToken"></param>
/// <returns>COTP DPDU instance</returns>
public static async Task<COTP> ReadAsync(Stream stream, CancellationToken cancellationToken)
{
/// <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>
/// <param name="cancellationToken"></param>
/// <returns>Data in TSDU</returns>
public static async Task<Byte[]> ReadAsync(Stream stream, CancellationToken cancellationToken)
{
var segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var tpkt = await TPKT.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (tpkt.Length == 0) throw new TPDUInvalidException("No protocol data received");

if (segment.LastDataUnit) return segment.Data;
var cotp = new COTP();
if (!cotp.Read(tpkt.Data)) throw new TPDUInvalidException("Invalid protocol data received");

// More segments are expected, prepare a buffer to store all data
var buffer = new Byte[segment.Data.Length];
Array.Copy(segment.Data, buffer, segment.Data.Length);
return cotp;
}

/// <summary>已重载。</summary>
/// <returns></returns>
public override String ToString() => $"[{Type}] TPDUNumber: {Number} Last: {LastDataUnit} Data[{Data?.Total}]";

while (!segment.LastDataUnit)
{
segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var previousLength = buffer.Length;
Array.Resize(ref buffer, buffer.Length + segment.Data.Length);
Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length);
}
/// <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>
/// <param name="cancellationToken"></param>
/// <returns>Data in TSDU</returns>
public static async Task<Packet> ReadTsduAsync(Stream stream, CancellationToken cancellationToken)
{
var cotp = await ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (cotp.LastDataUnit) return cotp.Data;

return buffer;
while (!cotp.LastDataUnit)
{
var seg = await ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (seg != null && seg.Data != null) cotp.Data.Append(seg.Data);

cotp = seg;
}

return cotp.Data;
}

}
14 changes: 14 additions & 0 deletions NewLife.Siemens/Protocols/PduType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace NewLife.Siemens.Protocols;

/// <summary>PDU类型</summary>
public enum PduType : Byte
{
/// <summary>数据帧</summary>
Data = 0xf0,

/// <summary>CR连接请求帧</summary>
ConnectionRequest = 0xe0,

/// <summary>CC连接确认帧</summary>
ConnectionConfirmed = 0xd0
}
Loading

0 comments on commit ed1fe43

Please sign in to comment.