#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