#region Copyright //////////////////////////////////////////////////////////////////////////////// // The following FIT Protocol software provided may be used with FIT protocol // devices only and remains the copyrighted property of Dynastream Innovations Inc. // The software is being provided on an "as-is" basis and as an accommodation, // and therefore all warranties, representations, or guarantees of any kind // (whether express, implied or statutory) including, without limitation, // warranties of merchantability, non-infringement, or fitness for a particular // purpose, are specifically disclaimed. // // Copyright 2016 Dynastream Innovations Inc. //////////////////////////////////////////////////////////////////////////////// // ****WARNING**** This file is auto-generated! Do NOT edit this file. // Profile Version = 16.60Release // Tag = production-akw-16.60.00-0-g5d3d436 //////////////////////////////////////////////////////////////////////////////// #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.IO; using System.Threading; namespace Dynastream.Fit { /// /// This class will decode a .fit file reading the file header and any definition or data messages. /// public class Decode { private const long CRCSIZE = 2; private const uint INVALID_DATA_SIZE = 0; #region Fields private MesgDefinition[] localMesgDefs = new MesgDefinition[Fit.MaxLocalMesgs]; private Header fileHeader; private uint timestamp = 0; private int lastTimeOffset = 0; private bool invalidDataSize = false; private Accumulator accumulator = new Accumulator(); #endregion #region Properties public bool InvalidDataSize { get { return invalidDataSize; } set { invalidDataSize = value; } } #endregion #region Constructors public Decode() { } #endregion #region Methods public event MesgEventHandler MesgEvent; public event MesgDefinitionEventHandler MesgDefinitionEvent; /// /// Reads the file header to check if the file is FIT. /// Does not check CRC. /// Returns true if file is FIT. /// /// Seekable (file)stream to parse public bool IsFIT(Stream fitStream) { long position = fitStream.Position; bool status = false; try { // Does the header contain the flag string ".FIT"? Header header = new Header(fitStream); fitStream.Position = position; status = header.IsValid(); } // If the header is malformed the ctor could throw an exception catch (FitException) { } fitStream.Position = position; return status; } /// /// Reads the FIT binary file header and crc to check compatibility and integrity. /// Also checks data reords size. /// Returns true if file is ok (not corrupt). /// /// Seekable (file)stream to parse. public bool CheckIntegrity(Stream fitStream) { bool isValid = true; long position = fitStream.Position; long fileSize = 0; try { while ((fitStream.Position < fitStream.Length) && (isValid == true)) { // Is there a valid header? Header header = new Header(fitStream); isValid = header.IsValid(); // Get the file size from the header // When the data size is 0 set flags, don't calculate CRC if (header.DataSize > INVALID_DATA_SIZE) { fileSize = header.Size + header.DataSize + CRCSIZE; // Is the file CRC ok? // Need to rewind the header size because the header is part of the CRC calculation. byte[] data = new byte[fileSize]; fitStream.Position = fitStream.Position - header.Size; fitStream.Read(data, 0, data.Length); isValid &= (CRC.Calc16(data, data.Length) == 0x0000); } else { invalidDataSize = true; isValid = false; } } } catch (FitException) { isValid = false; } fitStream.Position = position; return isValid; } /// /// Reads a FIT binary file. /// /// Seekable (file)stream to parse. /// /// Returns true if reading finishes successfully. /// public bool Read(Stream fitStream) { bool status = true; while ((fitStream.Position < fitStream.Length) && (status == true)) { status = Read(fitStream, false); } return status; } /// /// Reads a FIT binary file. /// /// Seekable (file)stream to parse. /// When true, skip file header. Also CRC will not be calculated. /// /// Returns true if reading finishes successfully. /// public bool Read(Stream fitStream, bool skipHeader) { bool readOK = true; long fileSize = 0; long filePosition = fitStream.Position; try { // Attempt to read header if (!skipHeader) { fileHeader = new Header(fitStream); readOK &= fileHeader.IsValid(); // Get the file size from the header // When the data size is invalid set the file size to the fitstream length if (!invalidDataSize) { fileSize = fileHeader.Size + fileHeader.DataSize + CRCSIZE; } else { fileSize = fitStream.Length; } if (!readOK) { throw new FitException("FIT decode error: File is not FIT format. Check file header data type. Error at stream position: " + fitStream.Position); } if ((fileHeader.ProtocolVersion & Fit.ProtocolVersionMajorMask) > (Fit.ProtocolMajorVersion << Fit.ProtocolVersionMajorShift)) { // The decoder does not support decode accross protocol major revisions 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)); } } else { // When skipping the header force the stream position to be at the beginning of the file // Also the fileSize is the length of the filestream. fitStream.Position = 0; fileSize = fitStream.Length; } // Read data messages and definitions while (fitStream.Position < (filePosition + fileSize - CRCSIZE)) { DecodeNextMessage(fitStream); } // Is the file CRC ok? if (!skipHeader && !invalidDataSize) { byte[] data = new byte[fileSize]; fitStream.Position = filePosition; fitStream.Read(data, 0, data.Length); readOK &= (CRC.Calc16(data, data.Length) == 0x0000); fitStream.Position = filePosition + fileSize; } } catch (EndOfStreamException e) { readOK = false; // Debug.Log(string.Format("{0} caught and ignored. ", e.GetType().Name)); throw new FitException("Decode:Read - Unexpected End of File at stream position" + fitStream.Position, e); } catch (FitException e) { // When attempting to decode files with invalid data size this indicates the EOF. if (!invalidDataSize) { throw e; } } return readOK; } public void DecodeNextMessage(Stream fitStream) { BinaryReader br = new BinaryReader(fitStream); byte nextByte = br.ReadByte(); // Is it a compressed timestamp mesg? if ((nextByte & Fit.CompressedHeaderMask) == Fit.CompressedHeaderMask) { MemoryStream mesgBuffer = new MemoryStream(); int timeOffset = nextByte & Fit.CompressedTimeMask; timestamp += (uint)((timeOffset - lastTimeOffset) & Fit.CompressedTimeMask); lastTimeOffset = timeOffset; Field timestampField = new Field(Profile.GetMesg(MesgNum.Record).GetField("Timestamp")); timestampField.SetValue(timestamp); byte localMesgNum = (byte)((nextByte & Fit.CompressedLocalMesgNumMask) >> 5); mesgBuffer.WriteByte(localMesgNum); if (localMesgDefs[localMesgNum] == null) { throw new FitException("Decode:DecodeNextMessage - FIT decode error: Missing message definition for local message number " + localMesgNum + " at stream position " + fitStream.Position); } int fieldsSize = localMesgDefs[localMesgNum].GetMesgSize() - 1; try { mesgBuffer.Write(br.ReadBytes(fieldsSize), 0, fieldsSize); } catch (IOException e) { throw new FitException("Decode:DecodeNextMessage - Compressed Data Message unexpected end of file. Wanted " + fieldsSize + " bytes at stream position " + fitStream.Position, e); } Mesg newMesg = new Mesg(mesgBuffer, localMesgDefs[localMesgNum]); newMesg.InsertField(0, timestampField); if (MesgEvent != null) { MesgEvent(this, new MesgEventArgs(newMesg)); } } // Is it a mesg def? else if ((nextByte & Fit.HeaderTypeMask) == Fit.MesgDefinitionMask) { MemoryStream mesgDefBuffer = new MemoryStream(); // Figure out number of fields (length) of our defn and build buffer mesgDefBuffer.WriteByte(nextByte); mesgDefBuffer.Write(br.ReadBytes(4), 0, 4); byte numfields = br.ReadByte(); mesgDefBuffer.WriteByte(numfields); try { mesgDefBuffer.Write(br.ReadBytes(numfields * 3), 0, numfields * 3); } catch (IOException e) { throw new FitException("Decode:DecodeNextMessage - Defn Message unexpected end of file. Wanted " + (numfields * 3) + " bytes at stream position " + fitStream.Position, e); } MesgDefinition newMesgDef = new MesgDefinition(mesgDefBuffer); localMesgDefs[newMesgDef.LocalMesgNum] = newMesgDef; if (MesgDefinitionEvent != null) { MesgDefinitionEvent(this, new MesgDefinitionEventArgs(newMesgDef)); } } // Is it a data mesg? else if ((nextByte & Fit.HeaderTypeMask) == Fit.MesgHeaderMask) { MemoryStream mesgBuffer = new MemoryStream(); byte localMesgNum = (byte)(nextByte & Fit.LocalMesgNumMask); mesgBuffer.WriteByte(localMesgNum); if (localMesgDefs[localMesgNum] == null) { throw new FitException("Decode:DecodeNextMessage - FIT decode error: Missing message definition for local message number " + localMesgNum + " at stream position " + fitStream.Position); } int fieldsSize = localMesgDefs[localMesgNum].GetMesgSize() - 1; try { mesgBuffer.Write(br.ReadBytes(fieldsSize), 0, fieldsSize); } catch (Exception e) { throw new FitException("Decode:DecodeNextMessage - Data Message unexpected end of file. Wanted " + fieldsSize + " bytes at stream position " + fitStream.Position, e); } Mesg newMesg = new Mesg(mesgBuffer, localMesgDefs[localMesgNum]); // If the new message contains a timestamp field, record the value to use as // a reference for compressed timestamp headers Field timestampField = newMesg.GetField("Timestamp"); if (timestampField != null) { object tsValue = timestampField.GetValue(); if (tsValue != null) { timestamp = (uint)tsValue; lastTimeOffset = (int)timestamp & Fit.CompressedTimeMask; } } foreach (Field field in newMesg.fields) { if (field.IsAccumulated) { int i; for (i = 0; i < field.GetNumValues(); i++) { long value = Convert.ToInt64(field.GetRawValue(i)); accumulator.Set(newMesg.Num, field.Num, value); } } } // Now that the entire message is decoded we can evaluate subfields and expand any components newMesg.ExpandComponents(accumulator); if (MesgEvent != null) { MesgEvent(this, new MesgEventArgs(newMesg)); } } else { throw new FitException("Decode:Read - FIT decode error: Unexpected Record Header Byte 0x" + nextByte.ToString("X") + " at stream position: " + fitStream.Position); } } #endregion } // class } // namespace