Garmin: Improve field cast safety

This commit is contained in:
José Rebelo
2026-01-13 22:08:54 +00:00
parent 8dad0e1da6
commit 0bdb76c6d4
3 changed files with 66 additions and 19 deletions
@@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import androidx.annotation.NonNull;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -18,8 +19,11 @@ import nodomain.freeyourgadget.gadgetbridge.util.GBToStringBuilder;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType.STRING;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RecordData {
private static final Logger LOG = LoggerFactory.getLogger(RecordData.class);
private final RecordDefinition recordDefinition;
private final RecordHeader recordHeader;
@@ -143,6 +147,59 @@ public class RecordData {
return null;
}
public <T> T getFieldByNumber(int number, final Class<T> clazz) {
return safeCast(getFieldByNumber(number), clazz);
}
public <T> T[] getArrayFieldByNumber(int number, final Class<T> clazz) {
final Object object = getFieldByNumber(number);
if (object == null)
return null;
if (!object.getClass().isArray()) {
final T casted = safeCast(object, clazz);
if (casted != null) {
@SuppressWarnings("unchecked")
final T[] ret = (T[]) Array.newInstance(clazz, 1);
ret[0] = casted;
return ret;
}
}
final Object[] objectsArray = (Object[]) object;
@SuppressWarnings("unchecked")
final T[] ret = (T[]) Array.newInstance(clazz, objectsArray.length);
for (int i = 0; i < objectsArray.length; i++) {
ret[i] = safeCast(objectsArray[i], clazz);
if (ret[i] == null) {
// One of the safe casts failed - abort
return null;
}
}
return ret;
}
private <T> T safeCast(final Object object, final Class<T> clazz) {
if (object == null) {
return null;
}
if (clazz.isInstance(object)) {
return clazz.cast(object);
}
LOG.error(
"Unable to cast {} ({}) to {}, returning null - this is likely a bug. Record: {}",
object,
object.getClass().getSimpleName(),
clazz.getSimpleName(),
this
);
return null;
}
public Object getFieldByName(String name) {
for (FieldData fieldData :
fieldDataList) {
@@ -290,6 +347,7 @@ public class RecordData {
return fieldDefinition.decode(valueHolder);
}
@NonNull
public String toString() {
return "(" + fieldDefinition.getBaseType().name() + "/" + size + ")";
}
@@ -52,7 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@RequiresApi(api = Build.VERSION_CODES.CUR_DEVELOPMENT)
public class FitCodeGen {
private static final String COPYRIGHT_HEADER = """
/* Copyright (C) 2025 Freeyourgadget
/* Copyright (C) 2026 Freeyourgadget
This file is part of Gadgetbridge.
@@ -126,7 +126,8 @@ public class FitCodeGen {
final String result = template
.replace("${copyrightHeader}", (existingHeader.isEmpty() ? COPYRIGHT_HEADER : existingHeader).strip())
.replace("${generatorClass}", Objects.requireNonNull(getClass().getCanonicalName()))
.replace("${switchCases}", String.join("\n", switchCases));
.replace("${switchCases}", String.join("\n", switchCases))
.replaceAll("\\R", System.lineSeparator());
FileUtils.copyStringToFile(result, factoryFile, "replace");
}
@@ -246,18 +247,7 @@ public class FitCodeGen {
@Nullable
public ${fieldType}[] ${getterName}() {
final Object object = getFieldByNumber(${primitiveNumber});
if (object == null)
return null;
if (!object.getClass().isArray()) {
return new ${fieldType}[]{(${fieldType}) object};
}
final Object[] objectsArray = (Object[]) object;
final ${fieldType}[] ret = new ${fieldType}[objectsArray.length];
for (int i = 0; i < objectsArray.length; i++) {
ret[i] = (${fieldType}) objectsArray[i];
}
return ret;
return getArrayFieldByNumber(${primitiveNumber}, ${fieldType}.class);
}
""";
} else {
@@ -266,7 +256,7 @@ public class FitCodeGen {
@Nullable
public ${fieldType} ${getterName}() {
return (${fieldType}) getFieldByNumber(${primitiveNumber});
return getFieldByNumber(${primitiveNumber}, ${fieldType}.class);
}
""";
}
@@ -344,7 +334,9 @@ public class FitCodeGen {
sb.append("}\n");
}
FileUtils.copyStringToFile(sb.toString(), outputFile, "replace");
final String output = sb.toString().replaceAll("\\R", System.lineSeparator());
FileUtils.copyStringToFile(output, outputFile, "replace");
}
public Class<?> getFieldType(final GlobalFITMessage.FieldDefinitionPrimitive primitive) {
@@ -1,3 +0,0 @@
# nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegenFitCodeGen
# is hard coded to uses \n
*.java text eol=lf