Decode.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. #region Copyright
  2. ////////////////////////////////////////////////////////////////////////////////
  3. // The following FIT Protocol software provided may be used with FIT protocol
  4. // devices only and remains the copyrighted property of Dynastream Innovations Inc.
  5. // The software is being provided on an "as-is" basis and as an accommodation,
  6. // and therefore all warranties, representations, or guarantees of any kind
  7. // (whether express, implied or statutory) including, without limitation,
  8. // warranties of merchantability, non-infringement, or fitness for a particular
  9. // purpose, are specifically disclaimed.
  10. //
  11. // Copyright 2016 Dynastream Innovations Inc.
  12. ////////////////////////////////////////////////////////////////////////////////
  13. // ****WARNING**** This file is auto-generated! Do NOT edit this file.
  14. // Profile Version = 16.60Release
  15. // Tag = production-akw-16.60.00-0-g5d3d436
  16. ////////////////////////////////////////////////////////////////////////////////
  17. #endregion
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Text;
  22. using System.IO;
  23. using System.Threading;
  24. namespace Dynastream.Fit
  25. {
  26. /// <summary>
  27. /// This class will decode a .fit file reading the file header and any definition or data messages.
  28. /// </summary>
  29. public class Decode
  30. {
  31. private const long CRCSIZE = 2;
  32. private const uint INVALID_DATA_SIZE = 0;
  33. #region Fields
  34. private MesgDefinition[] localMesgDefs = new MesgDefinition[Fit.MaxLocalMesgs];
  35. private Header fileHeader;
  36. private uint timestamp = 0;
  37. private int lastTimeOffset = 0;
  38. private bool invalidDataSize = false;
  39. private Accumulator accumulator = new Accumulator();
  40. #endregion
  41. #region Properties
  42. public bool InvalidDataSize
  43. {
  44. get
  45. {
  46. return invalidDataSize;
  47. }
  48. set
  49. {
  50. invalidDataSize = value;
  51. }
  52. }
  53. #endregion
  54. #region Constructors
  55. public Decode()
  56. {
  57. }
  58. #endregion
  59. #region Methods
  60. public event MesgEventHandler MesgEvent;
  61. public event MesgDefinitionEventHandler MesgDefinitionEvent;
  62. /// <summary>
  63. /// Reads the file header to check if the file is FIT.
  64. /// Does not check CRC.
  65. /// Returns true if file is FIT.
  66. /// </summary>
  67. /// <param name="fitStream"> Seekable (file)stream to parse</param>
  68. public bool IsFIT(Stream fitStream)
  69. {
  70. long position = fitStream.Position;
  71. bool status = false;
  72. try
  73. {
  74. // Does the header contain the flag string ".FIT"?
  75. Header header = new Header(fitStream);
  76. fitStream.Position = position;
  77. status = header.IsValid();
  78. }
  79. // If the header is malformed the ctor could throw an exception
  80. catch (FitException)
  81. {
  82. }
  83. fitStream.Position = position;
  84. return status;
  85. }
  86. /// <summary>
  87. /// Reads the FIT binary file header and crc to check compatibility and integrity.
  88. /// Also checks data reords size.
  89. /// Returns true if file is ok (not corrupt).
  90. ///</summary>
  91. /// <param name="fitStream">Seekable (file)stream to parse.</param>
  92. public bool CheckIntegrity(Stream fitStream)
  93. {
  94. bool isValid = true;
  95. long position = fitStream.Position;
  96. long fileSize = 0;
  97. try
  98. {
  99. while ((fitStream.Position < fitStream.Length) && (isValid == true))
  100. {
  101. // Is there a valid header?
  102. Header header = new Header(fitStream);
  103. isValid = header.IsValid();
  104. // Get the file size from the header
  105. // When the data size is 0 set flags, don't calculate CRC
  106. if (header.DataSize > INVALID_DATA_SIZE)
  107. {
  108. fileSize = header.Size + header.DataSize + CRCSIZE;
  109. // Is the file CRC ok?
  110. // Need to rewind the header size because the header is part of the CRC calculation.
  111. byte[] data = new byte[fileSize];
  112. fitStream.Position = fitStream.Position - header.Size;
  113. fitStream.Read(data, 0, data.Length);
  114. isValid &= (CRC.Calc16(data, data.Length) == 0x0000);
  115. }
  116. else
  117. {
  118. invalidDataSize = true;
  119. isValid = false;
  120. }
  121. }
  122. }
  123. catch (FitException)
  124. {
  125. isValid = false;
  126. }
  127. fitStream.Position = position;
  128. return isValid;
  129. }
  130. /// <summary>
  131. /// Reads a FIT binary file.
  132. /// </summary>
  133. /// <param name="fitStream">Seekable (file)stream to parse.</param>
  134. /// <returns>
  135. /// Returns true if reading finishes successfully.
  136. /// </returns>
  137. public bool Read(Stream fitStream)
  138. {
  139. bool status = true;
  140. while ((fitStream.Position < fitStream.Length) && (status == true))
  141. {
  142. status = Read(fitStream, false);
  143. }
  144. return status;
  145. }
  146. /// <summary>
  147. /// Reads a FIT binary file.
  148. /// </summary>
  149. /// <param name="fitStream">Seekable (file)stream to parse.</param>
  150. /// <param name="skipHeader">When true, skip file header. Also CRC will not be calculated.</param>
  151. /// <returns>
  152. /// Returns true if reading finishes successfully.
  153. /// </returns>
  154. public bool Read(Stream fitStream, bool skipHeader)
  155. {
  156. bool readOK = true;
  157. long fileSize = 0;
  158. long filePosition = fitStream.Position;
  159. try
  160. {
  161. // Attempt to read header
  162. if (!skipHeader)
  163. {
  164. fileHeader = new Header(fitStream);
  165. readOK &= fileHeader.IsValid();
  166. // Get the file size from the header
  167. // When the data size is invalid set the file size to the fitstream length
  168. if (!invalidDataSize)
  169. {
  170. fileSize = fileHeader.Size + fileHeader.DataSize + CRCSIZE;
  171. }
  172. else
  173. {
  174. fileSize = fitStream.Length;
  175. }
  176. if (!readOK)
  177. {
  178. throw new FitException("FIT decode error: File is not FIT format. Check file header data type. Error at stream position: " + fitStream.Position);
  179. }
  180. if ((fileHeader.ProtocolVersion & Fit.ProtocolVersionMajorMask) > (Fit.ProtocolMajorVersion << Fit.ProtocolVersionMajorShift))
  181. {
  182. // The decoder does not support decode accross protocol major revisions
  183. throw new FitException(String.Format("FIT decode error: Protocol Version {0}.X not supported by SDK Protocol Ver{1}.{2} ", (fileHeader.ProtocolVersion & Fit.ProtocolVersionMajorMask) >> Fit.ProtocolVersionMajorShift, Fit.ProtocolMajorVersion, Fit.ProtocolMinorVersion));
  184. }
  185. }
  186. else
  187. {
  188. // When skipping the header force the stream position to be at the beginning of the file
  189. // Also the fileSize is the length of the filestream.
  190. fitStream.Position = 0;
  191. fileSize = fitStream.Length;
  192. }
  193. // Read data messages and definitions
  194. while (fitStream.Position < (filePosition + fileSize - CRCSIZE))
  195. {
  196. DecodeNextMessage(fitStream);
  197. }
  198. // Is the file CRC ok?
  199. if (!skipHeader && !invalidDataSize)
  200. {
  201. byte[] data = new byte[fileSize];
  202. fitStream.Position = filePosition;
  203. fitStream.Read(data, 0, data.Length);
  204. readOK &= (CRC.Calc16(data, data.Length) == 0x0000);
  205. fitStream.Position = filePosition + fileSize;
  206. }
  207. }
  208. catch (EndOfStreamException e)
  209. {
  210. readOK = false;
  211. // Debug.Log(string.Format("{0} caught and ignored. ", e.GetType().Name));
  212. throw new FitException("Decode:Read - Unexpected End of File at stream position" + fitStream.Position, e);
  213. }
  214. catch (FitException e)
  215. {
  216. // When attempting to decode files with invalid data size this indicates the EOF.
  217. if (!invalidDataSize)
  218. {
  219. throw e;
  220. }
  221. }
  222. return readOK;
  223. }
  224. public void DecodeNextMessage(Stream fitStream)
  225. {
  226. BinaryReader br = new BinaryReader(fitStream);
  227. byte nextByte = br.ReadByte();
  228. // Is it a compressed timestamp mesg?
  229. if ((nextByte & Fit.CompressedHeaderMask) == Fit.CompressedHeaderMask)
  230. {
  231. MemoryStream mesgBuffer = new MemoryStream();
  232. int timeOffset = nextByte & Fit.CompressedTimeMask;
  233. timestamp += (uint)((timeOffset - lastTimeOffset) & Fit.CompressedTimeMask);
  234. lastTimeOffset = timeOffset;
  235. Field timestampField = new Field(Profile.GetMesg(MesgNum.Record).GetField("Timestamp"));
  236. timestampField.SetValue(timestamp);
  237. byte localMesgNum = (byte)((nextByte & Fit.CompressedLocalMesgNumMask) >> 5);
  238. mesgBuffer.WriteByte(localMesgNum);
  239. if (localMesgDefs[localMesgNum] == null)
  240. {
  241. throw new FitException("Decode:DecodeNextMessage - FIT decode error: Missing message definition for local message number " + localMesgNum + " at stream position " + fitStream.Position);
  242. }
  243. int fieldsSize = localMesgDefs[localMesgNum].GetMesgSize() - 1;
  244. try
  245. {
  246. mesgBuffer.Write(br.ReadBytes(fieldsSize), 0, fieldsSize);
  247. }
  248. catch (IOException e)
  249. {
  250. throw new FitException("Decode:DecodeNextMessage - Compressed Data Message unexpected end of file. Wanted " + fieldsSize + " bytes at stream position " + fitStream.Position, e);
  251. }
  252. Mesg newMesg = new Mesg(mesgBuffer, localMesgDefs[localMesgNum]);
  253. newMesg.InsertField(0, timestampField);
  254. if (MesgEvent != null)
  255. {
  256. MesgEvent(this, new MesgEventArgs(newMesg));
  257. }
  258. }
  259. // Is it a mesg def?
  260. else if ((nextByte & Fit.HeaderTypeMask) == Fit.MesgDefinitionMask)
  261. {
  262. MemoryStream mesgDefBuffer = new MemoryStream();
  263. // Figure out number of fields (length) of our defn and build buffer
  264. mesgDefBuffer.WriteByte(nextByte);
  265. mesgDefBuffer.Write(br.ReadBytes(4), 0, 4);
  266. byte numfields = br.ReadByte();
  267. mesgDefBuffer.WriteByte(numfields);
  268. try
  269. {
  270. mesgDefBuffer.Write(br.ReadBytes(numfields * 3), 0, numfields * 3);
  271. }
  272. catch (IOException e)
  273. {
  274. throw new FitException("Decode:DecodeNextMessage - Defn Message unexpected end of file. Wanted " + (numfields * 3) + " bytes at stream position " + fitStream.Position, e);
  275. }
  276. MesgDefinition newMesgDef = new MesgDefinition(mesgDefBuffer);
  277. localMesgDefs[newMesgDef.LocalMesgNum] = newMesgDef;
  278. if (MesgDefinitionEvent != null)
  279. {
  280. MesgDefinitionEvent(this, new MesgDefinitionEventArgs(newMesgDef));
  281. }
  282. }
  283. // Is it a data mesg?
  284. else if ((nextByte & Fit.HeaderTypeMask) == Fit.MesgHeaderMask)
  285. {
  286. MemoryStream mesgBuffer = new MemoryStream();
  287. byte localMesgNum = (byte)(nextByte & Fit.LocalMesgNumMask);
  288. mesgBuffer.WriteByte(localMesgNum);
  289. if (localMesgDefs[localMesgNum] == null)
  290. {
  291. throw new FitException("Decode:DecodeNextMessage - FIT decode error: Missing message definition for local message number " + localMesgNum + " at stream position " + fitStream.Position);
  292. }
  293. int fieldsSize = localMesgDefs[localMesgNum].GetMesgSize() - 1;
  294. try
  295. {
  296. mesgBuffer.Write(br.ReadBytes(fieldsSize), 0, fieldsSize);
  297. }
  298. catch (Exception e)
  299. {
  300. throw new FitException("Decode:DecodeNextMessage - Data Message unexpected end of file. Wanted " + fieldsSize + " bytes at stream position " + fitStream.Position, e);
  301. }
  302. Mesg newMesg = new Mesg(mesgBuffer, localMesgDefs[localMesgNum]);
  303. // If the new message contains a timestamp field, record the value to use as
  304. // a reference for compressed timestamp headers
  305. Field timestampField = newMesg.GetField("Timestamp");
  306. if (timestampField != null)
  307. {
  308. object tsValue = timestampField.GetValue();
  309. if (tsValue != null)
  310. {
  311. timestamp = (uint)tsValue;
  312. lastTimeOffset = (int)timestamp & Fit.CompressedTimeMask;
  313. }
  314. }
  315. foreach (Field field in newMesg.fields)
  316. {
  317. if (field.IsAccumulated)
  318. {
  319. int i;
  320. for (i = 0; i < field.GetNumValues(); i++)
  321. {
  322. long value = Convert.ToInt64(field.GetRawValue(i));
  323. accumulator.Set(newMesg.Num, field.Num, value);
  324. }
  325. }
  326. }
  327. // Now that the entire message is decoded we can evaluate subfields and expand any components
  328. newMesg.ExpandComponents(accumulator);
  329. if (MesgEvent != null)
  330. {
  331. MesgEvent(this, new MesgEventArgs(newMesg));
  332. }
  333. }
  334. else
  335. {
  336. throw new FitException("Decode:Read - FIT decode error: Unexpected Record Header Byte 0x" + nextByte.ToString("X") + " at stream position: " + fitStream.Position);
  337. }
  338. }
  339. #endregion
  340. } // class
  341. } // namespace