using Edge.Core.Processor.Communicator; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Dfs.WayneChina.GilbarcoDispenserPayTerminal { 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 = "GilbarcoPayTerminal msgCutter "; private readonly SizableWindow window; private State nextState = State.Uninitialized; /// /// Constructor /// 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) => { //if (innerLogger.IsDebugEnabled) //{ // innerLogger.Debug(BitConverter.ToString(data.ToArray())); //} 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 = BcdBytesToInt(new byte[2] { window[4], window[5] });//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 = BcdBytesToInt(new byte[2] { window[4], window[5] });//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(); } }; //this.window.OnWindowFull += (data) => //{ // switch (nextState) // { // case State.Uninitialized: // if (data.First() == 0xFA) // { // extendBufferTimes = 0; // window.NewSize = 2; // nextState = State.LengthReady; // } // else // { // OnInvalidMessageRead?.Invoke(this, new MessageCutterInvalidMessageReadEventArg() // { // Message = "invalid byte[0]: 0x" + data.First().ToString("x2") + ", will skip" // }); // window.Clear(); // } // 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 lengthBytes = window.Skip(4).Take(2); // window.NewSize = lengthBytes.ToArray().ToInt32() + 6; // nextState = State.CrcReady; // break; // case State.CrcReady: // window.NewSize = window.Skip(4).Take(2).ToArray().ToInt32() + 6 + 2; // nextState = State.BodyReady; // break; // case State.BodyReady: // Message = window.ToArray(); // var safe = OnMessageCut; // safe?.Invoke(this, null); // nextState = State.Uninitialized; // window.Clear(); // break; // default: // throw new ArgumentOutOfRangeException(); // } //}; } private int BcdBytesToInt(byte[] bcds) { int result = 0; foreach (byte bcd in bcds) { result *= 100; result += (10 * (bcd >> 4)); result += bcd & 0xf; } return result; } /// /// 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]); } } }