using DTS.Common.Utilities.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; namespace DTS.DASLib.Command.TDAS { /* * typedef struct _md { int delim; delimiter pattern int key; key to mem state long itemSize; mem size that this MDB controls long dataSize; length of valid data buffer unsigned int nCrc; buffer crc void *flink; forward link to next buffer struct _md *next; used for doubly-linked list struct _md *prev; of all mds void *this; this buffer of size itemSize void *freeList; a free list of objects long nInst; number of memory blocks created long nActive; number still out there somewhere long nFree; length of free list }*/ //note in CVI longs are the same size as ints, or 4 bytes internal class MDB_BLOCK { public enum MDB_Fields { delim = 0, key, itemSize, dataSize, nCrc, flink, next, prev, ptrThis, freeList, nInst, nActive, nFree } public int GetField(MDB_Fields field) { return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(_mdb, 4 * (int)field)); } private byte[] _mdb; public const int MDB_BLOCK_SIZE = 52; public const int DMA_SIZE = 1024; public const int DMA_BLOCK_SIZE = MDB_BLOCK_SIZE + DMA_SIZE; public const int MEM_KEY_START = 100; public static readonly byte[] DelimBytes = { 0xFE, 0x01, 0xFD, 0x02 }; public static readonly int DelimPattern = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(DelimBytes, 0)); public static readonly string DelimString = BitConverter.ToString(DelimBytes); public MDB_BLOCK(byte[] packet) { _mdb = packet; } public bool HasData => (null != _mdb && GetField(MDB_Fields.dataSize) > 0); public short[] GetData() { var data = new List(); for (var i = 0; i < GetField(MDB_Fields.dataSize) / 2; i++) { try { var value = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(_mdb, MDB_BLOCK_SIZE + i * 2)); data.Add(value); } catch (Exception) { Trace.WriteLine("failed to convert " + i); } } return data.ToArray(); } } public class TDASCommandPacketBase : CommandPacketBase { public bool RackCommand { get => _commandStrings[0].RackCommand; set => _commandStrings[0].RackCommand = value; } public bool SingleModuleCommand { get { return _commandStrings[0].SingleModuleCommand; } set { _commandStrings[0].SingleModuleCommand = value; } } public string RackSerialNumber { get { return _commandStrings[0].RackSerialNumber; } set { _commandStrings[0].RackSerialNumber = value; } } private char _moduleIndex = ' '; public int ModuleIndex { get => int.Parse(new string(_moduleIndex, 1)); set { if (value > 9) { throw new Exception("ModuleIndex must be <10 for this command" + ModuleIndex); } _moduleIndex = value.ToString()[0]; RackCommand = false; } } public bool AllModule { get; set; } public bool RebuildBytes { get; set; } public bool ExpectsData { get => _commandStrings[0].ExpectsData; set => _commandStrings[0].ExpectsData = value; } public bool ExpectsMultipleLines { get => _commandStrings[0].ExpectsMultipleLines; set => _commandStrings[0].ExpectsMultipleLines = value; } public int LinesExpected { get => _commandStrings[0].LinesExpected; set => _commandStrings[0].LinesExpected = value; } private const int MAX_CMD = 1; private const int MAX_CMD_SIZE = 512; public const int CDB_SIZE = 536; private readonly byte _cdbId; private readonly byte _cdbQueue; public byte CharWait { get; set; } public int ReplyWait { get; set; } private readonly byte[] _reserved; private readonly int _rackId; private readonly byte[] _memoryAddress = GENERIC_MEMORY_ADDRESS; private readonly List _commandStrings = new List(); private readonly byte[] _responseBytes; private static readonly byte[] GENERIC_MEMORY_ADDRESS = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public override byte[] ToBytes() { if (null != _responseBytes) { return _responseBytes; } var ms = new MemoryStream(CDB_SIZE); ms.WriteByte(_cdbId); ms.WriteByte(_cdbQueue); ms.WriteByte(Convert.ToByte(_commandStrings.Count)); ms.WriteByte(CharWait); ms.Write(BitConverter.GetBytes(ReplyWait), 0, 4); ms.Write(_reserved, 0, _reserved.Length); ms.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(_rackId)), 0, 4); ms.Write(_memoryAddress, 0, _memoryAddress.Length); if (RackCommand) { ms.Write(new byte[] { 0x31, 0x30, 0x30, 0x30 }, 0, 4); } else { if (AllModule) { ms.WriteByte(Convert.ToByte('*')); } if (_moduleIndex != ' ') { ms.WriteByte(Convert.ToByte(Convert.ToByte(_moduleIndex))); } } foreach (var cs in _commandStrings) { if (RebuildBytes) { cs.RebuildBytes(); } ms.Write(cs.GetBytes(), 0, cs.Length); } //17716 eliminate extra characters filling out outgoing TDAS coms //if (ms.Length < CDB_SIZE) //{ // ms.Write(new byte[CDB_SIZE - (int)ms.Length], 0, CDB_SIZE - (int)ms.Length); //} return ms.ToArray(); } public override object ConvertByteToCommandType(byte b) { throw new NotSupportedException("TDASCommandPacketBase::ConvertByteToCommandType not supported"); } private int _expectedBytes; private int _bytesSoFar; public bool UseMDBMode { get; set; } public bool MonitorDataMode { get; set; } private readonly List _data = new List(); private int _curOffset; public int GetExpectedBytes() { return _expectedBytes; } public short[] GetData() { List data = new List(); //we have to skip the first one for some odd reason? for (int i = 1; i < _data.Count; i++) { MDB_BLOCK mdb = _data[i]; if (mdb.HasData) { data.AddRange(mdb.GetData()); } } return data.ToArray(); } public bool RealtimeCommand { get; set; } public override PacketState VerifyPacket(byte[] Bytes) { if (!ExpectsData && Bytes.Length > 0) { Thread.Sleep(100); //cut from 500 to 100 for performance reasons APILogger.Log($"VerifyPacket [{SequenceNumber}], returning OK as we don't expect data and have greater than 0 length ({Bytes.Length})"); return PacketState.OK; } if (UseMDBMode) { if (0 == _curOffset) { int index = -1; string s = BitConverter.ToString(Bytes); try { index = s.IndexOf(MDB_BLOCK.DelimString, 1); } catch (Exception) { } if (index < 0) { return PacketState.TooShort; } if (index > 0) { //need to advance to the start of the block for (var i = 1; i < Bytes.Length - MDB_BLOCK.DelimBytes.Length; i++) { if (Bytes[i] != MDB_BLOCK.DelimBytes[0] || Bytes[i + 1] != MDB_BLOCK.DelimBytes[1] || Bytes[i + 2] != MDB_BLOCK.DelimBytes[2] || Bytes[i + 3] != MDB_BLOCK.DelimBytes[3]) continue; _curOffset = i; break; } } } while (Bytes.Length - _curOffset >= MDB_BLOCK.DMA_BLOCK_SIZE) { byte[] block; using (var ms = new MemoryStream()) { //need to advance to the start of the block for (var i = _curOffset; i < Bytes.Length - MDB_BLOCK.DelimBytes.Length; i++) { if (Bytes[i] != MDB_BLOCK.DelimBytes[0] || Bytes[i + 1] != MDB_BLOCK.DelimBytes[1] || Bytes[i + 2] != MDB_BLOCK.DelimBytes[2] || Bytes[i + 3] != MDB_BLOCK.DelimBytes[3]) continue; _curOffset = i; break; } //scan forward from _curOffset till we see the ms.Write(Bytes, _curOffset, MDB_BLOCK.DMA_BLOCK_SIZE); _curOffset += MDB_BLOCK.DMA_BLOCK_SIZE; block = ms.ToArray(); } var mdb = new MDB_BLOCK(block); _data.Add(mdb); if (mdb.GetField(MDB_BLOCK.MDB_Fields.delim) == MDB_BLOCK.DelimPattern) { if (mdb.GetField(MDB_BLOCK.MDB_Fields.key) == MDB_BLOCK.MEM_KEY_START) { var s = Encoding.ASCII.GetString(block, MDB_BLOCK.MDB_BLOCK_SIZE + 5, 10); var tokens = s.Split(' '); _expectedBytes = Convert.ToInt32(tokens[0]); } else { _bytesSoFar += mdb.GetField(MDB_BLOCK.MDB_Fields.dataSize); if (_bytesSoFar >= _expectedBytes && 0 != _expectedBytes) { return PacketState.OK; } } } else { return PacketState.OK; } } return PacketState.TooShort; } { string s = Encoding.ASCII.GetString(Bytes); //Could be a multi-line response where we've grabbed the newline as the first characters. if (s.Length > 2 && s.IndexOf("\r\n") >= 0) { if (!s.EndsWith("\r\n") && ExpectsMultipleLines) { APILogger.Log($"TDASCommandPacketBase:VerifyPacket [{SequenceNumber}], returning too short {s}"); return PacketState.TooShort; } if (!ExpectsMultipleLines) { APILogger.Log($"TDASCommandPacketBase:VerifyPacket [{SequenceNumber}], returning OK, not expecting multiple lines: {s}"); return PacketState.OK; } var lines = s.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); //G5 returns ~ and if we see that we know we still have more lines coming //TDAS PRO does not, but we do have an expectation of a certain number of lines if (lines[lines.Length - 1].Contains('~')) { APILogger.Log($"[{SequenceNumber}] returning packet too short (contains ~) {s}"); return PacketState.TooShort; } if (lines.Length < LinesExpected) { APILogger.Log( $"[{SequenceNumber}] Lines less than expected, returning too short {lines.Length}<{LinesExpected} for {s}"); return PacketState.TooShort; } //if command has a required response and we haven't seen it yet, //consider us as having not received the full response yet. //18127 Test Channel Run Broadcast can complete before response is received and processed if (null != _commandStrings && 1 == _commandStrings.Count && !string.IsNullOrEmpty(_commandStrings[0].RequiredResponseString)) { if (!s.Contains(_commandStrings[0].RequiredResponseString)) { return PacketState.TooShort; } } return PacketState.OK; } APILogger.Log($"[{SequenceNumber}] Returning too short - {s} - didn't find CR-EOL"); return PacketState.TooShort; } } private static ushort _globalSequenceNumber; private static readonly object GLOBAL_SEQUENCE_NUMBER_LOCK = new object(); public override void GetNextSequenceNumber() { lock (GLOBAL_SEQUENCE_NUMBER_LOCK) { SequenceNumber = _globalSequenceNumber; _globalSequenceNumber++; } } public override void ComputeCRCs() { } private void AddCommand(CommandString commandString) { if (_commandStrings.Count + 1 > MAX_CMD) { throw new CommandPacketException(CommandPacketException.ERROR_CODES.TOO_MANY_COMMANDS, CommandPacketException.ERROR_CODES.TOO_MANY_COMMANDS.ToString()); } int totalBytes = commandString.Length; foreach (CommandString cs in _commandStrings) { totalBytes += cs.Length; } if (totalBytes > MAX_CMD_SIZE) { throw new CommandPacketException(CommandPacketException.ERROR_CODES.TOO_MANY_BYTES, CommandPacketException.ERROR_CODES.TOO_MANY_BYTES.ToString()); } _commandStrings.Add(commandString); SetCommand(0x00, commandString.GetCommandDescription()); Type = "TDAS"; } public TDASCommandPacketBase(CommandString cs) { GetNextSequenceNumber(); _cdbId = 1; _cdbQueue = Convert.ToByte(false); //support.dtsweb.com/hc/requests/7294 //this is used to avoid flooding buffer, but was too large, TDC is using 1ms and presumably when we copied //tdc we must have used an older version CharWait = 0x01; ReplyWait = 30; _reserved = new byte[] { 0x00, 0x00, 0x00, 0x00 }; _rackId = 1006; AddCommand(cs); } public TDASCommandPacketBase(byte[] buffer, CommandPacketBase command) { if (null != command) { SequenceNumber = command.SequenceNumber; } var s = Encoding.ASCII.GetString(buffer); if (s.Contains("1000ARM")) { APILogger.Log($"Sequence [{SequenceNumber}] complete response", s); } _responseBytes = buffer; SetCommand(0x00, ""); Type = "TDAS"; } public string GetCommandString(int index) { return _commandStrings[index].GetCommandPortion(); } public CommandString GetCommandStringObject(int index) { return _commandStrings[index]; } public string ToCommandString() { byte[] bytes = ToBytes(); return Encoding.ASCII.GetString(bytes, 24, bytes.Length - 24).Replace("\r\n", string.Empty).Replace("\0", string.Empty); } } public abstract class CommandString { /// /// defines a required string to find in the response /// if string is not found response is not complete yet /// override to require a response /// 18127 Test Channel Run Broadcast can complete before response is received and processed /// public virtual string RequiredResponseString { get; set; } = string.Empty; public bool ExpectsData { get; set; } = true; public bool ExpectsMultipleLines { get; set; } public int LinesExpected { get; set; } public virtual byte[] GetParameters() { return new byte[] { }; } private bool _bRackCommand = true; public bool RackCommand { get { return _bRackCommand; } set { _bRackCommand = value; } } private bool _bSingleModuleCommand = false; public bool SingleModuleCommand { get { return _bSingleModuleCommand; } set { _bSingleModuleCommand = value; } } private string _RackSerialNumber = string.Empty; public string RackSerialNumber { get { return _RackSerialNumber; } set { _RackSerialNumber = value; } } protected abstract string _CommandString { get; } public virtual string GetCommandPortion() { return _CommandString; } protected abstract string _CommandDescription { get; } public string GetCommandDescription() { return _CommandDescription; } private byte[] _bytes; public int Length { get { if (null == _bytes) { _bytes = GetBytes(); } return _bytes.Length; } } public void RebuildBytes() { _bytes = null; } public byte[] GetBytes() { if (null != _bytes) return _bytes; var ms = new MemoryStream(); ms.Write(Encoding.ASCII.GetBytes(_CommandString), 0, _CommandString.Length); byte[] parameters = GetParameters(); if (parameters.Length > 0) { ms.Write(parameters, 0, parameters.Length); } ms.Write(new byte[] { 0x0d, 0x0a }, 0, 2); _bytes = ms.ToArray(); return _bytes; } } public class CommandPacketException : Exception { public enum ERROR_CODES { TOO_MANY_COMMANDS = 0, TOO_MANY_BYTES = 1, UNKNOWN = 2 } public CommandPacketException(ERROR_CODES errorCode, string description) : base(description) { Data["ErrorCode"] = errorCode; } public ERROR_CODES ErrorCode { get { if (!Data.Contains("ErrorCode")) { return ERROR_CODES.UNKNOWN; } return (ERROR_CODES)Data["ErrorCode"]; } } } }