ParserBase.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  1. using Edge.Core.Parser.BinaryParser.Attributes;
  2. using Edge.Core.Parser.BinaryParser.MessageEntity;
  3. using Edge.Core.Parser.BinaryParser.Util;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.ComponentModel.DataAnnotations;
  8. using System.Linq;
  9. using System.Reflection;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. namespace Edge.Core.Parser.BinaryParser
  13. {
  14. public abstract class ParserBase : IMessageParser<byte[], MessageTemplateBase>
  15. {
  16. protected static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("ParserBase");
  17. private int debug_ConvertToObject_depth = 0;
  18. private IMessageTemplateLookup templateLookup;
  19. /// <summary>
  20. /// ongoing processing raw data array, make 2 of this array is for thread safe
  21. /// </summary>
  22. private byte[] processingRawDataUsedInSerialize = null;
  23. /// <summary>
  24. /// ongoing processing raw data array, make 2 of this array is for thread safe
  25. /// </summary>
  26. private byte[] processingRawDataUsedInDeserialize = null;
  27. private int positiveIndexAccu = 0;
  28. /// <summary>
  29. /// Fired on when bytes incoming and will start to convert to a object, but not started yet.
  30. /// </summary>
  31. public event EventHandler<ParsingEventArg<byte[], MessageTemplateBase>> Deserializing;
  32. /// <summary>
  33. /// Fired on when bytes incoming and started to convert to a object, and finished already.
  34. /// </summary>
  35. public event EventHandler<DeserializeEventArg<byte[], MessageTemplateBase>> Deserialized;
  36. public event EventHandler<ParsingEventArg<MessageTemplateBase, byte[]>> Serializing;
  37. public event EventHandler<ParsingEventArg<MessageTemplateBase, byte[]>> Serialized;
  38. /// <summary>
  39. /// Fired on a Format Attibute marked field will starting to convert to bytes, but not started yet.
  40. /// string in ParsingEventArg is the field name, byte[] should always null here since not started.
  41. /// </summary>
  42. public event EventHandler<ParsingEventArg<string, byte[]>> FieldSerializing;
  43. /// <summary>
  44. /// Fired on a Format Attibute marked field started to convert to bytes, and finished already.
  45. /// string in ParsingEventArg is the field name, byte[] should have values for this already serialized field.
  46. /// </summary>
  47. public event EventHandler<ParsingEventArg<string, byte[]>> FieldSerialized;
  48. protected ParserBase(IMessageTemplateLookup templateLookup)
  49. {
  50. this.templateLookup = templateLookup;
  51. }
  52. protected ParserBase() : this(null)
  53. {
  54. }
  55. /// <summary>
  56. /// There's a build-in check in Deserialize process, if all fields in a template had been filled, but still
  57. /// some extra bytes left, an exception will throw, this must be the error in raw bytes.
  58. /// But you can.
  59. /// </summary>
  60. public bool IgnoreExtraTailBytesInDeserialize
  61. {
  62. get; set;
  63. }
  64. /// <summary>
  65. /// Deserialize a byte[] into a Message entity, the `TemplateLookup` must specified firstly via constructor.
  66. /// </summary>
  67. /// <param name="rawData"></param>
  68. /// <returns></returns>
  69. public virtual MessageTemplateBase Deserialize(byte[] rawData)
  70. {
  71. if (this.templateLookup == null) throw new ArgumentNullException("Must specify a 'TemplateLookup' to start Deserialize");
  72. MessageTemplateBase messageTemplateBase = templateLookup.GetMessageTemplateByRawBytes(rawData);
  73. return messageTemplateBase.ToObject(rawData);
  74. //return this.Deserialize(rawData, templateLookup.GetMessageTemplateByRawBytes(rawData));
  75. }
  76. public virtual MessageTemplateBase Deserialize(byte[] rawData, MessageTemplateBase withTemplate)
  77. {
  78. this.debug_ConvertToObject_depth = 0;
  79. var debug = rawData.ToHexLogString();
  80. var safeEvent = this.Deserializing;
  81. safeEvent?.Invoke(this, new ParsingEventArg<byte[], MessageTemplateBase>() { From = rawData, To = default(MessageTemplateBase) });
  82. //MessageTemplateBase template = templateLookup.GetMessageTemplateByRawBytes(rawData);
  83. this.processingRawDataUsedInDeserialize = rawData;
  84. this.ConvertToObject(withTemplate, this.processingRawDataUsedInDeserialize);
  85. if (this.processingRawDataUsedInDeserialize.Length > 0)
  86. {
  87. if (!IgnoreExtraTailBytesInDeserialize)
  88. throw new FormatException("Unexpected Element Deserialize error occured(additional tail existed)");
  89. }
  90. #region if message level custom validation defined? then execute it
  91. Exception customValidationException;
  92. if ((customValidationException = withTemplate.Validate()) != null)
  93. {
  94. throw customValidationException;
  95. }
  96. #endregion
  97. var safeDeserializedEvent = this.Deserialized;
  98. safeDeserializedEvent?.Invoke(this, new DeserializeEventArg<byte[], MessageTemplateBase>() { From = rawData, Extra = this.processingRawDataUsedInDeserialize, To = withTemplate });
  99. return withTemplate;
  100. }
  101. public virtual byte[] Serialize(MessageTemplateBase message)
  102. {
  103. #region if message level custom validation defined? then execute it
  104. Exception customValidationException;
  105. if ((customValidationException = message?.Validate()) != null)
  106. {
  107. throw customValidationException;
  108. }
  109. #endregion
  110. var safeEvent = this.Serializing;
  111. safeEvent?.Invoke(this, new ParsingEventArg<MessageTemplateBase, byte[]>() { From = message, To = null });
  112. this.processingRawDataUsedInSerialize = new byte[0];
  113. this.positiveIndexAccu = 0;
  114. var content = this.ConvertToBytes(message);
  115. safeEvent = this.Serialized;
  116. safeEvent?.Invoke(this, new ParsingEventArg<MessageTemplateBase, byte[]>() { From = message, To = content });
  117. return content;
  118. }
  119. private void ConvertToObject(object elementInstance, byte[] rawData)
  120. {
  121. //if (++this.debug_ConvertToObject_depth >= 6) logger.Info("debug_ConvertToObject_depth is " + this.debug_ConvertToObject_depth + ": 0x" + rawData.ToHexLogString());
  122. if (rawData.Length == 0 || elementInstance == null)
  123. {
  124. return;
  125. }
  126. else
  127. {
  128. // Only processing Format related Attribute marked properties, and order by its Index
  129. var targetPropertyList = elementInstance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
  130. .Where(p => p.GetCustomAttributes(typeof(AttributeBase), true).Length > 0)
  131. .OrderBy(info => ((AttributeBase)(info.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index).ToList();
  132. foreach (var propertyInfo in targetPropertyList)
  133. {
  134. // normal plain Property
  135. if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length == 0)
  136. {
  137. int currentElementLength = -1;
  138. var format =
  139. (FormatAttribute)propertyInfo.GetCustomAttributes(typeof(FormatAttribute), true)[0];
  140. if (!string.IsNullOrEmpty(format.LengthOrCountLink))
  141. {
  142. currentElementLength = int.Parse(
  143. elementInstance.GetType().GetProperty(format.LengthOrCountLink).GetValue(elementInstance, null).ToString());
  144. if (!string.IsNullOrEmpty(format.LengthOrCountLinkExpression))
  145. {
  146. currentElementLength += ParseLengthOrCountLinkExpression(format.LengthOrCountLinkExpression);
  147. }
  148. }
  149. else
  150. {
  151. currentElementLength = format.FixedLengthOrCount;
  152. }
  153. // check if the remaining raw data bytes are enough to fill the current property
  154. if (this.processingRawDataUsedInDeserialize.Count() < currentElementLength)
  155. {
  156. throw new ArgumentException(
  157. string.Format("ConvertToObject Error: The remaining raw data bytes are insufficient to fill the current property '{0}'",
  158. propertyInfo.Name));
  159. }
  160. this.FillPropertyData(format.EncodingType, propertyInfo, elementInstance, this.processingRawDataUsedInDeserialize.Take(currentElementLength).ToArray());
  161. #region validate the range for Range Attribute marked field.
  162. // Validate the range, once failed, throw a exception
  163. var rangeValidator = propertyInfo.GetCustomAttributes(typeof(RangeAttribute), true).Length > 0
  164. ? propertyInfo.GetCustomAttributes(typeof(RangeAttribute), true)[0] : null;
  165. if (rangeValidator != null
  166. && !((RangeAttribute)rangeValidator).IsValid(propertyInfo.GetValue(elementInstance, null)))
  167. {
  168. throw new ArgumentException(((RangeAttribute)rangeValidator).ErrorMessage);
  169. }
  170. #endregion
  171. this.processingRawDataUsedInDeserialize = this.processingRawDataUsedInDeserialize.Skip(currentElementLength).ToArray();
  172. }
  173. // the IList Property
  174. else
  175. {
  176. bool isCascadedList = false;
  177. var listFormat =
  178. (EnumerableFormatAttribute)propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true)[0];
  179. int? listCount = null;
  180. if (!string.IsNullOrEmpty(listFormat.LengthOrCountLink) && listFormat.LengthOrCountLink.ToLower() == "%cascade")
  181. {
  182. // never know the end of the data, it's a flow, so set a big value.
  183. listCount = this.processingRawDataUsedInDeserialize.Length;
  184. listCount += ParseLengthOrCountLinkExpression(listFormat.LengthOrCountLinkExpression);
  185. isCascadedList = true;
  186. }
  187. else if (!string.IsNullOrEmpty(listFormat.LengthOrCountLink))
  188. {
  189. var linkPropertyValue = elementInstance.GetType().GetProperty(listFormat.LengthOrCountLink).GetValue(elementInstance, null);
  190. if (linkPropertyValue is Enum)
  191. {
  192. listCount = (int)linkPropertyValue;
  193. }
  194. else
  195. {
  196. listCount = int.Parse(linkPropertyValue.ToString());
  197. }
  198. if (!string.IsNullOrEmpty(listFormat.LengthOrCountLinkExpression))
  199. {
  200. listCount += ParseLengthOrCountLinkExpression(listFormat.LengthOrCountLinkExpression);
  201. }
  202. }
  203. else if (listFormat.FixedLengthOrCount > 0)
  204. {
  205. listCount = listFormat.FixedLengthOrCount;
  206. }
  207. else
  208. {
  209. throw new FormatException("Can't resolve the count number for IList property: " + propertyInfo.Name);
  210. }
  211. IList list = null;
  212. // since it's CascadedList, then probably there's no data anymore, it's a empty list, no need to do any parsing.
  213. if (this.processingRawDataUsedInDeserialize.Count() == 0 && isCascadedList)
  214. {
  215. }
  216. else
  217. {
  218. list = (IList)Activator.CreateInstance(propertyInfo.PropertyType);
  219. var genericArg = list.GetType().GetGenericArguments()[0];
  220. for (int i = 0; i < listCount; i++)
  221. {
  222. if (genericArg.IsPrimitive)
  223. {
  224. var subPrimitiveInstance = Activator.CreateInstance(genericArg);
  225. // assume primitive always take 1 byte, since we only use int and byte. need refine future.
  226. subPrimitiveInstance = this.processingRawDataUsedInDeserialize.Take(listFormat.ElementLength).ToArray().ToInt32();
  227. if (genericArg.IsAssignableFrom(typeof(Byte)))
  228. {
  229. list.Add(Convert.ToByte(subPrimitiveInstance));
  230. }
  231. else if (genericArg.IsAssignableFrom(typeof(int)))
  232. {
  233. list.Add(subPrimitiveInstance);
  234. }
  235. else
  236. {
  237. throw new ArgumentException("Now Only support primitive types of 'byte' and 'int'");
  238. }
  239. this.processingRawDataUsedInDeserialize = this.processingRawDataUsedInDeserialize.Skip(listFormat.ElementLength).ToArray();
  240. }
  241. else
  242. {
  243. var subElementInstance = Activator.CreateInstance(genericArg);
  244. list.Add(subElementInstance);
  245. this.ConvertToObject(subElementInstance, this.processingRawDataUsedInDeserialize);
  246. }
  247. // no data need to be processing, break it since we might set a big listCount earlier for cascade structure.
  248. // or you'll see a big List<T>
  249. if (this.processingRawDataUsedInDeserialize.Length == 0)
  250. {
  251. break;
  252. }
  253. }
  254. }
  255. propertyInfo.SetValue(elementInstance, list, null);
  256. }
  257. }
  258. }
  259. }
  260. /// <summary>
  261. /// parse the expression and return the operator value.
  262. /// </summary>
  263. /// <param name="expression"></param>
  264. /// <returns></returns>
  265. protected virtual int ParseLengthOrCountLinkExpression(string expression)
  266. {
  267. if (expression != null
  268. && (expression.Contains('+') || expression.Contains('-')))
  269. {
  270. var pureOperatorAndNumber = expression.Trim().Replace(" ", "");
  271. if (pureOperatorAndNumber.Contains('+'))
  272. {
  273. return int.Parse(pureOperatorAndNumber.Substring(1));
  274. }
  275. else if (pureOperatorAndNumber.Contains('-'))
  276. {
  277. return -int.Parse(pureOperatorAndNumber.Substring(1));
  278. }
  279. }
  280. return 0;
  281. }
  282. /// <summary>
  283. /// Set the value to a property in a specific object based on the encoding type associated with the property.
  284. /// </summary>
  285. /// <param name="encodingType"></param>
  286. /// <param name="propertyInfo"></param>
  287. /// <param name="elementInstance"></param>
  288. /// <param name="data"></param>
  289. private void FillPropertyData(EncodingType encodingType, PropertyInfo propertyInfo, object elementInstance, byte[] data)
  290. {
  291. switch (encodingType)
  292. {
  293. case EncodingType.ASCII:
  294. case EncodingType.ASCII_PadLeftWithZero:
  295. propertyInfo.SetValue(elementInstance, Encoding.ASCII.GetString(data), null);
  296. break;
  297. case EncodingType.ASCIIInt:
  298. propertyInfo.SetValue(elementInstance, int.Parse(Encoding.ASCII.GetString(data)), null);
  299. break;
  300. case EncodingType.ASCIILong:
  301. propertyInfo.SetValue(elementInstance, long.Parse(Encoding.ASCII.GetString(data)), null);
  302. break;
  303. case EncodingType.BCD:
  304. var tempBytes = data;
  305. var newValue = tempBytes.Length == 1 ? tempBytes[0].GetBCD() : tempBytes.GetBCD();
  306. if (propertyInfo.PropertyType.FullName == "System.Byte")
  307. {
  308. propertyInfo.SetValue(elementInstance, (byte)newValue, null);
  309. }
  310. else
  311. {
  312. propertyInfo.SetValue(elementInstance, newValue, null);
  313. }
  314. break;
  315. case EncodingType.BcdString:
  316. tempBytes = data;
  317. //if (propertyInfo.PropertyType.BaseType.FullName == "System.Enum") {
  318. // if (!Enum.IsDefined(propertyInfo.PropertyType, tempBytes.GetBCD()))
  319. // throw new ArgumentException("The given value is not a valid " + propertyInfo.PropertyType + " element.");
  320. //}
  321. var fieldValue = tempBytes.Length == 1 ? tempBytes[0].GetBCD().ToString() : tempBytes.GetBCDString();
  322. if (propertyInfo.PropertyType.IsAssignableFrom(typeof(long)))
  323. {
  324. propertyInfo.SetValue(elementInstance, long.Parse(fieldValue)
  325. , null);
  326. }
  327. else if (propertyInfo.PropertyType.IsAssignableFrom(typeof(DateTime)))
  328. {
  329. propertyInfo.SetValue(elementInstance, DateTime.ParseExact(fieldValue, "yyyyMMddHHmmss", System.Globalization.CultureInfo.InvariantCulture));
  330. }
  331. else if (propertyInfo.PropertyType.IsAssignableFrom(typeof(decimal)))
  332. {
  333. propertyInfo.SetValue(elementInstance, decimal.Parse(fieldValue)
  334. , null);
  335. }
  336. else
  337. {
  338. propertyInfo.SetValue(elementInstance, fieldValue
  339. , null);
  340. }
  341. break;
  342. case EncodingType.BIN:
  343. if (propertyInfo.PropertyType.IsAssignableFrom(typeof(Enum)))
  344. {
  345. var o = Enum.ToObject(propertyInfo.PropertyType, data.ToInt32());
  346. propertyInfo.SetValue(elementInstance, o, null);
  347. }
  348. else if (propertyInfo.PropertyType == typeof(Byte))
  349. {
  350. try
  351. {
  352. propertyInfo.SetValue(elementInstance, (byte)(data.ToInt32()), null);
  353. }
  354. catch (Exception e)
  355. {
  356. }
  357. }
  358. // in case the Property is a Nullable enum type.
  359. else if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
  360. {
  361. if (propertyInfo.PropertyType.GetGenericArguments()[0].IsEnum)
  362. {
  363. var o = Enum.ToObject(propertyInfo.PropertyType.GetGenericArguments()[0], data.ToInt32());
  364. propertyInfo.SetValue(elementInstance, o, null);
  365. }
  366. else
  367. {
  368. var value = Convert.ChangeType(data.ToInt32(), propertyInfo.PropertyType);
  369. propertyInfo.SetValue(elementInstance, value, null);
  370. }
  371. }
  372. else if (propertyInfo.PropertyType == typeof(ushort))
  373. {
  374. try
  375. {
  376. propertyInfo.SetValue(elementInstance, Convert.ToUInt16(data.ToInt32()));
  377. }
  378. catch (Exception ex)
  379. {
  380. throw;
  381. }
  382. }
  383. else
  384. {
  385. propertyInfo.SetValue(elementInstance, data.ToInt32(), null);
  386. }
  387. break;
  388. case EncodingType.HexString:
  389. propertyInfo.SetValue(elementInstance, ByteArrayToString(data));
  390. break;
  391. case EncodingType.ReverseHexString:
  392. propertyInfo.SetValue(elementInstance, ByteArrayToString(data.Reverse().ToArray()));
  393. break;
  394. }
  395. }
  396. /// <summary>
  397. ///
  398. /// </summary>
  399. /// <param name="encodingType"></param>
  400. /// <param name="propertyValue"></param>
  401. /// <param name="propertyValueFixedLength">if length is avarible bytes, then leave null, the serialized bytes will auto fits the actual value.</param>
  402. /// <param name="elementInstance"></param>
  403. /// <returns></returns>
  404. private byte[] SerializePropertyData(EncodingType encodingType, object propertyValue, int? propertyValueFixedLength, object elementInstance)
  405. {
  406. string enumTypeConvertedValue;
  407. switch (encodingType)
  408. {
  409. case EncodingType.ASCII:
  410. //if (propertyValue is Enum)
  411. //{
  412. // var temp = ((int)propertyValue).ToString();
  413. // enumTypeConvertedValue = temp.ToString().PadLeft(propertyValueFixedLength, '0');
  414. //}
  415. //else
  416. //{
  417. enumTypeConvertedValue = propertyValue.ToString();
  418. //}
  419. if (propertyValueFixedLength.HasValue && enumTypeConvertedValue.Length > propertyValueFixedLength.Value)
  420. throw new ArgumentOutOfRangeException("ASCII encoded property with value: " + enumTypeConvertedValue
  421. + " exceed its declared Fixedlength(" + propertyValueFixedLength + "), try shorten the value");
  422. if (propertyValueFixedLength.HasValue)
  423. return Encoding.ASCII.GetBytes(enumTypeConvertedValue.PadRight(propertyValueFixedLength.Value, ' '));
  424. else
  425. return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
  426. break;
  427. case EncodingType.ASCII_PadLeftWithZero:
  428. enumTypeConvertedValue = propertyValue.ToString();
  429. if (propertyValueFixedLength.HasValue && enumTypeConvertedValue.Length > propertyValueFixedLength.Value)
  430. throw new ArgumentOutOfRangeException("ASCII_PadLeftWithZero encoded property with value: " + enumTypeConvertedValue
  431. + " exceed its declared Fixedlength(" + propertyValueFixedLength + "), try shorten the value");
  432. if (propertyValueFixedLength.HasValue)
  433. return Encoding.ASCII.GetBytes(enumTypeConvertedValue.PadLeft(propertyValueFixedLength.Value, '0'));
  434. else
  435. return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
  436. break;
  437. case EncodingType.ASCIIInt:
  438. if (propertyValue is Enum)
  439. {
  440. var temp = ((int)propertyValue).ToString();
  441. enumTypeConvertedValue = temp.ToString().PadLeft(propertyValueFixedLength.Value, '0');
  442. }
  443. else
  444. {
  445. enumTypeConvertedValue = propertyValue.ToString().PadLeft(propertyValueFixedLength.Value, '0'); ;
  446. }
  447. return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
  448. break;
  449. case EncodingType.ASCIILong:
  450. if (propertyValue is Enum)
  451. {
  452. var temp = ((long)propertyValue).ToString();
  453. enumTypeConvertedValue = temp.ToString().PadLeft(propertyValueFixedLength.Value, '0');
  454. }
  455. else
  456. {
  457. enumTypeConvertedValue = propertyValue.ToString().PadLeft(propertyValueFixedLength.Value, '0'); ;
  458. }
  459. return Encoding.ASCII.GetBytes(enumTypeConvertedValue);
  460. break;
  461. case EncodingType.BCD:
  462. if (propertyValue is Enum)
  463. {
  464. enumTypeConvertedValue = ((int)propertyValue).ToString();
  465. }
  466. else
  467. {
  468. enumTypeConvertedValue = propertyValue.ToString();
  469. }
  470. return long.Parse(enumTypeConvertedValue).GetBCDBytes(propertyValueFixedLength.Value);
  471. break;
  472. case EncodingType.BcdString:
  473. if (propertyValue is Enum)
  474. {
  475. enumTypeConvertedValue = ((int)propertyValue).ToString();
  476. }
  477. else
  478. {
  479. enumTypeConvertedValue = propertyValue.ToString();
  480. }
  481. return System.Numerics.BigInteger.Parse(enumTypeConvertedValue).GetBCDBytes(propertyValueFixedLength.Value);
  482. break;
  483. case EncodingType.BIN:
  484. if (propertyValue is Enum)
  485. {
  486. enumTypeConvertedValue = ((int)propertyValue).ToString();
  487. }
  488. else
  489. {
  490. enumTypeConvertedValue = propertyValue.ToString();
  491. }
  492. if (propertyValueFixedLength.HasValue)
  493. {
  494. if (long.Parse(enumTypeConvertedValue) > int.MaxValue)
  495. {
  496. return long.Parse(enumTypeConvertedValue).GetBinBytes(propertyValueFixedLength.Value);
  497. }
  498. else
  499. {
  500. return (int.Parse(enumTypeConvertedValue)).GetBinBytes(propertyValueFixedLength.Value);
  501. }
  502. }
  503. else
  504. {
  505. logger.Info($"Parsing value: {enumTypeConvertedValue}");
  506. return (int.Parse(enumTypeConvertedValue)).GetBinBytes(1);
  507. }
  508. break;
  509. case EncodingType.HexString:
  510. return StringToByteArray(propertyValue.ToString());
  511. break;
  512. }
  513. return null;
  514. }
  515. public static byte[] StringToByteArray(string hex)
  516. {
  517. int numberChars = hex.Length;
  518. byte[] bytes = new byte[numberChars / 2];
  519. for (int i = 0; i < numberChars; i += 2)
  520. bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  521. return bytes;
  522. }
  523. public static string ByteArrayToString(byte[] arr)
  524. {
  525. return BitConverter.ToString(arr).Replace("-", "");
  526. }
  527. static bool IsNullable<T>(T obj)
  528. {
  529. if (obj == null) return true; // obvious
  530. Type type = typeof(T);
  531. if (!type.IsValueType) return true; // ref-type
  532. if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
  533. return false; // value-type
  534. }
  535. private byte[] ConvertToBytes(object elementInstance)
  536. {
  537. // Only processing MessageAttribute marked properties, and order by its Index in DESC, like 10, 9, 8, 7.... -1, -2...-10.
  538. var targetPropertyList = elementInstance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
  539. .Where(p => p.GetCustomAttributes(typeof(AttributeBase), true).Length > 0)
  540. .OrderByDescending(info => ((AttributeBase)(info.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index);
  541. foreach (var propertyInfo in targetPropertyList)
  542. {
  543. // normal Format Property
  544. if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length == 0
  545. && propertyInfo.GetCustomAttributes(typeof(FormatAttribute), true).Length > 0)
  546. {
  547. #region validate the Range Attibute marked property, if existed and failed to pass, throw exception
  548. // Validate the range, once failed, throw a exception
  549. var propertyRangeAttributes = propertyInfo.GetCustomAttributes(typeof(RangeAttribute), true);
  550. if (propertyRangeAttributes.Length > 0)
  551. {
  552. var rangeAttribute = (RangeAttribute)propertyRangeAttributes[0];
  553. if (rangeAttribute != null)
  554. {
  555. if (!rangeAttribute.IsValid(propertyInfo.GetValue(elementInstance, null)))
  556. {
  557. throw new ArgumentException(rangeAttribute.ErrorMessage);
  558. }
  559. }
  560. }
  561. #endregion
  562. var format =
  563. (FormatAttribute)propertyInfo.GetCustomAttributes(typeof(FormatAttribute), true)[0];
  564. if (format.LengthOrCountLinkExpression != null
  565. && format.LengthOrCountLinkExpression.Contains("%OnSerializingBytesCount")
  566. )
  567. {
  568. var lengthValue = this.processingRawDataUsedInSerialize.Length;
  569. // if this property's value is default with null (most likely the nullable int), then will not set any value on it, the serializing
  570. // result will omit this property.
  571. if (lengthValue == 0 && propertyInfo.GetValue(elementInstance, null) == null)
  572. {
  573. }
  574. else
  575. propertyInfo.SetValue(elementInstance, this.processingRawDataUsedInSerialize.Length, null);
  576. }
  577. else if (!string.IsNullOrEmpty(format.LengthOrCountLink))
  578. {
  579. var lengthLinkProperty = targetPropertyList.FirstOrDefault(t => t.Name == format.LengthOrCountLink);
  580. if (lengthLinkProperty != null)
  581. {
  582. propertyInfo.SetValue(elementInstance, propertyInfo.GetValue(elementInstance, null).ToString().Length, null);
  583. }
  584. }
  585. var propertyValue = propertyInfo.GetValue(elementInstance, null);
  586. // if this property value is null, then indicate it's a optional field, then skip to parsing it.
  587. if (propertyValue == null)
  588. {
  589. var safeEvent = this.FieldSerializing;
  590. safeEvent?.Invoke(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = null });
  591. continue;
  592. }
  593. var safe = this.FieldSerializing;
  594. safe?.Invoke(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = null });
  595. byte[] currentPropertyBytes = this.SerializePropertyData(format.EncodingType,
  596. propertyValue, format.FixedLengthOrCount > 0 ? format.FixedLengthOrCount : default(int?),
  597. elementInstance);
  598. safe = this.FieldSerialized;
  599. safe?.Invoke(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = currentPropertyBytes });
  600. this.processingRawDataUsedInSerialize = this.processingRawDataUsedInSerialize.AppendToHeader(currentPropertyBytes);
  601. if (format.Index >= 0) this.positiveIndexAccu += currentPropertyBytes.Length;
  602. // try fill the LengthLink field.
  603. //if (!string.IsNullOrEmpty(format.LengthOrCountLink))
  604. //{
  605. // var lengthLinkProperty = targetPropertyList.FirstOrDefault(t => t.Name == format.LengthOrCountLink);
  606. // if (lengthLinkProperty != null
  607. // && string.IsNullOrEmpty(format.LengthOrCountLinkExpression)
  608. // // if it's not a LengthOrCountLinkExpression field, then the linking field is directly reflected the length, so it must euqal 0
  609. // // if not, means the users code set the length explicitly, accept this behavior to NOT overwirte it.
  610. // && int.Parse(lengthLinkProperty.GetValue(elementInstance, null).ToString()) == 0)
  611. // {
  612. // lengthLinkProperty.SetValue(elementInstance, currentPropertyBytes.Length, null);
  613. // }
  614. // else if (false && !string.IsNullOrEmpty(format.LengthOrCountLinkExpression))
  615. // {
  616. // var pair = format.LengthOrCountLinkExpression.Split(';');
  617. // bool found = false;
  618. // foreach (string p in pair)
  619. // {
  620. // var mappings = p.Split(':');
  621. // if (currentPropertyBytes.Length == int.Parse(mappings[1]))
  622. // {
  623. // //lengthLinkProperty.SetValue(elementInstance, int.Parse(mappings[0], null));
  624. // lengthLinkProperty.SetValue(elementInstance, int.Parse(mappings[0], null), null);
  625. // found = true;
  626. // break;
  627. // }
  628. // }
  629. // if (!found)
  630. // { throw new ArgumentException("Serializing LengthOrCountLinkExpression: " + format.LengthOrCountLinkExpression + " didn't found match values in link field: " + format.LengthOrCountLink); }
  631. // }
  632. //}
  633. }
  634. // the Enumerable Property
  635. else if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length > 0)
  636. {
  637. var enumerableFormat =
  638. (EnumerableFormatAttribute)propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true)[0];
  639. // for now, we only support List<T>
  640. if (propertyInfo.PropertyType.IsAssignableFrom(typeof(IList)))
  641. {
  642. throw new ArgumentException("For now, we only support Enumerable of List<T>");
  643. }
  644. var list = (IList)propertyInfo.GetValue(elementInstance, null);
  645. if (list != null)
  646. {
  647. if (enumerableFormat.FixedLengthOrCount > 0)
  648. {
  649. if (enumerableFormat.FixedLengthOrCount != list.Count)
  650. {
  651. throw new ArgumentException("The current count(" + list.Count + ") of EnumerableFormat property: "
  652. + propertyInfo.Name + " must match its declared FixedLengthOrCount(" + enumerableFormat.FixedLengthOrCount + ")");
  653. }
  654. }
  655. if (list.GetType().GetGenericArguments()[0].IsPrimitive)
  656. {
  657. var safe = this.FieldSerializing;
  658. if (safe != null) { safe(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = null }); }
  659. byte[] primitiveSerilaizedBytes = new byte[0];
  660. for (int i = list.Count - 1; i >= 0; i--)
  661. {
  662. primitiveSerilaizedBytes = primitiveSerilaizedBytes.AppendToHeader(
  663. this.SerializePropertyData(
  664. enumerableFormat.EncodingType,
  665. list[i],
  666. 1,
  667. elementInstance)
  668. );
  669. }
  670. this.processingRawDataUsedInSerialize = this.processingRawDataUsedInSerialize.AppendToHeader(primitiveSerilaizedBytes);
  671. if (enumerableFormat.Index >= 0) this.positiveIndexAccu += primitiveSerilaizedBytes.Length;
  672. safe = this.FieldSerialized;
  673. if (safe != null) { safe(this, new ParsingEventArg<string, byte[]>() { From = propertyInfo.Name, To = primitiveSerilaizedBytes }); }
  674. }
  675. else
  676. {
  677. // reverse
  678. for (int i = list.Count - 1; i >= 0; i--)
  679. {
  680. this.processingRawDataUsedInSerialize.AppendToHeader(this.ConvertToBytes(list[i]));
  681. }
  682. }
  683. if (!string.IsNullOrEmpty(enumerableFormat.LengthOrCountLink) && enumerableFormat.LengthOrCountLink.ToLower() != "%cascade")
  684. {
  685. var countLinkProperty = targetPropertyList.FirstOrDefault(t => t.Name == enumerableFormat.LengthOrCountLink);
  686. if (countLinkProperty != null)
  687. {
  688. try
  689. {
  690. countLinkProperty.SetValue(elementInstance, list.Count, null);
  691. }
  692. catch (Exception ex)
  693. {
  694. }
  695. }
  696. }
  697. else
  698. {
  699. if (!string.IsNullOrEmpty(enumerableFormat.LengthOrCountLink) && enumerableFormat.LengthOrCountLink.ToLower() != "%cascade")
  700. {
  701. targetPropertyList.First(t => t.Name == enumerableFormat.LengthOrCountLink).
  702. SetValue(elementInstance, 0, null);
  703. }
  704. }
  705. }
  706. }
  707. }
  708. return this.processingRawDataUsedInSerialize;
  709. }
  710. // make sure the Indexes property of one element including the sub elements
  711. // of its list structures are sequential and unique
  712. protected void CheckAttributeIndexes(object elementInstance)
  713. {
  714. if (elementInstance == null)
  715. {
  716. return;
  717. }
  718. // Only processing WayneAttribute marked properties, and order by its Index in DESC
  719. var targetPropertyList = elementInstance.GetType()
  720. .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
  721. .Where(p => p.GetCustomAttributes(typeof(AttributeBase), true).Length > 0)
  722. .OrderByDescending(
  723. info =>
  724. ((AttributeBase)(info.GetCustomAttributes(typeof(AttributeBase), true)[0]))
  725. .Index);
  726. // make sure no duplicated Index defined.
  727. targetPropertyList.GroupBy(
  728. p => ((AttributeBase)(p.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index)
  729. .ToList()
  730. .ForEach(g =>
  731. {
  732. if (g.Count() > 1)
  733. {
  734. throw new ArgumentException(
  735. string.Format("Duplicated Index: {0} defined, make sure the Index value unique", g.Key));
  736. }
  737. });
  738. // make sure the Indexes are contiguously sequential and count from 0
  739. if (targetPropertyList.Max(
  740. p => ((AttributeBase)(p.GetCustomAttributes(typeof(AttributeBase), true)[0])).Index) !=
  741. (targetPropertyList.Count() - 1))
  742. {
  743. throw new ArgumentException(string.Format("Index must be in sequential and count from 0"));
  744. }
  745. // recursively check the sub element type of ListPropertyFormatAttribute
  746. foreach (var propertyInfo in targetPropertyList)
  747. {
  748. // the IList Property
  749. if (propertyInfo.GetCustomAttributes(typeof(EnumerableFormatAttribute), true).Length != 0)
  750. {
  751. var subElementInstance = Activator.CreateInstance(propertyInfo.PropertyType.GetGenericArguments()[0]);
  752. CheckAttributeIndexes(subElementInstance);
  753. }
  754. }
  755. }
  756. }
  757. }