mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-15 14:39:26 +01:00
Garmin protocol: create helper class GarminByteBufferReader
separate the logic specific for GFDI messages from the generally useful logic. Also centralize the logging in case of leftover bytes while parsing GFDI messages.
This commit is contained in:
parent
8524426b70
commit
b2f995b736
@ -0,0 +1,79 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class GarminByteBufferReader {
|
||||||
|
protected final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
public GarminByteBufferReader(byte[] data) {
|
||||||
|
this.byteBuffer = ByteBuffer.wrap(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer asReadOnlyBuffer() {
|
||||||
|
return byteBuffer.asReadOnlyBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setByteOrder(ByteOrder byteOrder) {
|
||||||
|
this.byteBuffer.order(byteOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readByte() {
|
||||||
|
if (!byteBuffer.hasRemaining()) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return Byte.toUnsignedInt(byteBuffer.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readShort() {
|
||||||
|
if (byteBuffer.remaining() < 2) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return Short.toUnsignedInt(byteBuffer.getShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readInt() {
|
||||||
|
if (byteBuffer.remaining() < 4) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return byteBuffer.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long readLong() {
|
||||||
|
if (byteBuffer.remaining() < 8) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return byteBuffer.getLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float readFloat32() {
|
||||||
|
if (byteBuffer.remaining() < 4) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return byteBuffer.getFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double readFloat64() {
|
||||||
|
if (byteBuffer.remaining() < 8) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return byteBuffer.getDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readString() {
|
||||||
|
final int size = readByte();
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
if (byteBuffer.remaining() < size) throw new IllegalStateException();
|
||||||
|
byteBuffer.get(bytes);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readBytes(int size) {
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
|
||||||
|
if (byteBuffer.remaining() < size) throw new IllegalStateException();
|
||||||
|
byteBuffer.get(bytes);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
|||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
|
|
||||||
public class DevFieldDefinition {
|
public class DevFieldDefinition {
|
||||||
@ -20,10 +20,10 @@ public class DevFieldDefinition {
|
|||||||
this.valueHolder = ByteBuffer.allocate(size);
|
this.valueHolder = ByteBuffer.allocate(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DevFieldDefinition parseIncoming(MessageReader reader) {
|
public static DevFieldDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader) {
|
||||||
int number = reader.readByte();
|
int number = garminByteBufferReader.readByte();
|
||||||
int size = reader.readByte();
|
int size = garminByteBufferReader.readByte();
|
||||||
int developerDataIndex = reader.readByte();
|
int developerDataIndex = garminByteBufferReader.readByte();
|
||||||
|
|
||||||
return new DevFieldDefinition(number, size, developerDataIndex, "");
|
return new DevFieldDefinition(number, size, developerDataIndex, "");
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
|||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
|
|
||||||
public class FieldDefinition implements FieldInterface {
|
public class FieldDefinition implements FieldInterface {
|
||||||
@ -27,11 +27,10 @@ public class FieldDefinition implements FieldInterface {
|
|||||||
this(localNumber, size, baseType, name, 1, 0);
|
this(localNumber, size, baseType, name, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FieldDefinition parseIncoming(MessageReader reader) {
|
public static FieldDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader) {
|
||||||
int localNumber = reader.readByte();
|
int localNumber = garminByteBufferReader.readByte();
|
||||||
int size = reader.readByte();
|
int size = garminByteBufferReader.readByte();
|
||||||
int baseTypeIdentifier = reader.readByte();
|
int baseTypeIdentifier = garminByteBufferReader.readByte();
|
||||||
|
|
||||||
BaseType baseType = BaseType.fromIdentifier(baseTypeIdentifier);
|
BaseType baseType = BaseType.fromIdentifier(baseTypeIdentifier);
|
||||||
|
|
||||||
if (size % baseType.getSize() != 0) {
|
if (size % baseType.getSize() != 0) {
|
||||||
|
@ -6,8 +6,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||||
|
|
||||||
@ -44,10 +43,10 @@ public class RecordData {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseDataMessage(MessageReader reader) {
|
public void parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
||||||
reader.setByteOrder(valueHolder.order());
|
garminByteBufferReader.setByteOrder(valueHolder.order());
|
||||||
for (FieldData fieldData : fieldDataList) {
|
for (FieldData fieldData : fieldDataList) {
|
||||||
fieldData.parseDataMessage(reader);
|
fieldData.parseDataMessage(garminByteBufferReader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,9 +166,9 @@ public class RecordData {
|
|||||||
valueHolder.position(position);
|
valueHolder.position(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseDataMessage(MessageReader reader) {
|
private void parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
||||||
goToPosition();
|
goToPosition();
|
||||||
valueHolder.put(reader.readBytes(size));
|
valueHolder.put(garminByteBufferReader.readBytes(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encode(Object... objects) {
|
public void encode(Object... objects) {
|
||||||
|
@ -4,7 +4,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
|
|
||||||
public class RecordDefinition {
|
public class RecordDefinition {
|
||||||
@ -33,35 +33,33 @@ public class RecordDefinition {
|
|||||||
this(recordHeader, byteOrder, mesgType, globalMesgNum, null, null);
|
this(recordHeader, byteOrder, mesgType, globalMesgNum, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecordDefinition parseIncoming(MessageReader reader, RecordHeader recordHeader) {
|
public static RecordDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader, RecordHeader recordHeader) {
|
||||||
if (!recordHeader.isDefinition())
|
if (!recordHeader.isDefinition())
|
||||||
return null;
|
return null;
|
||||||
reader.readByte();//ignore
|
garminByteBufferReader.readByte();//ignore
|
||||||
ByteOrder byteOrder = reader.readByte() == 0x01 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
|
ByteOrder byteOrder = garminByteBufferReader.readByte() == 0x01 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
|
||||||
reader.setByteOrder(byteOrder);
|
garminByteBufferReader.setByteOrder(byteOrder);
|
||||||
final int globalMesgNum = reader.readShort();
|
final int globalMesgNum = garminByteBufferReader.readShort();
|
||||||
|
|
||||||
RecordDefinition definitionMessage = new RecordDefinition(recordHeader, byteOrder, recordHeader.getMesgType(), globalMesgNum);
|
RecordDefinition definitionMessage = new RecordDefinition(recordHeader, byteOrder, recordHeader.getMesgType(), globalMesgNum);
|
||||||
|
|
||||||
final int numFields = reader.readByte();
|
final int numFields = garminByteBufferReader.readByte();
|
||||||
List<FieldDefinition> fieldDefinitions = new ArrayList<>(numFields);
|
List<FieldDefinition> fieldDefinitions = new ArrayList<>(numFields);
|
||||||
for (int i = 0; i < numFields; i++) {
|
for (int i = 0; i < numFields; i++) {
|
||||||
fieldDefinitions.add(FieldDefinition.parseIncoming(reader));
|
fieldDefinitions.add(FieldDefinition.parseIncoming(garminByteBufferReader));
|
||||||
}
|
}
|
||||||
|
|
||||||
definitionMessage.setFieldDefinitions(fieldDefinitions);
|
definitionMessage.setFieldDefinitions(fieldDefinitions);
|
||||||
|
|
||||||
if (recordHeader.isDeveloperData()) {
|
if (recordHeader.isDeveloperData()) {
|
||||||
final int numDevFields = reader.readByte();
|
final int numDevFields = garminByteBufferReader.readByte();
|
||||||
List<DevFieldDefinition> devFieldDefinitions = new ArrayList<>(numDevFields);
|
List<DevFieldDefinition> devFieldDefinitions = new ArrayList<>(numDevFields);
|
||||||
for (int i = 0; i < numDevFields; i++) {
|
for (int i = 0; i < numDevFields; i++) {
|
||||||
devFieldDefinitions.add(DevFieldDefinition.parseIncoming(reader));
|
devFieldDefinitions.add(DevFieldDefinition.parseIncoming(garminByteBufferReader));
|
||||||
}
|
}
|
||||||
definitionMessage.setDevFieldDefinitions(devFieldDefinitions);
|
definitionMessage.setDevFieldDefinitions(devFieldDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
|
|
||||||
return definitionMessage;
|
return definitionMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ public class ConfigurationMessage extends GFDIMessage {
|
|||||||
public static ConfigurationMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static ConfigurationMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
final int endOfPayload = reader.readByte();
|
final int endOfPayload = reader.readByte();
|
||||||
ConfigurationMessage configurationMessage = new ConfigurationMessage(garminMessage, reader.readBytes(endOfPayload - reader.getPosition()));
|
ConfigurationMessage configurationMessage = new ConfigurationMessage(garminMessage, reader.readBytes(endOfPayload - reader.getPosition()));
|
||||||
reader.warnIfLeftover();
|
|
||||||
return configurationMessage;
|
return configurationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ public class CurrentTimeRequestMessage extends GFDIMessage {
|
|||||||
public static CurrentTimeRequestMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static CurrentTimeRequestMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
final int referenceID = reader.readInt();
|
final int referenceID = reader.readInt();
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new CurrentTimeRequestMessage(referenceID, garminMessage);
|
return new CurrentTimeRequestMessage(referenceID, garminMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@ public class DeviceInformationMessage extends GFDIMessage {
|
|||||||
final String deviceName = reader.readString();
|
final String deviceName = reader.readString();
|
||||||
final String deviceModel = reader.readString();
|
final String deviceModel = reader.readString();
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new DeviceInformationMessage(garminMessage, protocolVersion, productNumber, unitNumber, softwareVersion, maxPacketSize, bluetoothFriendlyName, deviceName, deviceModel);
|
return new DeviceInformationMessage(garminMessage, protocolVersion, productNumber, unitNumber, softwareVersion, maxPacketSize, bluetoothFriendlyName, deviceName, deviceModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ public class FindMyPhoneRequestMessage extends GFDIMessage {
|
|||||||
public static FindMyPhoneRequestMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static FindMyPhoneRequestMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
final int duration = reader.readByte();
|
final int duration = reader.readByte();
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new FindMyPhoneRequestMessage(garminMessage, duration);
|
return new FindMyPhoneRequestMessage(garminMessage, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ public class FitDataMessage extends GFDIMessage {
|
|||||||
List<RecordData> recordDataList = new ArrayList<>();
|
List<RecordData> recordDataList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
while (!reader.isEndOfPayload()) {
|
while (reader.remaining() > 0) {
|
||||||
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
|
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
|
||||||
if (recordHeader.isDefinition())
|
if (recordHeader.isDefinition())
|
||||||
return null;
|
return null;
|
||||||
|
@ -24,7 +24,7 @@ public class FitDefinitionMessage extends GFDIMessage {
|
|||||||
public static FitDefinitionMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static FitDefinitionMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
List<RecordDefinition> recordDefinitions = new ArrayList<>();
|
List<RecordDefinition> recordDefinitions = new ArrayList<>();
|
||||||
|
|
||||||
while (!reader.isEndOfPayload()) {
|
while (reader.remaining() > 0) {
|
||||||
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
|
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
|
||||||
recordDefinitions.add(RecordDefinition.parseIncoming(reader, recordHeader));
|
recordDefinitions.add(RecordDefinition.parseIncoming(reader, recordHeader));
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,10 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GFDIStatusMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GFDIStatusMessage;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GenericStatusMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GenericStatusMessage;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public abstract class GFDIMessage {
|
public abstract class GFDIMessage {
|
||||||
public static final int MESSAGE_REQUEST = 5001;
|
public static final int MESSAGE_REQUEST = 5001;
|
||||||
@ -45,6 +47,8 @@ public abstract class GFDIMessage {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("UNHANDLED GFDI MESSAGE TYPE {}, MESSAGE {}", messageType, message);
|
LOG.error("UNHANDLED GFDI MESSAGE TYPE {}, MESSAGE {}", messageType, message);
|
||||||
return new UnhandledMessage(messageType);
|
return new UnhandledMessage(messageType);
|
||||||
|
} finally {
|
||||||
|
messageReader.warnIfLeftover();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,4 +160,53 @@ public abstract class GFDIMessage {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class MessageReader extends GarminByteBufferReader {
|
||||||
|
|
||||||
|
private final int payloadSize;
|
||||||
|
|
||||||
|
public MessageReader(byte[] data) {
|
||||||
|
super(data);
|
||||||
|
this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
this.payloadSize = readShort(); //includes CRC
|
||||||
|
checkSize();
|
||||||
|
checkCRC();
|
||||||
|
this.byteBuffer.limit(payloadSize - 2); //remove CRC
|
||||||
|
}
|
||||||
|
|
||||||
|
public int remaining() {
|
||||||
|
return byteBuffer.remaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skip(int offset) {
|
||||||
|
if (remaining() < offset) throw new IllegalStateException();
|
||||||
|
byteBuffer.position(byteBuffer.position() + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSize() {
|
||||||
|
if (payloadSize != byteBuffer.capacity()) {
|
||||||
|
LOG.error("Received GFDI packet with invalid length: {} vs {}", payloadSize, byteBuffer.capacity());
|
||||||
|
throw new IllegalArgumentException("Received GFDI packet with invalid length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCRC() {
|
||||||
|
final int crc = Short.toUnsignedInt(byteBuffer.getShort(payloadSize - 2));
|
||||||
|
final int correctCrc = ChecksumCalculator.computeCrc(byteBuffer.asReadOnlyBuffer(), 0, payloadSize - 2);
|
||||||
|
if (crc != correctCrc) {
|
||||||
|
LOG.error("Received GFDI packet with invalid CRC: {} vs {}", crc, correctCrc);
|
||||||
|
throw new IllegalArgumentException("Received GFDI packet with invalid CRC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warnIfLeftover() {
|
||||||
|
if (byteBuffer.hasRemaining() && byteBuffer.position() < (byteBuffer.limit())) {
|
||||||
|
int pos = byteBuffer.position();
|
||||||
|
int numBytes = (byteBuffer.limit()) - byteBuffer.position();
|
||||||
|
byte[] leftover = new byte[numBytes];
|
||||||
|
byteBuffer.get(leftover);
|
||||||
|
byteBuffer.position(pos);
|
||||||
|
LOG.warn("Leftover bytes when parsing message. Bytes: {}, complete message: {}", GB.hexdump(leftover), GB.hexdump(byteBuffer.array()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
||||||
|
|
||||||
public class MessageReader {
|
|
||||||
protected static final Logger LOG = LoggerFactory.getLogger(MessageReader.class);
|
|
||||||
|
|
||||||
private final ByteBuffer byteBuffer;
|
|
||||||
private final int payloadSize;
|
|
||||||
|
|
||||||
public MessageReader(byte[] data) {
|
|
||||||
this.byteBuffer = ByteBuffer.wrap(data);
|
|
||||||
this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
this.payloadSize = readShort();
|
|
||||||
|
|
||||||
checkSize();
|
|
||||||
checkCRC();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setByteOrder(ByteOrder byteOrder) {
|
|
||||||
this.byteBuffer.order(byteOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEof() {
|
|
||||||
return !byteBuffer.hasRemaining();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEndOfPayload() {
|
|
||||||
return byteBuffer.position() >= payloadSize - 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPosition() {
|
|
||||||
return byteBuffer.position();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void skip(int offset) {
|
|
||||||
if (byteBuffer.remaining() < offset) throw new IllegalStateException();
|
|
||||||
byteBuffer.position(byteBuffer.position() + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readByte() {
|
|
||||||
if (!byteBuffer.hasRemaining()) throw new IllegalStateException();
|
|
||||||
|
|
||||||
return Byte.toUnsignedInt(byteBuffer.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readShort() {
|
|
||||||
if (byteBuffer.remaining() < 2) throw new IllegalStateException();
|
|
||||||
|
|
||||||
return Short.toUnsignedInt(byteBuffer.getShort());
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readInt() {
|
|
||||||
if (byteBuffer.remaining() < 4) throw new IllegalStateException();
|
|
||||||
|
|
||||||
return byteBuffer.getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long readLong() {
|
|
||||||
if (byteBuffer.remaining() < 8) throw new IllegalStateException();
|
|
||||||
|
|
||||||
return byteBuffer.getLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public float readFloat32() {
|
|
||||||
if (byteBuffer.remaining() < 4) throw new IllegalStateException();
|
|
||||||
|
|
||||||
return byteBuffer.getFloat();
|
|
||||||
}
|
|
||||||
|
|
||||||
public double readFloat64() {
|
|
||||||
if (byteBuffer.remaining() < 8) throw new IllegalStateException();
|
|
||||||
|
|
||||||
return byteBuffer.getDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String readString() {
|
|
||||||
final int size = readByte();
|
|
||||||
byte[] bytes = new byte[size];
|
|
||||||
if (byteBuffer.remaining() < size) throw new IllegalStateException();
|
|
||||||
byteBuffer.get(bytes);
|
|
||||||
return new String(bytes, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] readBytes(int size) {
|
|
||||||
byte[] bytes = new byte[size];
|
|
||||||
|
|
||||||
if (byteBuffer.remaining() < size) throw new IllegalStateException();
|
|
||||||
byteBuffer.get(bytes);
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getCapacity() {
|
|
||||||
return byteBuffer.capacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkSize() {
|
|
||||||
if (payloadSize > getCapacity()) {
|
|
||||||
LOG.error("Received GFDI packet with invalid length: {} vs {}", payloadSize, getCapacity());
|
|
||||||
throw new IllegalArgumentException("Received GFDI packet with invalid length");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCRC() {
|
|
||||||
final int crc = Short.toUnsignedInt(byteBuffer.getShort(payloadSize - 2));
|
|
||||||
final int correctCrc = ChecksumCalculator.computeCrc(byteBuffer.asReadOnlyBuffer(), 0, payloadSize - 2);
|
|
||||||
if (crc != correctCrc) {
|
|
||||||
LOG.error("Received GFDI packet with invalid CRC: {} vs {}", crc, correctCrc);
|
|
||||||
throw new IllegalArgumentException("Received GFDI packet with invalid CRC");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void warnIfLeftover() {
|
|
||||||
if (byteBuffer.hasRemaining() && byteBuffer.position() < (byteBuffer.limit() - 2)) {
|
|
||||||
int pos = byteBuffer.position();
|
|
||||||
int numBytes = (byteBuffer.limit() - 2) - byteBuffer.position();
|
|
||||||
byte[] leftover = new byte[numBytes];
|
|
||||||
byteBuffer.get(leftover);
|
|
||||||
byteBuffer.position(pos);
|
|
||||||
LOG.warn("Leftover bytes when parsing message. Bytes: {}, complete message: {}", GB.hexdump(leftover), GB.hexdump(byteBuffer.array()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ public class MusicControlCapabilitiesMessage extends GFDIMessage {
|
|||||||
public static MusicControlCapabilitiesMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static MusicControlCapabilitiesMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
final int supportedCapabilities = reader.readByte();
|
final int supportedCapabilities = reader.readByte();
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new MusicControlCapabilitiesMessage(garminMessage, supportedCapabilities);
|
return new MusicControlCapabilitiesMessage(garminMessage, supportedCapabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ public class MusicControlMessage extends GFDIMessage {
|
|||||||
public static MusicControlMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static MusicControlMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
MusicControlCapabilitiesMessage.GarminMusicControlCommand command = commands[reader.readByte()];
|
MusicControlCapabilitiesMessage.GarminMusicControlCommand command = commands[reader.readByte()];
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new MusicControlMessage(garminMessage, command);
|
return new MusicControlMessage(garminMessage, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@ public class ProtobufMessage extends GFDIMessage {
|
|||||||
final int protobufDataLength = reader.readInt();
|
final int protobufDataLength = reader.readInt();
|
||||||
final byte[] messageBytes = reader.readBytes(protobufDataLength);
|
final byte[] messageBytes = reader.readBytes(protobufDataLength);
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new ProtobufMessage(garminMessage, requestID, dataOffset, totalProtobufLength, protobufDataLength, messageBytes, false);
|
return new ProtobufMessage(garminMessage, requestID, dataOffset, totalProtobufLength, protobufDataLength, messageBytes, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
|
||||||
|
|
||||||
public class FitDataStatusMessage extends GFDIStatusMessage {
|
public class FitDataStatusMessage extends GFDIStatusMessage {
|
||||||
|
|
||||||
private final Status status;
|
private final Status status;
|
||||||
@ -27,7 +25,6 @@ public class FitDataStatusMessage extends GFDIStatusMessage {
|
|||||||
final Status status = Status.fromCode(reader.readByte());
|
final Status status = Status.fromCode(reader.readByte());
|
||||||
final FitDataStatusCode fitDataStatusCode = FitDataStatusCode.fromCode(reader.readByte());
|
final FitDataStatusCode fitDataStatusCode = FitDataStatusCode.fromCode(reader.readByte());
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new FitDataStatusMessage(garminMessage, status, fitDataStatusCode);
|
return new FitDataStatusMessage(garminMessage, status, fitDataStatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
|
||||||
|
|
||||||
public class FitDefinitionStatusMessage extends GFDIStatusMessage {
|
public class FitDefinitionStatusMessage extends GFDIStatusMessage {
|
||||||
|
|
||||||
private final Status status;
|
private final Status status;
|
||||||
@ -26,7 +24,6 @@ public class FitDefinitionStatusMessage extends GFDIStatusMessage {
|
|||||||
final Status status = Status.fromCode(reader.readByte());
|
final Status status = Status.fromCode(reader.readByte());
|
||||||
final FitDefinitionStatusCode fitDefinitionStatusCode = FitDefinitionStatusCode.fromCode(reader.readByte());
|
final FitDefinitionStatusCode fitDefinitionStatusCode = FitDefinitionStatusCode.fromCode(reader.readByte());
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new FitDefinitionStatusMessage(garminMessage, status, fitDefinitionStatusCode);
|
return new FitDefinitionStatusMessage(garminMessage, status, fitDefinitionStatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,29 +2,28 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
|
|||||||
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
|
||||||
|
|
||||||
public abstract class GFDIStatusMessage extends GFDIMessage {
|
public abstract class GFDIStatusMessage extends GFDIMessage {
|
||||||
private Status status;
|
private Status status;
|
||||||
|
|
||||||
public static GFDIStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
public static GFDIStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
|
||||||
final GarminMessage originalGarminMessage = GFDIMessage.GarminMessage.fromId(reader.readShort());
|
int originalMessageType = reader.readShort();
|
||||||
|
final GarminMessage originalGarminMessage = GFDIMessage.GarminMessage.fromId(originalMessageType);
|
||||||
if (GarminMessage.PROTOBUF_REQUEST.equals(originalGarminMessage) || GarminMessage.PROTOBUF_RESPONSE.equals(originalGarminMessage)) {
|
if (GarminMessage.PROTOBUF_REQUEST.equals(originalGarminMessage) || GarminMessage.PROTOBUF_RESPONSE.equals(originalGarminMessage)) {
|
||||||
return ProtobufStatusMessage.parseIncoming(reader, garminMessage);
|
return ProtobufStatusMessage.parseIncoming(reader, originalGarminMessage);
|
||||||
} else if (GarminMessage.FIT_DEFINITION.equals(originalGarminMessage)) {
|
} else if (GarminMessage.FIT_DEFINITION.equals(originalGarminMessage)) {
|
||||||
return FitDefinitionStatusMessage.parseIncoming(reader, garminMessage);
|
return FitDefinitionStatusMessage.parseIncoming(reader, originalGarminMessage);
|
||||||
} else if (GarminMessage.FIT_DATA.equals(originalGarminMessage)) {
|
} else if (GarminMessage.FIT_DATA.equals(originalGarminMessage)) {
|
||||||
return FitDataStatusMessage.parseIncoming(reader, garminMessage);
|
return FitDataStatusMessage.parseIncoming(reader, originalGarminMessage);
|
||||||
} else {
|
} else {
|
||||||
final Status status = Status.fromCode(reader.readByte());
|
final Status status = Status.fromCode(reader.readByte());
|
||||||
|
|
||||||
if (Status.ACK == status) {
|
if (Status.ACK == status) {
|
||||||
LOG.info("Received ACK for message {}", originalGarminMessage.name());
|
LOG.info("Received ACK for message {}", originalGarminMessage.name());
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Received {} for message {}", status, originalGarminMessage.name());
|
LOG.warn("Received {} for message {}", status, (null == originalGarminMessage) ? originalMessageType : originalGarminMessage.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new GenericStatusMessage(garminMessage, status);
|
return new GenericStatusMessage(garminMessage, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
|
|
||||||
public class ProtobufStatusMessage extends GFDIStatusMessage {
|
public class ProtobufStatusMessage extends GFDIStatusMessage {
|
||||||
@ -35,7 +34,6 @@ public class ProtobufStatusMessage extends GFDIStatusMessage {
|
|||||||
final ProtobufChunkStatus protobufStatus = ProtobufChunkStatus.fromCode(reader.readByte());
|
final ProtobufChunkStatus protobufStatus = ProtobufChunkStatus.fromCode(reader.readByte());
|
||||||
final ProtobufStatusCode error = ProtobufStatusCode.fromCode(reader.readByte());
|
final ProtobufStatusCode error = ProtobufStatusCode.fromCode(reader.readByte());
|
||||||
|
|
||||||
reader.warnIfLeftover();
|
|
||||||
return new ProtobufStatusMessage(garminMessage, status, requestID, dataOffset, protobufStatus, error, false);
|
return new ProtobufStatusMessage(garminMessage, status, requestID, dataOffset, protobufStatus, error, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user