mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin: Fix fit file checksum computation
This commit is contained in:
+2
-1
@@ -45,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FitViewerActivity extends AbstractGBActivity implements MenuProvider {
|
||||
@@ -85,7 +86,7 @@ public class FitViewerActivity extends AbstractGBActivity implements MenuProvide
|
||||
|
||||
try {
|
||||
fitFile = FitFile.parseIncoming(fitPath);
|
||||
} catch (final IOException e) {
|
||||
} catch (final IOException | FitParseException e) {
|
||||
GB.toast("Failed to parse fit file", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Failed to parse fit file", e);
|
||||
finish();
|
||||
|
||||
+13
-1
@@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitCourse;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitWorkout;
|
||||
@@ -50,6 +51,7 @@ public class GarminFitFileInstallHandler implements InstallHandler {
|
||||
private byte[] rawBytes;
|
||||
private FitFile fitFile;
|
||||
private FileType.FILETYPE fileType;
|
||||
private FitParseException fitParseException;
|
||||
|
||||
public GarminFitFileInstallHandler(final Uri uri, final Context context) {
|
||||
this.mContext = context;
|
||||
@@ -99,6 +101,9 @@ public class GarminFitFileInstallHandler implements InstallHandler {
|
||||
}
|
||||
|
||||
fileType = fitFileId.getType();
|
||||
} catch (final FitParseException e) {
|
||||
LOG.error("Fit file is corrupted", e);
|
||||
fitParseException = e;
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to read fit file", e);
|
||||
}
|
||||
@@ -106,11 +111,18 @@ public class GarminFitFileInstallHandler implements InstallHandler {
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return fitFile != null && fileType != null;
|
||||
// If we got a fitParseException, the file is "valid" (a fit file) for this handler, but corrupted
|
||||
return fitParseException != null || (fitFile != null && fileType != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
|
||||
if (fitParseException != null) {
|
||||
installActivity.setInfoText(fitParseException.getLocalizedMessage());
|
||||
installActivity.setInstallEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fitFile == null || fileType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitLap;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
|
||||
@@ -79,7 +80,7 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
|
||||
final FitFile fitFile;
|
||||
try {
|
||||
fitFile = FitFile.parseIncoming(file);
|
||||
} catch (final IOException e) {
|
||||
} catch (final IOException | FitParseException e) {
|
||||
LOG.error("Failed to parse fit file", e);
|
||||
return summary;
|
||||
}
|
||||
|
||||
+8
-6
@@ -18,6 +18,7 @@ import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecordDataFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||
|
||||
@@ -50,12 +51,12 @@ public class FitFile {
|
||||
}
|
||||
}
|
||||
|
||||
public static FitFile parseIncoming(File file) throws IOException {
|
||||
public static FitFile parseIncoming(File file) throws IOException, FitParseException {
|
||||
return parseIncoming(readFileToByteArray(file));
|
||||
}
|
||||
|
||||
//TODO: process file in chunks??
|
||||
public static FitFile parseIncoming(byte[] fileContents) {
|
||||
public static FitFile parseIncoming(byte[] fileContents) throws FitParseException {
|
||||
|
||||
final GarminByteBufferReader garminByteBufferReader = new GarminByteBufferReader(fileContents);
|
||||
garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
@@ -73,7 +74,7 @@ public class FitFile {
|
||||
final Integer timeOffset = recordHeader.getTimeOffset();
|
||||
if (timeOffset != null) {
|
||||
if (referenceTimestamp == null) {
|
||||
throw new IllegalArgumentException("Got compressed timestamp without knowing current timestamp");
|
||||
throw new FitParseException("Got compressed timestamp without knowing current timestamp");
|
||||
}
|
||||
|
||||
if (timeOffset >= (referenceTimestamp & 0x1FL)) {
|
||||
@@ -104,9 +105,10 @@ public class FitFile {
|
||||
}
|
||||
}
|
||||
garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
int fileCrc = garminByteBufferReader.readShort();
|
||||
if (fileCrc != ChecksumCalculator.computeCrc(fileContents, header.getHeaderSize(), fileContents.length - header.getHeaderSize() - 2)) {
|
||||
throw new IllegalArgumentException("Wrong CRC for FIT file");
|
||||
final int fileCrc = garminByteBufferReader.readShort();
|
||||
final int actualCrc = ChecksumCalculator.computeCrc(fileContents, 0, fileContents.length - 2);
|
||||
if (fileCrc != actualCrc) {
|
||||
throw new FitParseException("Wrong CRC for FIT file: got " + actualCrc + " expected " + fileCrc);
|
||||
}
|
||||
return new FitFile(header, dataRecords);
|
||||
}
|
||||
|
||||
+2
-1
@@ -60,6 +60,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitEvent;
|
||||
@@ -118,7 +119,7 @@ public class FitImporter {
|
||||
/**
|
||||
* @noinspection StatementWithEmptyBody
|
||||
*/
|
||||
public void importFile(final File file) throws IOException {
|
||||
public void importFile(final File file) throws IOException, FitParseException {
|
||||
reset();
|
||||
|
||||
final FitFile fitFile = FitFile.parseIncoming(file);
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception;
|
||||
|
||||
public class FitParseException extends Exception {
|
||||
public FitParseException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -17,6 +17,7 @@ import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
|
||||
|
||||
public final class ActivitySummaryUtils {
|
||||
@@ -61,7 +62,7 @@ public final class ActivitySummaryUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static File convertFitToGpx(final BaseActivitySummary summary, final File file) throws IOException, ActivityTrackExporter.GPXTrackEmptyException {
|
||||
private static File convertFitToGpx(final BaseActivitySummary summary, final File file) throws IOException, ActivityTrackExporter.GPXTrackEmptyException, FitParseException {
|
||||
final FitFile fitFile = FitFile.parseIncoming(file);
|
||||
final List<ActivityPoint> activityPoints = fitFile.getRecords().stream()
|
||||
.filter(r -> r instanceof FitRecord)
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDat
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.exception.FitParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
@@ -270,7 +271,7 @@ public class GarminSupportTest extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestFitFileSettings2() {
|
||||
public void TestFitFileSettings2() throws FitParseException {
|
||||
|
||||
byte[] fileContents = GB.hexStringToByteArray("0e101405b90600002e464954b18b40000000000603048c04048601028402" +
|
||||
"028405028400010000ed2adce7ffffffff01001906ffff02410000310002" +
|
||||
@@ -356,7 +357,7 @@ public class GarminSupportTest extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestFitFileDevelopersField() {
|
||||
public void TestFitFileDevelopersField() throws FitParseException {
|
||||
byte[] fileContents = GB.hexStringToByteArray("0e206806a20000002e464954bed040000100000401028400010002028403048c00000f042329000006a540000100cf0201100d030102000101020305080d1522375990e97962db0040000100ce05000102010102020102031107080a0700000001646f7567686e7574735f6561726e656400646f7567686e7574730060000100140403010204010205048606028401000100008c580000c738b98001008f5a00032c808e400200905c0005a9388a1003d39e");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/DeveloperData.fit
|
||||
|
||||
String expectedOutput = "[" +
|
||||
|
||||
+1
-2
@@ -4,12 +4,11 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FitImporterTest {
|
||||
@Test
|
||||
@Ignore("helper test for development, remove this while debugging")
|
||||
public void localTest() throws IOException {
|
||||
public void localTest() throws Exception {
|
||||
final FitImporter fitImporter = new FitImporter( null, null);
|
||||
fitImporter.importFile(new File("/storage/SKIN_TEMP.fit"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user