mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-24 10:56:50 +01:00
Xiaomi: Fix off-by-one workout summary error (#3916)
This commit is contained in:
parent
7712ea773a
commit
0188820048
@ -501,12 +501,11 @@ public class XiaomiSupport extends AbstractDeviceSupport {
|
||||
try (InputStream in = new FileInputStream(activityFile)) {
|
||||
data = FileUtils.readAll(in, 999999);
|
||||
} catch (final IOException ioe) {
|
||||
LOG.error("Failed to read " + activityFile, ioe);
|
||||
LOG.error("Failed to read {}", activityFile, ioe);
|
||||
continue;
|
||||
}
|
||||
|
||||
final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
|
||||
final byte[] activityData = Arrays.copyOfRange(data, 8, data.length - 4);
|
||||
final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
|
||||
|
||||
final XiaomiActivityParser activityParser = XiaomiActivityParser.create(fileId);
|
||||
@ -516,7 +515,7 @@ public class XiaomiSupport extends AbstractDeviceSupport {
|
||||
}
|
||||
|
||||
try {
|
||||
if (activityParser.parse(this, fileId, activityData)) {
|
||||
if (activityParser.parse(this, fileId, data)) {
|
||||
LOG.info("Successfully parsed {}", fileId);
|
||||
} else {
|
||||
LOG.warn("Failed to parse {}", fileId);
|
||||
|
@ -25,14 +25,10 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -98,7 +94,6 @@ public class XiaomiActivityFileFetcher {
|
||||
}
|
||||
|
||||
final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
|
||||
final byte[] activityData = Arrays.copyOfRange(data, 8, data.length - 4);
|
||||
final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
@ -120,7 +115,7 @@ public class XiaomiActivityFileFetcher {
|
||||
}
|
||||
|
||||
try {
|
||||
if (activityParser.parse(mHealthService.getSupport(), fileId, activityData)) {
|
||||
if (activityParser.parse(mHealthService.getSupport(), fileId, data)) {
|
||||
LOG.info("Successfully parsed {}", fileId);
|
||||
} else {
|
||||
LOG.warn("Failed to parse {}", fileId);
|
||||
|
@ -63,6 +63,12 @@ public class DailyDetailsParser extends XiaomiActivityParser {
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
final byte[] header = new byte[headerSize];
|
||||
buf.get(header);
|
||||
|
||||
@ -74,7 +80,7 @@ public class DailyDetailsParser extends XiaomiActivityParser {
|
||||
timestamp.setTime(fileId.getTimestamp());
|
||||
|
||||
final List<XiaomiActivitySample> samples = new ArrayList<>();
|
||||
while (buf.position() < buf.limit()) {
|
||||
while (buf.position() < buf.limit() - 4 /* crc at the end */) {
|
||||
complexParser.reset();
|
||||
|
||||
final XiaomiActivitySample sample = new XiaomiActivitySample();
|
||||
|
@ -53,6 +53,12 @@ public class DailySummaryParser extends XiaomiActivityParser {
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
final byte[] header = new byte[headerSize];
|
||||
buf.get(header);
|
||||
|
||||
|
@ -51,6 +51,11 @@ public class ManualSamplesParser extends XiaomiActivityParser {
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
// Looks like there is no header, it starts right away with samples:
|
||||
// 8A90A965 12 63 <- spo2
|
||||
@ -62,7 +67,7 @@ public class ManualSamplesParser extends XiaomiActivityParser {
|
||||
|
||||
final List<XiaomiManualSample> samples = new ArrayList<>();
|
||||
|
||||
while (buf.position() < buf.limit()) {
|
||||
while (buf.position() < buf.limit() - 4 /* crc at the end */) {
|
||||
final int timestamp = buf.getInt();
|
||||
final int type = buf.get() & 0xff;
|
||||
|
||||
|
@ -59,6 +59,12 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
int versionDependentFields = 0;
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
final byte header = buf.get();
|
||||
|
||||
final int isAwake = buf.get() & 0xff; // 0/1 - more correctly this would be !isSleepFinish
|
||||
|
@ -53,6 +53,11 @@ public class SleepStagesParser extends XiaomiActivityParser {
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
// over 4 days
|
||||
// first 2 bytes: always FF FF
|
||||
|
@ -68,6 +68,12 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
final byte[] header = new byte[headerSize];
|
||||
buf.get(header);
|
||||
|
||||
@ -80,7 +86,7 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
|
||||
final ActivityTrack activityTrack = new ActivityTrack();
|
||||
|
||||
while (buf.position() < buf.limit()) {
|
||||
while (buf.position() < buf.limit() - 4 /* crc at the end */) {
|
||||
final int ts = buf.getInt();
|
||||
final float longitude = buf.getFloat();
|
||||
final float latitude = buf.getFloat();
|
||||
|
@ -73,6 +73,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
@ -83,9 +84,11 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class WorkoutSummaryParser extends XiaomiActivityParser implements ActivitySummaryParser {
|
||||
@ -98,7 +101,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
summary.setStartTime(fileId.getTimestamp()); // due to a bug this has to be set
|
||||
summary.setEndTime(fileId.getTimestamp()); // due to a bug this has to be set
|
||||
summary.setActivityKind(ActivityKind.TYPE_UNKNOWN);
|
||||
summary.setRawSummaryData(ArrayUtils.addAll(fileId.toBytes(), bytes));
|
||||
summary.setRawSummaryData(bytes);
|
||||
|
||||
try {
|
||||
summary = parseBinaryData(summary);
|
||||
@ -138,10 +141,33 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
|
||||
@Override
|
||||
public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary) {
|
||||
final ByteBuffer buf = ByteBuffer.wrap(summary.getRawSummaryData()).order(ByteOrder.LITTLE_ENDIAN);
|
||||
final byte[] data = summary.getRawSummaryData();
|
||||
|
||||
final int arrCrc32 = CheckSums.getCRC32(data, 0, data.length - 4);
|
||||
final int expectedCrc32 = BLETypeConversions.toUint32(data, data.length - 4);
|
||||
|
||||
final ByteBuffer buf;
|
||||
if (arrCrc32 != expectedCrc32) {
|
||||
// If the CRC32 is not valid, we're missing 1 header padding byte due to a previous bug
|
||||
// This previous version also did not include the CRC at the end
|
||||
// More info: https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3916
|
||||
buf = ByteBuffer.allocate(data.length + 1).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.put(data, 0, 7);
|
||||
buf.put((byte) 0);
|
||||
buf.put(data, 7, data.length - 7);
|
||||
buf.flip();
|
||||
} else {
|
||||
// Valid full file, skip crc
|
||||
buf = ByteBuffer.wrap(data, 0, data.length - 4).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(buf);
|
||||
|
||||
final byte fileIdPadding = buf.get();
|
||||
if (fileIdPadding != 0) {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
XiaomiSimpleActivityParser parser = null;
|
||||
|
||||
switch (fileId.getSubtype()) {
|
||||
@ -151,7 +177,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
break;
|
||||
case SPORTS_OUTDOOR_RUNNING:
|
||||
summary.setActivityKind(ActivityKind.TYPE_RUNNING);
|
||||
// TODO
|
||||
parser = getOutdoorWalkingV1Parser(fileId);
|
||||
break;
|
||||
case SPORTS_INDOOR_CYCLING:
|
||||
summary.setActivityKind(ActivityKind.TYPE_INDOOR_CYCLING);
|
||||
@ -197,7 +223,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
final int headerSize;
|
||||
switch (version) {
|
||||
case 8:
|
||||
headerSize = 5;
|
||||
headerSize = 6;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
|
||||
@ -433,7 +459,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
final int headerSize;
|
||||
switch (version) {
|
||||
case 5:
|
||||
headerSize = 3;
|
||||
headerSize = 4;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
|
||||
|
Loading…
Reference in New Issue
Block a user