using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Edge.Core.Parser.BinaryParser.Util; using Edge.Core.Processor.Communicator; namespace LanTian_Sinopec_PumpIcCardReader { public class StateMachineMessageCutter : IMessageCutter { public byte[] Message { get; private set; } public event EventHandler OnMessageCut; public event EventHandler OnInvalidMessageRead; static NLog.Logger innerLogger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("StateMachineMessageCutter"); private string loggerAppendix = "LanTian_Sinopec_PumpIcCardReader msgCutter "; private readonly SizableWindow window; private State nextState = State.Uninitialized; /// /// /// public StateMachineMessageCutter() { this.window = new SizableWindow(1); //each 0xFA pair in message body part need extend buffer 1 byte. int extendBufferTimes = 0; this.window.OnWindowFull += (data) => { switch (nextState) { case State.Uninitialized: if (data[0] == 0xFA) { extendBufferTimes = 0; this.window.NewSize = 2; this.nextState = State.HeaderReady; if (innerLogger.IsDebugEnabled) innerLogger.Debug(this.loggerAppendix + " state is State.Uninitialized and next is 0xFA, switch to HeaderReady"); } else { this.window.Clear(); return; } break; case State.HeaderReady: if (this.window.Count(h => h == 0xFA) == 1) { if (innerLogger.IsDebugEnabled) innerLogger.Debug(this.loggerAppendix + " Only 1 time of 0xFA in header window, switch to LengthReady"); this.nextState = State.LengthReady; this.window.NewSize += 4; } else { if (innerLogger.IsDebugEnabled) innerLogger.Debug(this.loggerAppendix + " 2 times of 0xFA in header window, drop all and wait..."); // double 0xFA, not a starter, will drop this 2 0xFA. this.nextState = State.Uninitialized; this.window.Clear(); this.window.NewSize = 1; } break; case State.LengthReady: var bodyLen = this.window[4] * 10 + this.window[5]; if (bodyLen > 9999 || bodyLen < 2) { var safe8 = this.OnInvalidMessageRead; safe8?.Invoke(this, new MessageCutterInvalidMessageReadEventArg() { Message = "MessageBody Length is a 2 bytes BCD encoded and expected value here is >=2 and <=9999 while now is " + bodyLen }); this.nextState = State.Uninitialized; this.window.Clear(); this.window.NewSize = 1; return; } this.window.NewSize += bodyLen; this.nextState = State.BodyReady; if (innerLogger.IsDebugEnabled) innerLogger.Debug(this.loggerAppendix + " MsgBodyLen caculated with: " + this.window.NewSize + ", and set default windowSize to this value."); break; case State.BodyReady: /* window size not match with MsgBodyLen, indicates there's one or more 0xFA in body.*/ var s = this.Get0xFAPairCountInWindow(this.window.Skip(6)); if (extendBufferTimes < s) { this.window.NewSize += s - extendBufferTimes; extendBufferTimes = s; return; } extendBufferTimes = 0; this.window.NewSize += 2; // shrink the NewSize. this.window.NewSize -= this.Reduce0xFAPair(this.window, 6); this.nextState = State.CrcReady; break; case State.CrcReady: bodyLen = this.window[4] * 10 + this.window[5]; var p = this.Get0xFAPairCountInWindow(this.window.Skip(6 + bodyLen)); if (extendBufferTimes < p) { this.window.NewSize += p - extendBufferTimes; extendBufferTimes = p; return; } this.Reduce0xFAPair(this.window, 6 + bodyLen); if (this.window.Count != (6 + bodyLen + 2)) { var safe8 = this.OnInvalidMessageRead; safe8?.Invoke(this, new MessageCutterInvalidMessageReadEventArg() { Message = "CRC part is invalid" }); this.nextState = State.Uninitialized; this.window.Clear(); this.window.NewSize = 1; return; } this.Message = this.window.ToArray(); var safe11 = this.OnMessageCut; safe11?.Invoke(this, null); this.nextState = State.Uninitialized; this.window.Clear(); this.window.NewSize = 1; return; default: throw new ArgumentOutOfRangeException(); } }; } /// /// if the 0xFA count is odd, will use round up for count/2. /// e.g.: 0xFA appeared 2 times, return 1. /// 0xFA appeared 3 times, return 2, this is considered as window is not big enought to include further 0xFA since they're always even. /// /// private int Get0xFAPairCountInWindow(IEnumerable data) { return (int)Math.Round(((double)(data.Count(w => w == 0xFA)) / 2), MidpointRounding.AwayFromZero); } /// /// Iterates the target by searching all pair(one besides one) of 0xFA, and reduce each pair into a single 0xFA. /// /// /// /// how many timed of reduced happened. each reduce will remove one byte public int Reduce0xFAPair(IList target, int startIndex) { int reducedCount = 0; var faAppearedPositions = new List(); for (int i = startIndex; i < target.Count; i++) { if (target[i] == 0xFA) { if (i <= (target.Count - 2)) { if (target[i + 1] == 0xFA) { faAppearedPositions.Add(i); i++; } } } } for (int i = 0; i < faAppearedPositions.Count; i++) { target.RemoveAt(faAppearedPositions[i] - i); reducedCount++; } return reducedCount; } private enum State { Uninitialized, //HeaderSeeking, HeaderReady, LengthReady, BodyReady, CrcReady, } public void Feed(byte[] next) { //innerLogger.Debug(this.loggerAppendix + " " + next.ToHexLogString() + " is feed in Window in state: " + nextState); for (int i = 0; i < next.Length; i++) this.window.Add(next[i]); } } }