1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-09 03:37:03 +01:00

Garmin: fix major issue with field encoding/decoding

Our implementation of scale and offset was backwards: we were adding offset and then dividing by scale when decoding fields, but the publicly available protocol description dictates otherwise ( http://web.archive.org/web/20240519102659/https://developer.garmin.com/fit/protocol/#scaleoffset ):  "the binary quantity is divided by the scale factor and then the offset is subtracted".
For this reason the sign of GARMIN_TIME_EPOCH in Timestamp field definition must be flipped as well.
This commit is contained in:
Daniele Gobbetti 2024-08-16 13:57:00 +02:00
parent 404e432adf
commit f2f6536ea8
9 changed files with 30 additions and 29 deletions

View File

@ -48,11 +48,11 @@ public enum BaseType {
return identifier; return identifier;
} }
public Object decode(ByteBuffer byteBuffer, int scale, int offset) { public Object decode(ByteBuffer byteBuffer, double scale, int offset) {
return baseTypeInterface.decode(byteBuffer, scale, offset); return baseTypeInterface.decode(byteBuffer, scale, offset);
} }
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
baseTypeInterface.encode(byteBuffer, o, scale, offset); baseTypeInterface.encode(byteBuffer, o, scale, offset);
} }

View File

@ -28,22 +28,22 @@ public class BaseTypeByte implements BaseTypeInterface {
} }
@Override @Override
public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
int b = unsigned ? Byte.toUnsignedInt(byteBuffer.get()) : byteBuffer.get(); int b = unsigned ? Byte.toUnsignedInt(byteBuffer.get()) : byteBuffer.get();
if (b < min || b > max) if (b < min || b > max)
return null; return null;
if (b == invalid) if (b == invalid)
return null; return null;
return (b + offset) / scale; return (int) (Math.round(b / scale) - offset);
} }
@Override @Override
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
if (null == o) { if (null == o) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;
} }
int i = ((Number) o).intValue() * scale - offset; int i = (int) ((((Number) o).intValue() + offset) * scale);
if (i < min || i > max) { if (i < min || i > max) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;

View File

@ -19,23 +19,23 @@ public class BaseTypeDouble implements BaseTypeInterface {
} }
@Override @Override
public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
double d = byteBuffer.getDouble(); double d = byteBuffer.getDouble();
if (d < min || d > max) { if (d < min || d > max) {
return null; return null;
} }
if (Double.isNaN(d) || d == invalid) if (Double.isNaN(d) || d == invalid)
return null; return null;
return (d + offset) / scale; return (d / scale) - offset;
} }
@Override @Override
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
if (null == o) { if (null == o) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;
} }
double d = ((Number) o).doubleValue() * scale - offset; double d = (((Number) o).doubleValue() + offset) * scale;
if (d < min || d > max) { if (d < min || d > max) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;

View File

@ -19,23 +19,23 @@ public class BaseTypeFloat implements BaseTypeInterface {
} }
@Override @Override
public Object decode(ByteBuffer byteBuffer, int scale, int offset) { public Object decode(ByteBuffer byteBuffer, double scale, int offset) {
float f = byteBuffer.getFloat(); float f = byteBuffer.getFloat();
if (f < min || f > max) { if (f < min || f > max) {
return null; return null;
} }
if (Float.isNaN(f) || f == invalid) if (Float.isNaN(f) || f == invalid)
return null; return null;
return (f + offset) / scale; return (float) (f / scale) - offset;
} }
@Override @Override
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
if (null == o) { if (null == o) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;
} }
float f = ((Number) o).floatValue() * scale - offset; float f = (float) ((((Number) o).floatValue() + offset) * scale);
if (f < min || f > max) { if (f < min || f > max) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;

View File

@ -26,22 +26,22 @@ public class BaseTypeInt implements BaseTypeInterface {
} }
@Override @Override
public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
long i = unsigned ? Integer.toUnsignedLong(byteBuffer.getInt()) : byteBuffer.getInt(); long i = unsigned ? Integer.toUnsignedLong(byteBuffer.getInt()) : byteBuffer.getInt();
if (i < min || i > max) if (i < min || i > max)
return null; return null;
if (i == invalid) if (i == invalid)
return null; return null;
return ((i + offset) / scale); return (Math.round(i / scale) - offset);
} }
@Override @Override
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
if (null == o) { if (null == o) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;
} }
long l = ((Number) o).longValue() * scale - offset; long l = (long) ((((Number) o).longValue() + offset) * scale);
if (l < min || l > max) { if (l < min || l > max) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;

View File

@ -5,9 +5,9 @@ import java.nio.ByteBuffer;
public interface BaseTypeInterface { public interface BaseTypeInterface {
int getByteSize(); int getByteSize();
Object decode(ByteBuffer byteBuffer, int scale, int offset); Object decode(ByteBuffer byteBuffer, double scale, int offset);
void encode(ByteBuffer byteBuffer, Object o, int scale, int offset); void encode(ByteBuffer byteBuffer, Object o, double scale, int offset);
void invalidate(ByteBuffer byteBuffer); void invalidate(ByteBuffer byteBuffer);
} }

View File

@ -1,5 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes; package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -27,22 +28,22 @@ public class BaseTypeLong implements BaseTypeInterface {
} }
@Override @Override
public Object decode(ByteBuffer byteBuffer, int scale, int offset) { public Object decode(ByteBuffer byteBuffer, double scale, int offset) {
BigInteger i = unsigned ? BigInteger.valueOf(byteBuffer.getLong() & 0xFFFFFFFFFFFFFFFFL) : BigInteger.valueOf(byteBuffer.getLong()); BigInteger i = unsigned ? BigInteger.valueOf(byteBuffer.getLong() & 0xFFFFFFFFFFFFFFFFL) : BigInteger.valueOf(byteBuffer.getLong());
if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0)) if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0))
return null; return null;
if (i.compareTo(BigInteger.valueOf(invalid)) == 0) if (i.compareTo(BigInteger.valueOf(invalid)) == 0)
return null; return null;
return (i.longValue() + offset) / scale; return new BigDecimal(i).divide(BigDecimal.valueOf(scale)).subtract(BigDecimal.valueOf(offset)).toBigInteger().longValue();
} }
@Override @Override
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
if (null == o) { if (null == o) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;
} }
BigInteger i = BigInteger.valueOf(((Number) o).longValue() * scale - offset); BigInteger i = new BigDecimal(((Number) o).longValue()).multiply(BigDecimal.valueOf(scale)).add(BigDecimal.valueOf(offset)).toBigInteger();
if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0)) { if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0)) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;

