ParserBase.cs 42 KB

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