LCOV - code coverage report
Current view: top level - src - fitstreamreader.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 91 125 72.8 %
Date: 2021-08-07 08:50:35 Functions: 22 32 68.8 %

          Line data    Source code
       1             : /*
       2             :     Copyright 2021 Paul Colby
       3             : 
       4             :     This file is part of QtFit.
       5             : 
       6             :     QtFit is free software: you can redistribute it and/or modify
       7             :     it under the terms of the GNU Lesser General Public License as published by
       8             :     the Free Software Foundation, either version 3 of the License, or
       9             :     (at your option) any later version.
      10             : 
      11             :     QtFit is distributed in the hope that it will be useful,
      12             :     but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :     GNU Lesser General Public License for more details.
      15             : 
      16             :     You should have received a copy of the GNU Lesser General Public License
      17             :     along with QtFit.  If not, see <http://www.gnu.org/licenses/>.
      18             : */
      19             : 
      20             : #include "fitstreamreader.h"
      21             : #include "fitstreamreader_p.h"
      22             : 
      23             : #include <QDebug>
      24             : #include <QBitArray>
      25             : #include <QtEndian>
      26             : 
      27             : QTFIT_BEGIN_NAMESPACE
      28             : 
      29           2 : FitStreamReader::FitStreamReader() : d_ptr(new FitStreamReaderPrivate(this))
      30             : {
      31             : 
      32           2 : }
      33             : 
      34          88 : FitStreamReader::FitStreamReader(const QByteArray &data) : d_ptr(new FitStreamReaderPrivate(this))
      35             : {
      36          88 :     Q_D(FitStreamReader);
      37          88 :     d->data = data;
      38          88 :     d->parseFileHeader<QByteArray>();
      39          88 : }
      40             : 
      41           0 : FitStreamReader::FitStreamReader(QIODevice *device) : d_ptr(new FitStreamReaderPrivate(this))
      42             : {
      43           0 :     Q_D(FitStreamReader);
      44           0 :     d->device = device;
      45           0 :     d->parseFileHeader<QIODevice>();
      46           0 : }
      47             : 
      48         180 : FitStreamReader::~FitStreamReader()
      49             : {
      50          90 :     delete d_ptr;
      51          90 : }
      52             : 
      53           0 : void FitStreamReader::addData(const QByteArray &data)
      54             : {
      55           0 :     Q_D(FitStreamReader);
      56           0 :     if (d->device != nullptr) {
      57           0 :         qWarning("FitStreamReader: addData() with device()");
      58             :         Q_ASSERT(d->device != nullptr);
      59           0 :         return;
      60             :     }
      61           0 :     d->data += data;
      62             : }
      63             : 
      64           0 : bool FitStreamReader::atEnd() const
      65             : {
      66           0 :     return false; ///< @todo
      67             : }
      68             : 
      69          30 : void FitStreamReader::clear()
      70             : {
      71          30 :     Q_D(FitStreamReader);
      72          30 :     d->data.clear();
      73          30 :     d->dataOffset = 0;
      74          30 :     d->device = nullptr;
      75             : //    d->headerSize = 0;
      76             : //    d->expectedChecksum = 0;
      77          30 :     d->expectedDataSize = 0;
      78          30 :     d->protocolVersion = QVersionNumber();
      79          30 :     d->profileVersion = QVersionNumber();
      80          30 :     d->dataDefinitions.clear();
      81          30 :     d->recordSizes.clear();
      82          30 : }
      83             : 
      84          42 : QIODevice * FitStreamReader::device() const
      85             : {
      86          42 :     Q_D(const FitStreamReader);
      87          42 :     return d->device;
      88             : }
      89             : 
      90             : // [enum] Error error() const; or lastError?
      91             : // QString errorString() const;
      92             : 
      93          14 : void FitStreamReader::setDevice(QIODevice *device)
      94             : {
      95          14 :     Q_D(FitStreamReader);
      96          14 :     if (device) clear();
      97          14 :     d->device = device;
      98          14 :     if (device) d->parseFileHeader<QIODevice>();
      99          14 : }
     100             : 
     101          28 : quint16 fitChecksum(const QByteArray &data) {
     102          28 :     quint16 checksum=0;
     103         364 :     for (const auto &byte: data) {
     104             :         static const quint16 crcTable[16] = {
     105             :             0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
     106             :             0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
     107             :         };
     108        1008 :         for (int byteShift=0; byteShift<=4; byteShift+=4) {
     109         672 :             const quint16 tmp = crcTable[checksum & 0xF];
     110         672 :             checksum = (checksum >> 4) & 0x0FFF;
     111         672 :             checksum = checksum ^ tmp ^ crcTable[(byte >> byteShift) & 0xF];
     112             :         }
     113             :     }
     114          28 :     return checksum;
     115             : }
     116             : 
     117         112 : QVersionNumber FitStreamReader::profileVersion() const
     118             : {
     119         112 :     Q_D(const FitStreamReader);
     120         112 :     return d->profileVersion;
     121             : }
     122             : 
     123         112 : QVersionNumber FitStreamReader::protocolVersion() const
     124             : {
     125         112 :     Q_D(const FitStreamReader);
     126         112 :     return d->protocolVersion;
     127             : }
     128             : 
     129           0 : FitDataMessage FitStreamReader::readNext()
     130             : {
     131           0 :     Q_D(FitStreamReader);
     132           0 :     return (d->device == nullptr)
     133           0 :         ? d->readNextDataMessage<QByteArray>() : d->readNextDataMessage<QIODevice>();
     134             : }
     135             : 
     136          90 : FitStreamReaderPrivate::FitStreamReaderPrivate(FitStreamReader * const q)
     137          90 :     : dataOffset(0), device(nullptr), q_ptr(q)
     138             : {
     139             : 
     140          90 : }
     141             : 
     142         180 : FitStreamReaderPrivate::~FitStreamReaderPrivate()
     143             : {
     144             : 
     145         180 : }
     146             : 
     147          88 : template<> FitStreamReaderPrivate::size_t FitStreamReaderPrivate::bytesAvailable<QByteArray>() const
     148             : {
     149             :     Q_ASSERT(device == nullptr);
     150          88 :     return data.size() - dataOffset;
     151             : }
     152             : 
     153          12 : template<> FitStreamReaderPrivate::size_t FitStreamReaderPrivate::bytesAvailable<QIODevice>() const
     154             : {
     155             :     Q_ASSERT(device != nullptr);
     156          12 :     return device->bytesAvailable();
     157             : }
     158             : 
     159         100 : template<class T> bool FitStreamReaderPrivate::parseFileHeader()
     160             : {
     161             :     Q_ASSERT(protocolVersion.isNull());
     162             :     Q_ASSERT(profileVersion.isNull());
     163             : 
     164             :     // Read the header bytes.
     165         200 :     const QByteArray header = readFileHeader<T>();
     166         100 :     qDebug() << "Header size" << header.size() << "bytes";
     167         100 :     if (header.isEmpty()) {
     168             :         /// @todo set not-enough-bytes; ie might need to wait for more bytes.
     169           4 :         qDebug() << "not enough bytes for header";
     170           4 :         return false;
     171             :     }
     172          96 :     if (header.size() < 12) {
     173             :         /// @todo set invalid header size error; ie FIT stream is corrupt / invalid.
     174           0 :         qDebug() << "invalid header size";
     175           0 :         return false;
     176             :     }
     177             : 
     178             :     {   // Protocol version is split into two parts: high 4 bits major, a low 4 bits minor.
     179          96 :         const quint8 version = header.at(1);
     180          96 :         protocolVersion = QVersionNumber(version >> 4, version & 0x0F);
     181          96 :         qDebug() << "Protocol version" << protocolVersion;
     182             :     }
     183             : 
     184             :     {   // Profile version is major*100 + minor (ie minor could not be more than 99).
     185          96 :         const quint16 version = qFromLittleEndian<quint16>(header.mid(2,2).data());
     186          96 :         profileVersion = QVersionNumber(version/100, version%100);
     187          96 :         qDebug() << "Profile version" << profileVersion;
     188             :     }
     189             : 
     190          96 :     expectedDataSize = qFromLittleEndian<quint32>(header.mid(4,4).data());
     191          96 :     qDebug() << "Data size" << expectedDataSize << "bytes";
     192             : 
     193         192 :     const QByteArray dataType = header.mid(8,4);
     194          96 :     qDebug() << "Data type" << dataType;
     195          96 :     if (dataType != QByteArray(".FIT")) {
     196             :         /// @todo set invalid header data type (must be ".FIT").
     197           0 :         return false;
     198             :     }
     199             : 
     200             :     // Check the header's checksum (only present in 14+ byte headers, and even then may be 0x0000).
     201          96 :     if (header.size() >= 14) {
     202          48 :         const quint16 expectedChecksum = qFromLittleEndian<quint16>(data.mid(12,2).data());
     203          48 :         if (expectedChecksum == 0x0000) {
     204          20 :             qDebug() << "FIT file has (optional) checksum 0x0000; ignoring.";
     205             :         } else {
     206          28 :             const quint16 calculatedChecksum = fitChecksum(header.mid(0,12));
     207          28 :             qDebug() << "Header checksum" << expectedChecksum << calculatedChecksum;
     208          28 :             if (calculatedChecksum != expectedChecksum) {
     209           0 :                 qWarning() << "Checksum failure:" << calculatedChecksum << "!=" << expectedChecksum;
     210             :                 /// @todo set error, and return false here?
     211             :             } else {
     212          28 :                 qDebug() << "Checkums match";
     213             :             }
     214             :         }
     215             :     }
     216          96 :     return true;
     217             : }
     218             : 
     219           0 : template<class T> bool FitStreamReaderPrivate::parseDefinitionMessage()
     220             : {
     221             :     Q_ASSERT(bytesAvailable<T>());
     222           0 :     return false; /// @todo Implement!
     223             : }
     224             : 
     225           0 : template<class T> FitDataMessage FitStreamReaderPrivate::parseDataMessage()
     226             : {
     227             :     Q_ASSERT(bytesAvailable<T>());
     228           0 :     return FitDataMessage(); /// @todo Implement!
     229             : }
     230             : 
     231          88 : template<> quint8 FitStreamReaderPrivate::peekByte<QByteArray>() const
     232             : {
     233             :     Q_ASSERT(device == nullptr);
     234          88 :     return (data.size() > dataOffset) ? data.at(dataOffset) : 0;
     235             : }
     236             : 
     237           8 : template<> quint8 FitStreamReaderPrivate::peekByte<QIODevice>() const
     238             : {
     239             :     Q_ASSERT(device != nullptr);
     240           8 :     const QByteArray byte = device->peek(1);
     241          16 :     return byte.isEmpty() ? 0 : byte.at(0);
     242             : }
     243             : 
     244          88 : template<> QByteArray FitStreamReaderPrivate::readBytes<QByteArray>(const size_t size)
     245             : {
     246             :     Q_ASSERT(device == nullptr);
     247          88 :     return ((data.size() - dataOffset) < size) ? QByteArray() : data.mid(dataOffset, size);
     248             : }
     249             : 
     250           8 : template<> QByteArray FitStreamReaderPrivate::readBytes<QIODevice>(const size_t size)
     251             : {
     252             :     Q_ASSERT(device != nullptr);
     253           8 :     return (device->bytesAvailable() < size) ? QByteArray() : device->read(size);
     254             : }
     255             : 
     256         100 : template<class T> QByteArray FitStreamReaderPrivate::readFileHeader()
     257             : {
     258             :     // Return `n` bytes, where `n` is given by the first byte (ie a length-prefixed buffer).
     259         100 :     return (bytesAvailable<T>()) ? readBytes<T>(peekByte<T>()) : QByteArray();
     260             : }
     261             : 
     262           0 : template<class T> FitDataMessage FitStreamReaderPrivate::readNextDataMessage()
     263             : {
     264             :     // If we haven't parsed the FIT File Header yet, do so now.
     265           0 :     if ((protocolVersion.isNull() && (!parseFileHeader<T>()))) {
     266           0 :         return FitDataMessage(); // Need a way to return a null message.
     267             :     }
     268             : 
     269             :     // Process all FIT Data Records until we get a FIT Data Message (or run out of bytes).
     270           0 :     while (bytesAvailable<T>()) { // At least one byte, for the next data record header byte.
     271           0 :         const quint8 recordHeader = peekByte<T>();
     272           0 :         if (recordHeader & (1 << 6)) { // Bit 6 indicates a Definition Message.
     273           0 :             if (!parseDefinitionMessage<T>()) return FitDataMessage();
     274             :             // Not returning here; we'll continue processing until we get a FIT Data Message.
     275           0 :         } else return parseDataMessage<T>();
     276             :     }
     277           0 :     return FitDataMessage();
     278             : }
     279             : 
     280             : QTFIT_END_NAMESPACE

Generated by: LCOV version 1.14