View File

@ -26,22 +26,22 @@ public class BaseTypeShort implements BaseTypeInterface {
} }
@Override @Override
public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
int s = unsigned ? Short.toUnsignedInt(byteBuffer.getShort()) : byteBuffer.getShort(); int s = unsigned ? Short.toUnsignedInt(byteBuffer.getShort()) : byteBuffer.getShort();
if (s < min || s > max) if (s < min || s > max)
return null; return null;
if (s == invalid) if (s == invalid)
return null; return null;
return (s + offset) / scale; return (int) Math.round(s / scale) - offset;
} }
@Override @Override
public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
if (null == o) { if (null == o) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;
} }
int i = ((Number) o).intValue() * scale - offset; int i = (int) ((((Number) o).intValue() + offset) * scale);
if (i < min || i > max) { if (i < min || i > max) {
invalidate(byteBuffer); invalidate(byteBuffer);
return; return;

View File

@ -7,7 +7,7 @@ import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.Garmin
public class FieldDefinitionTimestamp extends FieldDefinition { public class FieldDefinitionTimestamp extends FieldDefinition {
public FieldDefinitionTimestamp(int localNumber, int size, BaseType baseType, String name) { public FieldDefinitionTimestamp(int localNumber, int size, BaseType baseType, String name) {
super(localNumber, size, baseType, name, 1, GARMIN_TIME_EPOCH); super(localNumber, size, baseType, name, 1, -GARMIN_TIME_EPOCH);
} }
// @Override // @Override