QtFit  0.1
Internal library development documentation
All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
fitstreamreader.cpp
Go to the documentation of this file.
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 /*!
21  * \file
22  * Defines the FitStreamReader, and FitStreamReaderPrivate classes.
23  */
24 
25 #include "abstractdatamessage.h"
26 #include "fitstreamreader.h"
27 #include "fitstreamreader_p.h"
28 
29 #include <QDebug>
30 #include <QBitArray>
31 #include <QtEndian>
32 
34 
35 /*!
36  * \class FitStreamReader
37  *
38  * The FitStreamReader class provides a streaming parser for reading Garmin FIT files.
39  *
40  * \todo Mention that this is a lower-level class, a users should probably use some yet-to-be
41  * FitFile class, that wraps up the calls to this one.
42  *
43  * \sa https://developer.garmin.com/fit/protocol/
44  */
45 
46 /*!
47  * Constructs a FIT stream reader with no initial data.
48  *
49  * Use addData() or setDevice() to provide data when ready.
50  *
51  * \sa addData
52  * \sa setDevice
53  */
55 {
56 
57 }
58 
59 /*!
60  * Constructs a FIT stream reader that reads from \a data.
61  *
62  * \c data may be empty or incomplete, in which case more data can be added later via addData().
63  *
64  * \param data FIT data to begin reading.
65  *
66  * \sa addData
67  */
68 FitStreamReader::FitStreamReader(const QByteArray &data) : d_ptr(new FitStreamReaderPrivate(this))
69 {
70  Q_D(FitStreamReader);
71  d->data = data;
72  d->parseFileHeader<QByteArray>();
73 }
74 
75 /*!
76  * Constructs a FIT stream reader that reads from \c device.
77  *
78  * \c device must be open for reading, but does not need to have any bytes available yet.
79  *
80  * \note The caller is responsible for ensuring that \c device remains valid for the lifetime of the
81  * constructed reader, or until clear() or setDevice() is used to clear or replace the device.
82  *
83  * \param device Device to to begin reading.
84  *
85  * \sa clear
86  * \sa setDevice
87  */
88 FitStreamReader::FitStreamReader(QIODevice *device) : d_ptr(new FitStreamReaderPrivate(this))
89 {
90  Q_D(FitStreamReader);
91  d->device = device;
92  d->parseFileHeader<QIODevice>();
93 }
94 
95 /*!
96  * \cond internal
97  *
98  * Constructs a FIT stream reader with private implementation \a d.
99  *
100  * \param d Pointer to private implementation.
101  */
103 {
104 
105 }
106 /// \endcond
107 
108 /*!
109  * Destroys the FIT stream reader.
110  */
112 {
113  delete d_ptr;
114 }
115 
116 /*!
117  * Adds more data to read.
118  *
119  * \note It is not valid to use this function if a device has been assigned, ie via either the
120  * FitStreamReader(QIODevice *device) constructor, or setDevice().
121  *
122  * \param data Additonal data to read.
123  */
124 void FitStreamReader::addData(const QByteArray &data)
125 {
126  Q_D(FitStreamReader);
127  if (d->device != nullptr) {
128  qWarning("FitStreamReader: addData() with device()");
129  Q_ASSERT(d->device != nullptr);
130  return;
131  }
132  d->data += data;
133 }
134 
135 /*!
136  * Returns \c true if the reader has reached the end of the underlying data or device, or an error
137  * has occurred.
138  *
139  * \todo Implement this method.
140  *
141  * \return \c true if the reader has reached the end of the underlying data or device.
142  */
144 {
145  return false; /// \todo Implement this method.
146 }
147 
148 /*!
149  * Clears the FIT stream reader.
150  *
151  * This returns the reader to a state equivalent to having just been default-constructed.
152  *
153  * \sa FitStreamReader()
154  */
156 {
157  Q_D(FitStreamReader);
158  d->data.clear();
159  d->dataOffset = 0;
160  d->device = nullptr;
161 // d->headerSize = 0;
162 // d->expectedChecksum = 0;
163  d->expectedDataSize = 0;
164  d->protocolVersion = QVersionNumber();
165  d->profileVersion = QVersionNumber();
166  d->dataDefinitions.clear();
167  d->recordSizes.clear();
168 }
169 
170 /*!
171  * Returns the current device associated with the reader, or \c nullptr if no device is assigned.
172  *
173  * \return the current device associated with the reader, or \c nullptr if no device is assigned.
174  *
175  * \sa setDevice
176  */
177 QIODevice * FitStreamReader::device() const
178 {
179  Q_D(const FitStreamReader);
180  return d->device;
181 }
182 
183 /*!
184  * Sets the current device to \c device, and resets the stream to its initial state.
185  *
186  * \c device must be open for reading, but does not need to have any bytes available yet.
187  *
188  * \note The caller is responsible for ensuring that \c device remains valid for the lifetime of the
189  * constructed reader, or until clear() or setDevice() is used to clear or replace the device.
190  *
191  * \param device IO device to read from.
192  *
193  * \sa device
194  */
195 void FitStreamReader::setDevice(QIODevice *device)
196 {
197  Q_D(FitStreamReader);
198  if (device) clear();
199  d->device = device;
200  if (device) d->parseFileHeader<QIODevice>();
201 }
202 
203 /*!
204  * Returns the profile version read from the FIT file header, otherwise a null QVersionNumber.
205  *
206  * FIT profile versions have only two components - major and minor. Both have a maximum value of
207  * 15, as the underlying format uses 4-bits for each component.
208  *
209  * \return the profile version read from the FIT file header, otherwise a null QVersionNumber.
210  */
211 QVersionNumber FitStreamReader::profileVersion() const
212 {
213  Q_D(const FitStreamReader);
214  return d->profileVersion;
215 }
216 
217 /*!
218  * Returns the protocol version read from the FIT file header, otherwise a null QVersionNumber.
219  *
220  * FIT protocol versions have only two components - major and minor. The minor component is limited
221  * to the range 0 to 100, whereas the major component may be (theoreticlly) 0 to 655.
222  *
223  * \return the protocol version read from the FIT file header, otherwise a null QVersionNumber.
224  */
225 QVersionNumber FitStreamReader::protocolVersion() const
226 {
227  Q_D(const FitStreamReader);
228  return d->protocolVersion;
229 }
230 
231 /*!
232  * Returns the next FIT data message from the underlying stream, or a null data message if none
233  * could be read.
234  *
235  * If FIT header has not been read yet, it will be read first. If any FIT definition messages are
236  * found, they will be parsed, and kept in a dictionary, to used for interpretation of subsequent
237  * data messages. Reading will continue until the next FIT data message is found, or no more bytes
238  * are available for reading.
239  *
240  * \todo Document how the caller should distinguish errors, from more-data-needed.
241  *
242  * \return the next FIT data message, or a null data message.
243  *
244  * \sa addData
245  */
247 {
248  Q_D(FitStreamReader);
249  return (d->device == nullptr)
250  ? d->readNextDataMessage<QByteArray>() : d->readNextDataMessage<QIODevice>();
251 }
252 
253 /// \cond internal
254 
255 /*!
256  * \class FitStreamReaderPrivate
257  *
258  * Provides private implementation for FitStreamReader.
259  */
260 
261 /*!
262  * Constructs a FitStreamReaderPrivate object, with public implementation \a q.
263  *
264  * \param q Pointer to public implementation.
265  */
266 
268  : dataOffset(0), device(nullptr), q_ptr(q)
269 {
270 
271 }
272 
273 /*!
274  * Destroys the FitStreamReaderPrivate object.
275  */
277 {
278 
279 }
280 
281 /*!
282  * \fn FitStreamReaderPrivate::bytesAvailable
283  *
284  * Returns the number of bytes currently available for reading.
285  *
286  * \return the number of bytes available for reading.
287  */
288 
289 /*!
290  * Specialisation for reading from QByteArray objects.
291  *
292  * \return the number of bytes available for reading.
293  */
294 template<> FitStreamReaderPrivate::size_t FitStreamReaderPrivate::bytesAvailable<QByteArray>() const
295 {
296  Q_ASSERT(device == nullptr);
297  return data.size() - dataOffset;
298 }
299 
300 /*!
301  * Specialisation for reading from QIODevice streams.
302  *
303  * \return the number of bytes available for reading.
304  */
305 template<> FitStreamReaderPrivate::size_t FitStreamReaderPrivate::bytesAvailable<QIODevice>() const
306 {
307  Q_ASSERT(device != nullptr);
308  return device->bytesAvailable();
309 }
310 
311 /*!
312  * Reads and parses the FIT stream's file header.
313  *
314  * \todo Provide, and document, a way for the caller to distingish errors from simply not-enough-
315  * bytes-yet, when \c false it returned.
316  *
317  * \return \c true if the header was successfully read and parsed, \c false otherwise.
318  */
320 {
321  Q_ASSERT(protocolVersion.isNull());
322  Q_ASSERT(profileVersion.isNull());
323 
324  // Read the header bytes.
325  const QByteArray header = readFileHeader<T>();
326  qDebug() << "Header size" << header.size() << "bytes";
327  if (header.isEmpty()) {
328  /// \todo set not-enough-bytes; ie might need to wait for more bytes.
329  qDebug() << "not enough bytes for header";
330  return false;
331  }
332  if (header.size() < 12) {
333  /// \todo set invalid header size error; ie FIT stream is corrupt / invalid.
334  qDebug() << "invalid header size";
335  return false;
336  }
337 
338  { // Protocol version is split into two parts: high 4 bits major, a low 4 bits minor.
339  const quint8 version = header.at(1);
340  protocolVersion = QVersionNumber(version >> 4, version & 0x0F);
341  qDebug() << "Protocol version" << protocolVersion;
342  }
343 
344  { // Profile version is major*100 + minor (ie minor could not be more than 99).
345  const quint16 version = qFromLittleEndian<quint16>(header.mid(2,2).data());
346  profileVersion = QVersionNumber(version/100, version%100);
347  qDebug() << "Profile version" << profileVersion;
348  }
349 
350  expectedDataSize = qFromLittleEndian<quint32>(header.mid(4,4).data());
351  qDebug() << "Data size" << expectedDataSize << "bytes";
352 
353  const QByteArray dataType = header.mid(8,4);
354  qDebug() << "Data type" << dataType;
355  if (dataType != QByteArray(".FIT")) {
356  /// \todo set invalid header data type (must be ".FIT").
357  return false;
358  }
359 
360  // Check the header's checksum (only present in 14+ byte headers, and even then may be 0x0000).
361  if (header.size() >= 14) {
362  const quint16 expectedChecksum = qFromLittleEndian<quint16>(data.mid(12,2).data());
363  if (expectedChecksum == 0x0000) {
364  qDebug() << "FIT file has (optional) checksum 0x0000; ignoring.";
365  } else {
366  const quint16 calculatedChecksum = fitChecksum(header.mid(0,12));
367  qDebug() << "Header checksum" << expectedChecksum << calculatedChecksum;
368  if (calculatedChecksum != expectedChecksum) {
369  qWarning() << "Checksum failure:" << calculatedChecksum << "!=" << expectedChecksum;
370  /// \todo set error, and return false here?
371  } else {
372  qDebug() << "Checkums match";
373  }
374  }
375  }
376  return true;
377 }
378 
379 /*!
380  * Reads and parses a FIT Definition Message.
381  *
382  * Note, this function assumes that the next item in the FIT stream is indeed a Definition Message
383  * (the caller should have already determined this). Though it is perfectly acceptable that the
384  * message may not be completely available yet (in which case \c false will be returned).
385  *
386  * \return \c true if the Definition Message was successfully read and parsed, \c false otherwise.
387  */
389 {
390  // Parse the record header.
391  qDebug() << "parsing definition message";
392  Q_ASSERT(bytesAvailable<T>());
393  const quint8 recordHeader = peekByte<T>();
394  qDebug() << __func__ << "recordHeader" << recordHeader;
395  Q_ASSERT_X(isDefinitionMessage(recordHeader), "parseDefinitionMessage",
396  "FIT record header does not indiciate a definition message");
397  const bool hasDevFields = (recordHeader & (1 << 5));
398  qDebug() << __func__ << "has dev fields" << hasDevFields;
399  qDebug() << __func__ << "reserved" << (recordHeader & (1 << 4));
400  const quint8 localMessageType = (recordHeader & 0xF); // Least significant 4 bits.
401  qDebug() << __func__ << "local message type" << localMessageType;
402 
403  // Read the completed definition record (if available).
404  if (bytesAvailable<T>() < 6) {
405  return false; // Not enough bytes.
406  }
407  const quint8 numberOfFields = peekByte<T>(5);
408  qDebug() << __func__ << "number of fields" << numberOfFields;
409  int numberOfDevFields = 0;
410  if (hasDevFields) {
411  const int offsetToNumberOfDevFields = 6 + (numberOfFields * 3);
412  qDebug() << __func__ << "offset to number of dev fields" << offsetToNumberOfDevFields;
413  if (bytesAvailable<T>() < offsetToNumberOfDevFields) {
414  return false; // Not enough bytes.
415  }
416  numberOfDevFields = peekByte<T>(offsetToNumberOfDevFields);
417  }
418  const int recordSize = 6 + (numberOfFields * 3) + (hasDevFields ? 1 + (numberOfDevFields * 3) : 0);
419  qDebug() << __func__ << "number of dev fields" << numberOfDevFields;
420  qDebug() << __func__ << "record size" << recordSize;
421  const QByteArray record = readBytes<T>(recordSize);
422  if (record.isEmpty()) {
423  return false; // Not enough bytes.
424  }
425 
426  // Parse the rest of the definition record.
427  DataDefinition defn;
428  defn.recordSize = 0;
429  defn.architecture = static_cast<Architecture>(record.at(2));
430  qDebug() << __func__ << "architecture" << (int)defn.architecture;
431  qDebug() << record.mid(3,2);
433  ? qFromBigEndian<quint16>(record.mid(3,2))
434  : qFromLittleEndian<quint16>(record.mid(3,2)));
435  qDebug() << __func__ << "header" << (quint8)record.at(0);
436  qDebug() << __func__ << "reserved" << (quint8)record.at(1);
437  qDebug() << __func__ << "msgNum" << (int)defn.globalMessageNumber;
438 
439  // Parse the definition fields.
440  for (int i=0; i < numberOfFields; ++i) {
441  const int pos = 6 + (i * 3);
442  FieldDefinition field;
443  field.number = record.at(pos);
444  field.size = record.at(pos+1);
445  field.baseType = static_cast<FitBaseType>(record.at(pos+2));
446  qDebug() << __func__ << "field" << i << "number" << field.number;
447  qDebug() << __func__ << "field" << i << "size" << field.size;
448  qDebug() << __func__ << "field" << i << "type" << static_cast<quint8>(field.baseType);
449  defn.fieldDefinitions.append(field);
450  defn.recordSize += field.size;
451  }
452  Q_ASSERT(defn.fieldDefinitions.size() == numberOfFields);
453  qDebug() << __func__ << "defn record size" << defn.recordSize;
454 
455  // Parse the definition fields.
456  Q_ASSERT(hasDevFields || (numberOfDevFields == 0));
457  for (int i=0; i < numberOfDevFields; ++i) {
458  const int pos = 6 + (numberOfFields * 3) + 1 + (i * 3);
460  field.fieldNumber = record.at(pos);
461  field.size = record.at(pos+1);
462  field.devDataIndex = record.at(pos+2);
463  qDebug() << __func__ << "dev field" << i << "number" << field.fieldNumber;
464  qDebug() << __func__ << "dev field" << i << "size" << field.size;
465  qDebug() << __func__ << "dev field" << i << "dataIndex" << field.devDataIndex;
466  defn.developerFieldDefinitions.append(field);
467  defn.recordSize += field.size;
468  }
469  Q_ASSERT(defn.developerFieldDefinitions.size() == numberOfDevFields);
470  qDebug() << __func__ << "defn record size" << defn.recordSize;
471  if (defn.recordSize == 0) {
472  qWarning() << "defintion record size is zero";
473  }
474 
475  // Record the definition data for future Data Messages.
476  dataDefinitions.insert(localMessageType, defn);
477  return true;
478 }
479 
480 /*!
481  * Reads and parses a FIT Data Message.
482  *
483  * Note, this function assumes that the next item in the FIT stream is indeed a Data Message
484  * (the caller should have already determined this). Though it is perfectly acceptable that the
485  * message may not be completely available yet (in which case \c false will be returned).
486  *
487  * This fuction will, as part of decoding the message, look-up the Data Message's definition, which
488  * must have been stored previously by one or more Definition Messages being processed earlier. If
489  * no matching definition is found, \c false will be returned.
490  *
491  * \return \c true if the Data Message was successfully read and parsed, \c false otherwise.
492  */
494 {
495  Q_ASSERT(bytesAvailable<T>());
496  qDebug() << "parsing data message";
497  const quint8 recordHeader = peekByte<T>();
498  qDebug() << __func__ << "recordHeader" << recordHeader;
499  Q_ASSERT_X(!isDefinitionMessage(recordHeader), "parseDataMessage",
500  "FIT record header does not indiciate a data message");
501 
502  // Parse the record header.
503  quint8 localMessageType;
504  if (recordHeader & (1 << 7)) { // Compressed Timestamp Header.
505  qDebug() << __func__ << "Compressed Timestamp Header";
506  localMessageType = ((recordHeader >> 4) & 0x7);
507  const quint8 timeOffset = (recordHeader & 0xF);
508  qDebug() << "time offset" << timeOffset;
509  /// \todo Process timeOffset.
510  } else { // Normal (Data Message) Header
511  qDebug() << __func__ << "Normal (Data Message) Header";
512  qDebug() << __func__ << "reserved5" << (recordHeader & (1 << 5));
513  qDebug() << __func__ << "reserved4" << (recordHeader & (1 << 4));
514  localMessageType = (recordHeader & 0xF); // Least significant 4 bits.
515  }
516  qDebug() << __func__ << "local message type" << localMessageType;
517 
518  // Lookup the record's field definitions.
519  if (!dataDefinitions.contains(localMessageType)) {
520  qWarning() << "No definition for local message type" << localMessageType;
521  /// \todo Set error code.
522  return nullptr; // FIT data is corrupt; we cannot safely continue parsing.
523  }
524  const DataDefinition defn = dataDefinitions.value(localMessageType);
525 
526  // Parse record's Data Fields.
527  qDebug() << __func__ << "record size" << defn.recordSize;
528  if (defn.recordSize == 0) {
529  qWarning() << "record size is zero"; // Not really sure what to do here.
530  return nullptr;
531  }
532  const QByteArray record = readBytes<T>(defn.recordSize+1);
533  if (record.isEmpty()) {
534  return nullptr; // Not enough bytes.
535  }
536  qDebug() << "record" << record.mid(1);
537  return AbstractDataMessage::fromData(&defn, record.mid(1));
538 }
539 
540 /*!
541  * \fn FitStreamReaderPrivate::peekByte
542  *
543  * Peeks the next \a pos'th byte in the FIT stream.
544  *
545  * If less than \a pos bytes are available, \c 0 will be returned. It is the caller's responsibility
546  * to first check that there are enough bytes availble (eg via bytesAvailable) before calling this,
547  * otherwise it would be impossible to distinguish between not-enough-bytes-available and reading a
548  * valid \c 0x00 byte.
549  *
550  * Of course, we could provide a better error feedback mechanism, but as this is an internal private
551  * function, and all of our callers need to check for bytes first anyway, this is the more efficient
552  * pattern here.
553  *
554  * \param pos The position (relative to the current stream position) to peek.
555  *
556  * \return the next \a pos'th byte, or \c 0 if not enough bytes were available.
557  */
558 
559 /*!
560  * Specialisation for reading from QByteArray objects.
561  *
562  * \param pos The position (relative to the current stream position) to peek.
563  *
564  * \return the next \a pos'th byte, or \c 0 if not enough bytes were available.
565  */
566 template<> quint8 FitStreamReaderPrivate::peekByte<QByteArray>(const int pos) const
567 {
568  Q_ASSERT(device == nullptr);
569  return (data.size() > (dataOffset + pos)) ? data.at(dataOffset+pos) : 0;
570 }
571 
572 /*!
573  * Specialisation for reading from QIODevice streams.
574  *
575  * \param pos The position (relative to the current stream position) to peek.
576  *
577  * \return the next \a pos'th byte, or \c 0 if not enough bytes were available.
578  */
579 template<> quint8 FitStreamReaderPrivate::peekByte<QIODevice>(const int pos) const
580 {
581  Q_ASSERT(device != nullptr);
582  const QByteArray bytes = device->peek(1+pos);
583  return (bytes.size() > pos) ? bytes.at(pos) : 0;
584 }
585 
586 /*!
587  * \fn FitStreamReaderPrivate::readBytes
588  *
589  * Reads the next \a size bytes from the FIT stream.
590  *
591  * \param size The number of bytes to read.
592  *
593  * \return the next \a size bytes, or an empty \c QDataArray if less than \a size bytes were available.
594  */
595 
596 /*!
597  * Specialisation for reading from QIODevice streams.
598  *
599  * \param size The number of bytes to read.
600  *
601  * \return the next \a size bytes, or an empty \c QDataArray if less than \a size bytes were available.
602  */
603 template<> QByteArray FitStreamReaderPrivate::readBytes<QByteArray>(const size_t size)
604 {
605  Q_ASSERT(device == nullptr);
606  return ((data.size() - dataOffset) < size) ? QByteArray() : data.mid((dataOffset+=size)-size, size);
607 }
608 
609 /*!
610  * Specialisation for reading from QIODevice streams.
611  *
612  * \param size The number of bytes to read.
613  *
614  * \return the next \a size bytes, or an empty \c QDataArray if less than \a size bytes were available.
615  */
616 template<> QByteArray FitStreamReaderPrivate::readBytes<QIODevice>(const size_t size)
617 {
618  Q_ASSERT(device != nullptr);
619  return (device->bytesAvailable() < size) ? QByteArray() : device->read(size);
620 }
621 
622 /*!
623  * Reads all bytes of the FIT stream file header.
624  *
625  * This function will first peek the header length (if available), and then read the entire header
626  * if (and only if) all header bytes are available.
627  *
628  * \return the FIT file header, or an empty \c QByteArray if not enough bytes were available.
629  */
630 template<class T> QByteArray FitStreamReaderPrivate::readFileHeader()
631 {
632  // Return `n` bytes, where `n` is given by the first byte (ie a length-prefixed buffer).
633  return (bytesAvailable<T>()) ? readBytes<T>(peekByte<T>()) : QByteArray();
634 }
635 
636 /*!
637  * Reads up to, and including, the next FIT Data Message.
638  *
639  * This function will continue reading the FIT stream until either no bytes are available, or a FIT
640  * Data Message has been located and parsed (or failed to be parsed). If (as expected) Definition
641  * Messages are found in the process, then they will be parse, and their definitions cached for
642  * interpreting future Data Messages.
643  *
644  * \note The caller takes ownership of the returned pointer (if not \c nullptr), and is responsible
645  * for deleting object when finished with.
646  *
647  * \return the next FIT Data Message, or a \c nullptr if none found, or an error occurred.
648  */
650 {
651  // If we haven't parsed the FIT File Header yet, do so now.
652  if ((protocolVersion.isNull() && (!parseFileHeader<T>()))) {
653  return nullptr;
654  }
655 
656  // Process all FIT Data Records until we get a FIT Data Message (or run out of bytes).
657  while (bytesAvailable<T>()) { // At least one byte, for the next data record header byte.
658  const quint8 recordHeader = peekByte<T>();
659  if (isDefinitionMessage(recordHeader)) {
660  if (!parseDefinitionMessage<T>()) return nullptr;
661  // Not returning here; we'll continue processing until we get a FIT Data Message.
662  } else return parseDataMessage<T>();
663  }
664  return nullptr;
665 }
666 
667 /*!
668  * Calculates a checksum, as per the algorithm used by FIT file headers.
669  *
670  * \todo Move this somewhere appropriate; its not specific to FitStreamReader, but rather would be
671  * useful eventually for a FitStreamWriter class too. I guess its safe to keep in a private class
672  * for now, so it can then be moved without affecting the library's binary interface.
673  *
674  * \param data FIT file header to calculate the checksum for.
675  *
676  * \return 16-bit checksum for \a data.
677  */
678 quint16 FitStreamReaderPrivate::fitChecksum(const QByteArray &data)
679 {
680  quint16 checksum=0;
681  for (const auto &byte: data) {
682  static const quint16 crcTable[16] = {
683  0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
684  0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
685  };
686  for (int byteShift=0; byteShift<=4; byteShift+=4) {
687  const quint16 tmp = crcTable[checksum & 0xF];
688  checksum = (checksum >> 4) & 0x0FFF;
689  checksum = checksum ^ tmp ^ crcTable[(byte >> byteShift) & 0xF];
690  }
691  }
692  return checksum;
693 }
694 
695 /*!
696  * Returns \c true if \a recordHeader indicates a Definition Message, otherwise \c false.
697  *
698  * \param recordHeader A FIT Data Record header byte.
699  *
700  * \return \c true if \a recordHeader indicates a Definition Message, otherwise \c false.
701  */
702 bool FitStreamReaderPrivate::isDefinitionMessage(const quint8 recordHeader)
703 {
704  // For definition messages, bit 7 must be off (bit 7 on would indicate a Compressed Timestamp
705  // Data Message, which cannot be a Definition Message), and bit 6 must be on (otherwise this
706  // woud be a Normal Data Message, not a Definition Message).
707  return ((recordHeader >> 6) == 1); // Match bit 7 on and 6 off; ie 01xxxxxx.
708 }
709 
710 /// \endcond
711 
#define QTFIT_END_NAMESPACE
Macro for ending the QtFit library's top-most namespace (if one is defined).
Definition: QtFit_global.h:78
#define QTFIT_BEGIN_NAMESPACE
Macro for starting the QtFit library's top-most namespace (if one is defined).
Definition: QtFit_global.h:77
Declares the AbstractDataMessage class.
The AbstractDataMessage class is the polymorphic base class for all FIT Data Message classes.
static AbstractDataMessage * fromData(const DataDefinition *const defn, const QByteArray &record)
Constructs the relevant AbstractDataMessage-derived class to parse record according to defn.
Provides private implementation for FitStreamReader.
quint32 expectedDataSize
Total size, in bytes, expected to comprise the FIT file.
AbstractDataMessage * readNextDataMessage()
Reads up to, and including, the next FIT Data Message.
QIODevice * device
FIT File IO stream (alternative to data and dataOffset).
static bool isDefinitionMessage(const quint8 recordHeader)
Returns true if recordHeader indicates a Definition Message, otherwise false.
bool parseDefinitionMessage()
Reads and parses a FIT Definition Message.
QByteArray data
FIT File data (alternative to device).
static quint16 fitChecksum(const QByteArray &data)
Calculates a checksum, as per the algorithm used by FIT file headers.
AbstractDataMessage * parseDataMessage()
Reads and parses a FIT Data Message.
QVersionNumber profileVersion
Protocol version read from the parsed FIT file header.
QByteArray readFileHeader()
Reads all bytes of the FIT stream file header.
QVersionNumber protocolVersion
Protocol version read from the parsed FIT file header.
size_t dataOffset
Current position within data.
QHash< int, DataDefinition > dataDefinitions
Local message types to current data definitions.
FitStreamReaderPrivate(FitStreamReader *const q)
Constructs a FitStreamReaderPrivate object, with public implementation q.
qsizetype size_t
Size type for size-related operations.
bool parseFileHeader()
Reads and parses the FIT stream's file header.
virtual ~FitStreamReaderPrivate()
Destroys the FitStreamReaderPrivate object.
The FitStreamReader class provides a streaming parser for reading Garmin FIT files.
AbstractDataMessage * readNext()
Returns the next FIT data message from the underlying stream, or a null data message if none could be...
QVersionNumber profileVersion() const
Returns the profile version read from the FIT file header, otherwise a null QVersionNumber.
FitStreamReader()
Constructs a FIT stream reader with no initial data.
bool atEnd() const
Returns true if the reader has reached the end of the underlying data or device, or an error has occu...
~FitStreamReader()
Destroys the FIT stream reader.
void addData(const QByteArray &data)
Adds more data to read.
QIODevice * device() const
Returns the current device associated with the reader, or nullptr if no device is assigned.
void clear()
Clears the FIT stream reader.
void setDevice(QIODevice *device)
Sets the current device to device, and resets the stream to its initial state.
FitStreamReaderPrivate *const d_ptr
Internal d-pointer.
QVersionNumber protocolVersion() const
Returns the protocol version read from the FIT file header, otherwise a null QVersionNumber.
Declares the FitStreamReader class.
Declares the FitStreamReaderPrivate class.
Data Message definition.
Definition: types_p.h:83
QList< FieldDefinition > fieldDefinitions
Definitons list of all fields, if any, present in the described Data Message.
Definition: types_p.h:88
QList< DeveloperFieldDefinition > developerFieldDefinitions
Definitions list of all custom fields, if any, present in the described Data Message.
Definition: types_p.h:91
int recordSize
Total size, in byetes, of all fields in the described Data Message.
Definition: types_p.h:101
Architecture architecture
Architecture type for any multi-byte fields.
Definition: types_p.h:84
MesgNum globalMessageNumber
FIT Global Message Number the Data Message represents.
Definition: types_p.h:85
Custom developer field definition.
Definition: types_p.h:55
quint8 fieldNumber
Maps to the field_definition_number of a field_description Message.
Definition: types_p.h:56
quint8 size
Size (in bytes) of the specified FIT message’s field.
Definition: types_p.h:57
quint8 devDataIndex
Maps to the developer_data_index of a developer_data_id Message.
Definition: types_p.h:58
Field Definition for FIT Data Messages.
Definition: types_p.h:69
FitBaseType baseType
Base type for interpreting unknown fields.
Definition: types_p.h:72
quint8 number
Unique ID for the FIT field within a given FIT data message.
Definition: types_p.h:70
quint8 size
Size (in bytes) of the field.
Definition: types_p.h:71
FitBaseType
Garmin FIT FitBaseType type.
Definition: types.h:3388
MesgNum
Garmin FIT MesgNum type.
Definition: types.h:91
Architecture
Architecture Type for FIT Data Messages.
Definition: types_p.h:42
@ BigEndian
Little-endian byte ordering.