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