ParserBase.cs 42 KB

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