using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace S7.Net
{
///
/// COTP Protocol functions and types
///
internal class COTP
{
public enum PduType : byte
{
Data = 0xf0,
ConnectionConfirmed = 0xd0
}
///
/// Describes a COTP TPDU (Transport protocol data unit)
///
public class TPDU
{
public TPKT TPkt { get; }
public byte HeaderLength;
public PduType PDUType;
public int TPDUNumber;
public byte[] Data;
public bool LastDataUnit;
public TPDU(TPKT tPKT)
{
TPkt = tPKT;
HeaderLength = tPKT.Data[0]; // Header length excluding this length byte
if (HeaderLength >= 2)
{
PDUType = (PduType)tPKT.Data[1];
if (PDUType == PduType.Data) //DT Data
{
var flags = tPKT.Data[2];
TPDUNumber = flags & 0x7F;
LastDataUnit = (flags & 0x80) > 0;
Data = new byte[tPKT.Data.Length - HeaderLength - 1]; // substract header length byte + header length.
Array.Copy(tPKT.Data, HeaderLength + 1, Data, 0, Data.Length);
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
/// A cancellation token that can be used to cancel the asynchronous operation.
/// COTP DPDU instance
public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken)
{
var tpkt = await TPKT.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (tpkt.Length == 0)
{
throw new TPDUInvalidException("No protocol data received");
}
return new TPDU(tpkt);
}
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
///
/// The stream to read from
/// A cancellation token that can be used to cancel the asynchronous operation.
/// Data in TSDU
public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken)
{
var segment = await TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (segment.LastDataUnit)
{
return segment.Data;
}
// 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);
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);
}
return buffer;
}
}
}
}