mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
Fit: handle unknown fields gracefully
So far the FIT parser bailed out, whenever it found something unknown/uninterested to GC. This is quite orthogonal to the FIT design, as it's supposed to be extended. renamed read_<foo> functions to match the FIT base_type names. unified handling of "unavailable/invalid" values - i.e. if sensor data is temporary unavailable. This allows easier and consistent handling - especially for the uintXz base_types, which only differ by a different "invalid" value. Had to change the type of the "values" list to int64 to fit uint32/int32, as well. added proper support for signed integer types. I'm wondering, why lon, lat + temperature were decoded properly... added support for currently unsupported base types by just skipping their bytes. This allows us to continue reading.
This commit is contained in:
committed by
Mark Liversedge
parent
e478c24650
commit
bea79092ab
@@ -21,9 +21,11 @@
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QtEndian>
|
||||
#include <QDebug>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <limits>
|
||||
|
||||
#define RECORD_TYPE 20
|
||||
|
||||
@@ -31,29 +33,11 @@ static int fitFileReaderRegistered =
|
||||
RideFileFactory::instance().registerReader(
|
||||
"fit", "Garmin FIT", new FitFileReader());
|
||||
|
||||
/*static const char *base_type_names[] = {
|
||||
"enum", // 0
|
||||
"int8", // 1
|
||||
"uint8", // 2
|
||||
"int16", // 3
|
||||
"uint16", // 4
|
||||
"int32", // 5
|
||||
"uint32", // 6
|
||||
"string", // 7
|
||||
"float32", // 8
|
||||
"float64", // 9
|
||||
"uint8z", // 10
|
||||
"uint16z", // 11
|
||||
"uint32z", // 12
|
||||
"byte" // 13
|
||||
}; */
|
||||
static const int base_type_names_size(14);
|
||||
|
||||
static const QDateTime qbase_time(QDate(1989, 12, 31), QTime(0, 0, 0), Qt::UTC);
|
||||
|
||||
struct FitField {
|
||||
int num;
|
||||
int type;
|
||||
int type; // FIT base_type
|
||||
int size; // in bytes
|
||||
};
|
||||
|
||||
@@ -63,6 +47,13 @@ struct FitDefinition {
|
||||
std::vector<FitField> fields;
|
||||
};
|
||||
|
||||
/* FIT has uint32 as largest integer type. So qint64 is large enough to
|
||||
* store all integer types - no matter if they're signed or not */
|
||||
// XXX this needs to get changed to support non-integer values
|
||||
typedef qint64 fit_value_t;
|
||||
#define NA_VALUE std::numeric_limits<fit_value_t>::max()
|
||||
|
||||
|
||||
struct FitFileReaderState
|
||||
{
|
||||
QFile &file;
|
||||
@@ -71,7 +62,7 @@ struct FitFileReaderState
|
||||
time_t start_time;
|
||||
time_t last_time;
|
||||
QMap<int, FitDefinition> local_msg_types;
|
||||
QSet<int> unknown_record_fields, unknown_global_msg_nums;
|
||||
QSet<int> unknown_record_fields, unknown_global_msg_nums, unknown_base_type;
|
||||
int interval;
|
||||
int devices;
|
||||
bool stopped;
|
||||
@@ -88,42 +79,139 @@ struct FitFileReaderState
|
||||
|
||||
struct TruncatedRead {};
|
||||
|
||||
int read_byte(int *count = NULL) {
|
||||
char c;
|
||||
if (file.read(&c, 1) != 1)
|
||||
void read_unknown( int size, int *count = NULL ){
|
||||
char c[size+1];
|
||||
|
||||
// XXX: just seek instead of read?
|
||||
if (file.read(c, size ) != size)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += size;
|
||||
}
|
||||
|
||||
fit_value_t read_int8(int *count = NULL) {
|
||||
qint8 i;
|
||||
if (file.read(reinterpret_cast<char*>( &i), 1) != 1)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 1;
|
||||
return 0xff & c;
|
||||
|
||||
return i == 0x7f ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
int read_short(bool is_big_endian, int *count = NULL) {
|
||||
uint16_t s;
|
||||
if (file.read(reinterpret_cast<char*>(&s), 2) != 2)
|
||||
fit_value_t read_uint8(int *count = NULL) {
|
||||
quint8 i;
|
||||
if (file.read(reinterpret_cast<char*>( &i), 1) != 1)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 1;
|
||||
|
||||
return i == 0xff ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
fit_value_t read_uint8z(int *count = NULL) {
|
||||
quint8 i;
|
||||
if (file.read(reinterpret_cast<char*>( &i), 1) != 1)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 1;
|
||||
|
||||
return i == 0x00 ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
fit_value_t read_int16(bool is_big_endian, int *count = NULL) {
|
||||
qint16 i;
|
||||
if (file.read(reinterpret_cast<char*>(&i), 2) != 2)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 2;
|
||||
return 0xffff & ( is_big_endian
|
||||
? qFromBigEndian<quint16>( s )
|
||||
: qFromLittleEndian<quint16>( s ) );
|
||||
|
||||
i = is_big_endian
|
||||
? qFromBigEndian<qint16>( i )
|
||||
: qFromLittleEndian<qint16>( i );
|
||||
|
||||
return i == 0x7fff ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
int read_long(bool is_big_endian, int *count = NULL) {
|
||||
uint32_t l;
|
||||
if (file.read(reinterpret_cast<char*>(&l), 4) != 4)
|
||||
fit_value_t read_uint16(bool is_big_endian, int *count = NULL) {
|
||||
quint16 i;
|
||||
if (file.read(reinterpret_cast<char*>(&i), 2) != 2)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 2;
|
||||
|
||||
i = is_big_endian
|
||||
? qFromBigEndian<quint16>( i )
|
||||
: qFromLittleEndian<quint16>( i );
|
||||
|
||||
return i == 0xffff ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
fit_value_t read_uint16z(bool is_big_endian, int *count = NULL) {
|
||||
quint16 i;
|
||||
if (file.read(reinterpret_cast<char*>(&i), 2) != 2)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 2;
|
||||
|
||||
i = is_big_endian
|
||||
? qFromBigEndian<quint16>( i )
|
||||
: qFromLittleEndian<quint16>( i );
|
||||
|
||||
return i == 0x0000 ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
fit_value_t read_int32(bool is_big_endian, int *count = NULL) {
|
||||
qint32 i;
|
||||
if (file.read(reinterpret_cast<char*>(&i), 4) != 4)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 4;
|
||||
return is_big_endian
|
||||
? qFromBigEndian<quint32>( l )
|
||||
: qFromLittleEndian<quint32>( l );
|
||||
|
||||
i = is_big_endian
|
||||
? qFromBigEndian<qint32>( i )
|
||||
: qFromLittleEndian<qint32>( i );
|
||||
|
||||
return i == 0x7fffffff ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
void decodeFileId(const FitDefinition &def, int, const std::vector<int> values) {
|
||||
fit_value_t read_uint32(bool is_big_endian, int *count = NULL) {
|
||||
quint32 i;
|
||||
if (file.read(reinterpret_cast<char*>(&i), 4) != 4)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 4;
|
||||
|
||||
i = is_big_endian
|
||||
? qFromBigEndian<quint32>( i )
|
||||
: qFromLittleEndian<quint32>( i );
|
||||
|
||||
return i == 0xffffffff ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
fit_value_t read_uint32z(bool is_big_endian, int *count = NULL) {
|
||||
quint32 i;
|
||||
if (file.read(reinterpret_cast<char*>(&i), 4) != 4)
|
||||
throw TruncatedRead();
|
||||
if (count)
|
||||
(*count) += 4;
|
||||
|
||||
i = is_big_endian
|
||||
? qFromBigEndian<quint32>( i )
|
||||
: qFromLittleEndian<quint32>( i );
|
||||
|
||||
return i == 0x00000000 ? NA_VALUE : i;
|
||||
}
|
||||
|
||||
void decodeFileId(const FitDefinition &def, int, const std::vector<fit_value_t> values) {
|
||||
int i = 0;
|
||||
int manu = -1, prod = -1;
|
||||
foreach(const FitField &field, def.fields) {
|
||||
int value = values[i++];
|
||||
fit_value_t value = values[i++];
|
||||
|
||||
if( value == NA_VALUE )
|
||||
continue;
|
||||
|
||||
switch (field.num) {
|
||||
case 1: manu = value; break;
|
||||
case 2: prod = value; break;
|
||||
@@ -146,13 +234,17 @@ struct FitFileReaderState
|
||||
}
|
||||
}
|
||||
|
||||
void decodeEvent(const FitDefinition &def, int, const std::vector<int> values) {
|
||||
void decodeEvent(const FitDefinition &def, int, const std::vector<fit_value_t> values) {
|
||||
time_t time = 0;
|
||||
int event = -1;
|
||||
int event_type = -1;
|
||||
int i = 0;
|
||||
foreach(const FitField &field, def.fields) {
|
||||
int value = values[i++];
|
||||
fit_value_t value = values[i++];
|
||||
|
||||
if( value == NA_VALUE )
|
||||
continue;
|
||||
|
||||
switch (field.num) {
|
||||
case 253: time = value + qbase_time.toTime_t(); break;
|
||||
case 0: event = value; break;
|
||||
@@ -192,7 +284,7 @@ struct FitFileReaderState
|
||||
last_event_type = event_type;
|
||||
}
|
||||
|
||||
void decodeLap(const FitDefinition &def, int time_offset, const std::vector<int> values) {
|
||||
void decodeLap(const FitDefinition &def, int time_offset, const std::vector<fit_value_t> values) {
|
||||
time_t time = 0;
|
||||
if (time_offset > 0)
|
||||
time = last_time + time_offset;
|
||||
@@ -200,7 +292,11 @@ struct FitFileReaderState
|
||||
time_t this_start_time = 0;
|
||||
++interval;
|
||||
foreach(const FitField &field, def.fields) {
|
||||
int value = values[i++];
|
||||
fit_value_t value = values[i++];
|
||||
|
||||
if( value == NA_VALUE )
|
||||
continue;
|
||||
|
||||
switch (field.num) {
|
||||
case 253: time = value + qbase_time.toTime_t(); break;
|
||||
case 2: this_start_time = value + qbase_time.toTime_t(); break;
|
||||
@@ -215,30 +311,36 @@ struct FitFileReaderState
|
||||
}
|
||||
}
|
||||
|
||||
void decodeRecord(const FitDefinition &def, int time_offset, const std::vector<int> values) {
|
||||
void decodeRecord(const FitDefinition &def, int time_offset, const std::vector<fit_value_t> values) {
|
||||
time_t time = 0;
|
||||
if (time_offset > 0)
|
||||
time = last_time + time_offset;
|
||||
double alt = 0, cad = 0, km = 0, grade = 0, hr = 0, lat = 0, lng = 0, badgps = 0;
|
||||
double resistance = 0, kph = 0, temperature = 0, time_from_course = 0, watts = 0;
|
||||
int lati = 0x7fffffff, lngi = 0x7fffffff;
|
||||
fit_value_t lati = NA_VALUE, lngi = NA_VALUE;
|
||||
int i = 0;
|
||||
foreach(const FitField &field, def.fields) {
|
||||
int value = values[i++];
|
||||
fit_value_t value = values[i++];
|
||||
|
||||
if( value == NA_VALUE )
|
||||
continue;
|
||||
|
||||
switch (field.num) {
|
||||
case 253: time = value + qbase_time.toTime_t(); break;
|
||||
case 0: lati = value; break;
|
||||
case 1: lngi = value; break;
|
||||
case 2: alt = (value == 0xffff) ? 0 : (value / 5.0 - 500.0); break;
|
||||
case 3: hr = (value == 0xff) ? 0 : value; break;
|
||||
case 4: cad = (value == 0xff) ? 0 : value; break;
|
||||
case 5: km = ((uint32_t) value == 0xffffffff) ? 0 : value / 100000.0; break;
|
||||
case 6: kph = (value == 0xffff) ? 0 : value * 3.6 / 1000.0; break;
|
||||
case 7: watts = (value == 0xffff) ? 0 : value; break;
|
||||
case 9: grade = (value == 0x7fff) ? 0 : value / 100.0; break;
|
||||
case 10: resistance = (value == 0xff) ? 0 : value; break;
|
||||
case 11: time_from_course = (value == 0x7fffffff) ? 0 : value / 1000.0; break;
|
||||
case 13: temperature = (value == 0x7f) ? 0 : value; break;
|
||||
case 2: alt = value / 5.0 - 500.0; break;
|
||||
case 3: hr = value; break;
|
||||
case 4: cad = value; break;
|
||||
case 5: km = value / 100000.0; break;
|
||||
case 6: kph = value * 3.6 / 1000.0; break;
|
||||
case 7: watts = value; break;
|
||||
case 8: break; // XXX packed speed/dist
|
||||
case 9: grade = value / 100.0; break;
|
||||
case 10: resistance = value; break;
|
||||
case 11: time_from_course = value / 1000.0; break;
|
||||
case 12: break; // XXX "cycle_length"
|
||||
case 13: temperature = value; break;
|
||||
default: unknown_record_fields.insert(field.num);
|
||||
}
|
||||
}
|
||||
@@ -254,7 +356,7 @@ struct FitFileReaderState
|
||||
return;
|
||||
*/
|
||||
}
|
||||
if (lati != 0x7fffffff && lngi != 0x7fffffff) {
|
||||
if (lati != NA_VALUE && lngi != NA_VALUE) {
|
||||
lat = lati * 180.0 / 0x7fffffff;
|
||||
lng = lngi * 180.0 / 0x7fffffff;
|
||||
} else
|
||||
@@ -270,6 +372,14 @@ struct FitFileReaderState
|
||||
t.setTime_t(start_time);
|
||||
rideFile->setStartTime(t);
|
||||
}
|
||||
|
||||
//printf( "point time=%d lat=%.2lf lon=%.2lf alt=%.1lf hr=%.0lf "
|
||||
// "cad=%.0lf km=%.1lf kph=%.1lf watts=%.0lf grade=%.1lf "
|
||||
// "resist=%.1lf off=%.1lf temp=%.1lf\n",
|
||||
// time, lat, lng, alt, hr,
|
||||
// cad, km, kph, watts, grade,
|
||||
// resistance, time_from_course, temperature );
|
||||
|
||||
double secs = time - start_time;
|
||||
double nm = 0;
|
||||
double headwind = 0.0;
|
||||
@@ -320,7 +430,7 @@ struct FitFileReaderState
|
||||
int read_record(bool &stop, QStringList &errors) {
|
||||
stop = false;
|
||||
int count = 0;
|
||||
int header_byte = read_byte(&count);
|
||||
int header_byte = read_uint8(&count);
|
||||
if (!(header_byte & 0x80) && (header_byte & 0x40)) {
|
||||
// Definition record
|
||||
int local_msg_type = header_byte & 0xf;
|
||||
@@ -328,30 +438,24 @@ struct FitFileReaderState
|
||||
local_msg_types.insert(local_msg_type, FitDefinition());
|
||||
FitDefinition &def = local_msg_types[local_msg_type];
|
||||
|
||||
int reserved = read_byte(&count); (void) reserved; // unused
|
||||
def.is_big_endian = read_byte(&count);
|
||||
def.global_msg_num = read_short(def.is_big_endian, &count);
|
||||
int num_fields = read_byte(&count);
|
||||
/*printf("definition: local type %d\n",
|
||||
local_msg_type);*/
|
||||
// printf(", %d num_fields:\n", num_fields);
|
||||
int reserved = read_uint8(&count); (void) reserved; // unused
|
||||
def.is_big_endian = read_uint8(&count);
|
||||
def.global_msg_num = read_uint16(def.is_big_endian, &count);
|
||||
int num_fields = read_uint8(&count);
|
||||
//printf("definition: local type=%d global=%d arch=%d fields=%d\n",
|
||||
// local_msg_type, def.global_msg_num, def.is_big_endian,
|
||||
// num_fields );
|
||||
|
||||
for (int i = 0; i < num_fields; ++i) {
|
||||
def.fields.push_back(FitField());
|
||||
FitField &field = def.fields.back();
|
||||
|
||||
field.num = read_byte(&count);
|
||||
field.size = read_byte(&count);
|
||||
int base_type = read_byte(&count);
|
||||
field.num = read_uint8(&count);
|
||||
field.size = read_uint8(&count);
|
||||
int base_type = read_uint8(&count);
|
||||
field.type = base_type & 0x1f;
|
||||
if (field.type >= base_type_names_size) {
|
||||
errors << QString("unknown base type %1").arg(field.type);
|
||||
stop = true;
|
||||
return count;
|
||||
}
|
||||
/*printf(" field %d: %d bytes, num %d, type %s, %s endianness\n",
|
||||
i, field_size, field_def_num, base_type_names[base_type_num],
|
||||
(base_type & 0x80) ? "with" : "without");*/
|
||||
//printf(" field %d: %d bytes, num %d, type %d\n",
|
||||
// i, field.size, field.num, field.type );
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -368,31 +472,43 @@ struct FitFileReaderState
|
||||
}
|
||||
|
||||
if (!local_msg_types.contains(local_msg_type)) {
|
||||
errors << QString("unrecognized local type %1").arg(local_msg_type);
|
||||
errors << QString("local type %1 without previous definition").arg(local_msg_type);
|
||||
stop = true;
|
||||
return count;
|
||||
}
|
||||
const FitDefinition &def = local_msg_types[local_msg_type];
|
||||
std::vector<int> values;
|
||||
//printf( "message local=%d global=%d\n", local_msg_type,
|
||||
// def.global_msg_num );
|
||||
|
||||
std::vector<fit_value_t> values;
|
||||
foreach(const FitField &field, def.fields) {
|
||||
int v;
|
||||
switch (field.size) {
|
||||
case 1: v = read_byte(&count); break;
|
||||
case 2: v = read_short(def.is_big_endian, &count); break;
|
||||
case 4: v = read_long(def.is_big_endian, &count); break;
|
||||
fit_value_t v;
|
||||
switch (field.type) {
|
||||
case 0: v = read_uint8(&count); break;
|
||||
case 1: v = read_int8(&count); break;
|
||||
case 2: v = read_uint8(&count); break;
|
||||
case 3: v = read_int16(def.is_big_endian, &count); break;
|
||||
case 4: v = read_uint16(def.is_big_endian, &count); break;
|
||||
case 5: v = read_int32(def.is_big_endian, &count); break;
|
||||
case 6: v = read_uint32(def.is_big_endian, &count); break;
|
||||
case 10: v = read_uint8z(&count); break;
|
||||
case 11: v = read_uint16z(def.is_big_endian, &count); break;
|
||||
case 12: v = read_uint32z(def.is_big_endian, &count); break;
|
||||
//XXX: support float, string + byte base types
|
||||
default:
|
||||
errors << QString("unsupported field size %1").arg(field.size);
|
||||
stop = true;
|
||||
return count;
|
||||
read_unknown( field.size, &count );
|
||||
v = NA_VALUE;
|
||||
unknown_base_type.insert(field.num);
|
||||
}
|
||||
values.push_back(v);
|
||||
//printf( " field: type=%d num=%d value=%lld\n",
|
||||
// field.type, field.num, v );
|
||||
}
|
||||
// Most of the record types in the FIT format aren't actually all
|
||||
// that useful. FileId, Lap, and Record clearly are. The one
|
||||
// other one that might be useful is DeviceInfo, but it doesn't
|
||||
// seem to be filled in properly. Sean's Cinqo, for example,
|
||||
// shows up as manufacturer #65535, even though it should be #7.
|
||||
// printf("msg: %d\n", global_msg_num);
|
||||
switch (def.global_msg_num) {
|
||||
case 0: decodeFileId(def, time_offset, values); break;
|
||||
case 19: decodeLap(def, time_offset, values); break;
|
||||
@@ -406,14 +522,6 @@ struct FitFileReaderState
|
||||
case 49: /* file creator */
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
int i = 0;
|
||||
printf("----------------\n");
|
||||
foreach(const FitField &field, def.fields) {
|
||||
int value = values[i++];
|
||||
printf("msg %d: field %d = %d\n", def.global_msg_num, field.num, value);
|
||||
}
|
||||
*/
|
||||
unknown_global_msg_nums.insert(def.global_msg_num);
|
||||
}
|
||||
last_msg_type = def.global_msg_num;
|
||||
@@ -429,17 +537,17 @@ struct FitFileReaderState
|
||||
delete rideFile;
|
||||
return NULL;
|
||||
}
|
||||
int header_size = read_byte();
|
||||
int header_size = read_uint8();
|
||||
if (header_size != 12) {
|
||||
errors << QString("bad header size: %1").arg(header_size);
|
||||
delete rideFile;
|
||||
return NULL;
|
||||
}
|
||||
int protocol_version = read_byte();
|
||||
int protocol_version = read_uint8();
|
||||
(void) protocol_version;
|
||||
int profile_version = read_short(false); // always littleEndian
|
||||
int profile_version = read_uint16(false); // always littleEndian
|
||||
(void) profile_version; // not sure what to do with this
|
||||
int data_size = read_long(false); // always littleEndian
|
||||
int data_size = read_uint32(false); // always littleEndian
|
||||
char fit_str[5];
|
||||
if (file.read(fit_str, 4) != 4) {
|
||||
errors << "truncated header";
|
||||
@@ -468,13 +576,15 @@ struct FitFileReaderState
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
int crc = read_short( false ); // always littleEndian
|
||||
int crc = read_uint16( false ); // always littleEndian
|
||||
(void) crc;
|
||||
foreach(int num, unknown_global_msg_nums) {
|
||||
errors << QString("unknown global message number %1; ignoring it").arg(num);
|
||||
}
|
||||
foreach(int num, unknown_global_msg_nums)
|
||||
qDebug() << QString("FitRideFile: unknown global message number %1; ignoring it").arg(num);
|
||||
foreach(int num, unknown_record_fields)
|
||||
errors << QString("unknown record field %1; ignoring it").arg(num);
|
||||
qDebug() << QString("FitRideFile: unknown record field %1; ignoring it").arg(num);
|
||||
foreach(int num, unknown_base_type)
|
||||
qDebug() << QString("FitRideFile: unknown base type %1; skipped").arg(num);
|
||||
|
||||
return rideFile;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user