StateMachineMessageCutter.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. using Edge.Core.Processor.Communicator;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. namespace Dfs.WayneChina.GilbarcoDispenserPayTerminal
  7. {
  8. public class StateMachineMessageCutter : IMessageCutter<byte[]>
  9. {
  10. public byte[] Message { get; private set; }
  11. public event EventHandler OnMessageCut;
  12. public event EventHandler<MessageCutterInvalidMessageReadEventArg> OnInvalidMessageRead;
  13. static NLog.Logger innerLogger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("StateMachineMessageCutter");
  14. private string loggerAppendix = "GilbarcoPayTerminal msgCutter ";
  15. private readonly SizableWindow<byte> window;
  16. private State nextState = State.Uninitialized;
  17. /// <summary>
  18. /// Constructor
  19. /// </summary>
  20. public StateMachineMessageCutter()
  21. {
  22. this.window = new SizableWindow<byte>(1);
  23. //Each 0xFA pair in message body part need extend buffer 1 byte.
  24. int extendBufferTimes = 0;
  25. this.window.OnWindowFull += (data) =>
  26. {
  27. //if (innerLogger.IsDebugEnabled)
  28. //{
  29. // innerLogger.Debug(BitConverter.ToString(data.ToArray()));
  30. //}
  31. switch (nextState)
  32. {
  33. case State.Uninitialized:
  34. if (data[0] == 0xFA)
  35. {
  36. extendBufferTimes = 0;
  37. this.window.NewSize = 2;
  38. this.nextState = State.HeaderReady;
  39. if (innerLogger.IsDebugEnabled)
  40. innerLogger.Debug(this.loggerAppendix + " state is State.Uninitialized and next is 0xFA, switch to HeaderReady");
  41. }
  42. else
  43. {
  44. this.window.Clear();
  45. return;
  46. }
  47. break;
  48. case State.HeaderReady:
  49. if (this.window.Count(h => h == 0xFA) == 1)
  50. {
  51. if (innerLogger.IsDebugEnabled)
  52. innerLogger.Debug(this.loggerAppendix + " Only 1 time of 0xFA in header window, switch to LengthReady");
  53. this.nextState = State.LengthReady;
  54. this.window.NewSize += 4;
  55. }
  56. else
  57. {
  58. if (innerLogger.IsDebugEnabled)
  59. innerLogger.Debug(this.loggerAppendix + " 2 times of 0xFA in header window, drop all and wait...");
  60. // double 0xFA, not a starter, will drop this 2 0xFA.
  61. this.nextState = State.Uninitialized;
  62. this.window.Clear();
  63. this.window.NewSize = 1;
  64. }
  65. break;
  66. case State.LengthReady:
  67. var bodyLen = BcdBytesToInt(new byte[2] { window[4], window[5] });//this.window[4] * 10 + this.window[5];
  68. if (bodyLen > 9999 || bodyLen < 2)
  69. {
  70. var safe8 = this.OnInvalidMessageRead;
  71. safe8?.Invoke(this, new MessageCutterInvalidMessageReadEventArg()
  72. {
  73. Message = "MessageBody Length is a 2 bytes BCD encoded and expected value here is >=2 and <=9999 while now is " + bodyLen
  74. });
  75. this.nextState = State.Uninitialized;
  76. this.window.Clear();
  77. this.window.NewSize = 1;
  78. return;
  79. }
  80. this.window.NewSize += bodyLen;
  81. this.nextState = State.BodyReady;
  82. if (innerLogger.IsDebugEnabled)
  83. innerLogger.Debug(this.loggerAppendix + " MsgBodyLen caculated with: " + this.window.NewSize + ", and set default windowSize to this value.");
  84. break;
  85. case State.BodyReady:
  86. /* window size not match with MsgBodyLen, indicates there's one or more 0xFA in body.*/
  87. var s = this.Get0xFAPairCountInWindow(this.window.Skip(6));
  88. if (extendBufferTimes < s)
  89. {
  90. this.window.NewSize += s - extendBufferTimes;
  91. extendBufferTimes = s;
  92. return;
  93. }
  94. extendBufferTimes = 0;
  95. this.window.NewSize += 2;
  96. // shrink the NewSize.
  97. this.window.NewSize -= this.Reduce0xFAPair(this.window, 6);
  98. this.nextState = State.CrcReady;
  99. break;
  100. case State.CrcReady:
  101. bodyLen = BcdBytesToInt(new byte[2] { window[4], window[5] });//this.window[4] * 10 + this.window[5];
  102. var p = this.Get0xFAPairCountInWindow(this.window.Skip(6 + bodyLen));
  103. if (extendBufferTimes < p)
  104. {
  105. this.window.NewSize += p - extendBufferTimes;
  106. extendBufferTimes = p;
  107. return;
  108. }
  109. this.Reduce0xFAPair(this.window, 6 + bodyLen);
  110. if (this.window.Count != (6 + bodyLen + 2))
  111. {
  112. var safe8 = this.OnInvalidMessageRead;
  113. safe8?.Invoke(this, new MessageCutterInvalidMessageReadEventArg()
  114. {
  115. Message = "CRC part is invalid"
  116. });
  117. this.nextState = State.Uninitialized;
  118. this.window.Clear();
  119. this.window.NewSize = 1;
  120. return;
  121. }
  122. this.Message = this.window.ToArray();
  123. var safe11 = this.OnMessageCut;
  124. safe11?.Invoke(this, null);
  125. this.nextState = State.Uninitialized;
  126. this.window.Clear();
  127. this.window.NewSize = 1;
  128. return;
  129. default:
  130. throw new ArgumentOutOfRangeException();
  131. }
  132. };
  133. //this.window.OnWindowFull += (data) =>
  134. //{
  135. // switch (nextState)
  136. // {
  137. // case State.Uninitialized:
  138. // if (data.First() == 0xFA)
  139. // {
  140. // extendBufferTimes = 0;
  141. // window.NewSize = 2;
  142. // nextState = State.LengthReady;
  143. // }
  144. // else
  145. // {
  146. // OnInvalidMessageRead?.Invoke(this, new MessageCutterInvalidMessageReadEventArg()
  147. // {
  148. // Message = "invalid byte[0]: 0x" + data.First().ToString("x2") + ", will skip"
  149. // });
  150. // window.Clear();
  151. // }
  152. // break;
  153. // case State.HeaderReady:
  154. // if (this.window.Count(h => h == 0xFA) == 1)
  155. // {
  156. // if (innerLogger.IsDebugEnabled)
  157. // innerLogger.Debug(this.loggerAppendix + " Only 1 time of 0xFA in header window, switch to LengthReady");
  158. // this.nextState = State.LengthReady;
  159. // this.window.NewSize += 4;
  160. // }
  161. // else
  162. // {
  163. // if (innerLogger.IsDebugEnabled)
  164. // innerLogger.Debug(this.loggerAppendix + " 2 times of 0xFA in header window, drop all and wait...");
  165. // // double 0xFA, not a starter, will drop this 2 0xFA.
  166. // this.nextState = State.Uninitialized;
  167. // this.window.Clear();
  168. // this.window.NewSize = 1;
  169. // }
  170. // break;
  171. // case State.LengthReady:
  172. // var lengthBytes = window.Skip(4).Take(2);
  173. // window.NewSize = lengthBytes.ToArray().ToInt32() + 6;
  174. // nextState = State.CrcReady;
  175. // break;
  176. // case State.CrcReady:
  177. // window.NewSize = window.Skip(4).Take(2).ToArray().ToInt32() + 6 + 2;
  178. // nextState = State.BodyReady;
  179. // break;
  180. // case State.BodyReady:
  181. // Message = window.ToArray();
  182. // var safe = OnMessageCut;
  183. // safe?.Invoke(this, null);
  184. // nextState = State.Uninitialized;
  185. // window.Clear();
  186. // break;
  187. // default:
  188. // throw new ArgumentOutOfRangeException();
  189. // }
  190. //};
  191. }
  192. private int BcdBytesToInt(byte[] bcds)
  193. {
  194. int result = 0;
  195. foreach (byte bcd in bcds)
  196. {
  197. result *= 100;
  198. result += (10 * (bcd >> 4));
  199. result += bcd & 0xf;
  200. }
  201. return result;
  202. }
  203. /// <summary>
  204. /// if the 0xFA count is odd, will use round up for count/2.
  205. /// e.g.: 0xFA appeared 2 times, return 1.
  206. /// 0xFA appeared 3 times, return 2, this is considered as window is not big enought to include further 0xFA since they're always even.
  207. /// </summary>
  208. /// <returns></returns>
  209. private int Get0xFAPairCountInWindow(IEnumerable<byte> data)
  210. {
  211. return (int)Math.Round(((double)(data.Count(w => w == 0xFA)) / 2), MidpointRounding.AwayFromZero);
  212. }
  213. /// <summary>
  214. /// Iterates the target by searching all pair(one besides one) of 0xFA, and reduce each pair into a single 0xFA.
  215. /// </summary>
  216. /// <param name="target"></param>
  217. /// <param name="startIndex"></param>
  218. /// <returns>how many timed of reduced happened. each reduce will remove one byte</returns>
  219. public int Reduce0xFAPair(IList<byte> target, int startIndex)
  220. {
  221. int reducedCount = 0;
  222. var faAppearedPositions = new List<int>();
  223. for (int i = startIndex; i < target.Count; i++)
  224. {
  225. if (target[i] == 0xFA)
  226. {
  227. if (i <= (target.Count - 2))
  228. {
  229. if (target[i + 1] == 0xFA)
  230. {
  231. faAppearedPositions.Add(i);
  232. i++;
  233. }
  234. }
  235. }
  236. }
  237. for (int i = 0; i < faAppearedPositions.Count; i++)
  238. {
  239. target.RemoveAt(faAppearedPositions[i] - i);
  240. reducedCount++;
  241. }
  242. return reducedCount;
  243. }
  244. private enum State
  245. {
  246. Uninitialized,
  247. //HeaderSeeking,
  248. HeaderReady,
  249. LengthReady,
  250. BodyReady,
  251. CrcReady,
  252. }
  253. public void Feed(byte[] next)
  254. {
  255. //innerLogger.Debug(this.loggerAppendix + " " + next.ToHexLogString() + " is feed in Window in state: " + nextState);
  256. for (int i = 0; i < next.Length; i++)
  257. this.window.Add(next[i]);
  258. }
  259. }
  260. }