1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-08 14:18:08 +02:00

Garmin protocol: add AGPS data checks

This commit is contained in:
kuhy 2024-04-29 16:21:27 +02:00 committed by Daniele Gobbetti
parent ad44459fc4
commit eeb80d712b
4 changed files with 135 additions and 12 deletions

View File

@ -0,0 +1,25 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file;
public enum GarminAgpsDataType {
GLONASS("CPE_GLO.BIN"), QZSS("CPE_QZSS.BIN"), GPS("CPE_GPS.BIN"),
GALILEO("CPE_GAL.BIN");
private final String fileName;
GarminAgpsDataType(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
public static boolean isValidAgpsDataFileName(String fileName) {
for (GarminAgpsDataType type: GarminAgpsDataType.values()) {
if (fileName.equals(type.fileName)) {
return true;
}
}
return false;
}
}

View File

@ -3,15 +3,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GBTarFile;
public class GarminAgpsFile {
private static final Logger LOG = LoggerFactory.getLogger(GarminAgpsFile.class);
public static final int TAR_MAGIC_BYTES_OFFSET = 257;
public static final byte[] TAR_MAGIC_BYTES = new byte[]{
'u', 's', 't', 'a', 'r', '\0'
};
private final byte[] tarBytes;
public GarminAgpsFile(final byte[] tarBytes) {
@ -19,17 +15,18 @@ public class GarminAgpsFile {
}
public boolean isValid() {
if (!ArrayUtils.equals(tarBytes, TAR_MAGIC_BYTES, TAR_MAGIC_BYTES_OFFSET)) {
if (!GBTarFile.isTarFile(tarBytes)) {
LOG.debug("Is not TAR file!");
return false;
}
// TODO Add additional checks.
// Archive usually contains following files:
// CPE_GLO.BIN
// CPE_QZSS.BIN
// CPE_GPS.BIN
// CPE_GAL.BIN
final GBTarFile tarFile = new GBTarFile(tarBytes);
for (final String fileName: tarFile.listFileNames()) {
if (!GarminAgpsDataType.isValidAgpsDataFileName(fileName)) {
LOG.error("Unknown file in TAR archive: {}", fileName);
return false;
}
}
return true;
}

View File

@ -8,13 +8,17 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file.GarminAgpsDataType;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GBTarFile;
public class EphemerisHandler {
private static final Logger LOG = LoggerFactory.getLogger(EphemerisHandler.class);
private static final String QUERY_CONSTELLATIONS = "constellations";
private final GarminSupport deviceSupport;
public EphemerisHandler(GarminSupport deviceSupport) {
@ -23,6 +27,10 @@ public class EphemerisHandler {
public byte[] handleEphemerisRequest(final String path, final Map<String, String> query) {
try {
if (!query.containsKey(QUERY_CONSTELLATIONS)) {
LOG.debug("Query does not contain information about constellations; skipping request.");
return null;
}
final File agpsFile = deviceSupport.getAgpsFile();
if (!agpsFile.exists() || !agpsFile.isFile()) {
LOG.info("File with AGPS data does not exist.");
@ -30,6 +38,20 @@ public class EphemerisHandler {
}
try(InputStream agpsIn = new FileInputStream(agpsFile)) {
final byte[] rawBytes = FileUtils.readAll(agpsIn, 1024 * 1024); // 1MB, they're usually ~60KB
final GBTarFile tarFile = new GBTarFile(rawBytes);
final String[] requestedConstellations = Objects.requireNonNull(query.get(QUERY_CONSTELLATIONS)).split(",");
for (final String constellation: requestedConstellations) {
try {
final GarminAgpsDataType garminAgpsDataType = GarminAgpsDataType.valueOf(constellation);
if (!tarFile.containsFile(garminAgpsDataType.getFileName())) {
LOG.error("AGPS archive is missing requested file: {}", garminAgpsDataType.getFileName());
return null;
}
} catch (IllegalArgumentException e) {
LOG.error("Device requested unsupported AGPS data type: {}", constellation);
return null;
}
}
LOG.info("Sending new AGPS data to the device.");
return rawBytes;
}

View File

@ -0,0 +1,79 @@
package nodomain.freeyourgadget.gadgetbridge.util;
import org.bouncycastle.shaded.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class GBTarFile {
private static final Logger LOG = LoggerFactory.getLogger(GBTarFile.class);
private final byte[] tarBytes;
public static final int TAR_MAGIC_BYTES_OFFSET = 257;
public static final byte[] TAR_MAGIC_BYTES = new byte[]{
'u', 's', 't', 'a', 'r', '\0'
};
public static final int TAR_BLOCK_SIZE = 512;
public static final int TAR_HEADER_FILE_NAME_OFFSET = 0;
public static final int TAR_HEADER_FILE_NAME_LENGTH = 100;
public static final int TAR_HEADER_FILE_SIZE_OFFSET = 124;
public static final int TAR_HEADER_FILE_SIZE_LENGTH = 12;
public GBTarFile(byte[] tarBytes) {
this.tarBytes = tarBytes;
}
public static boolean isTarFile(byte[] data) {
return ArrayUtils.equals(data, TAR_MAGIC_BYTES, TAR_MAGIC_BYTES_OFFSET);
}
public List<String> listFileNames() {
final List<String> fileNames = new ArrayList<>();
for (TarHeader header: listHeaders()) {
fileNames.add(header.fileName);
}
return fileNames;
}
public boolean containsFile(String fileName) {
for (TarHeader header: listHeaders()) {
if (fileName.equals(header.fileName)) {
return true;
}
}
return false;
}
private List<TarHeader> listHeaders() {
final List<TarHeader> headers = new ArrayList<>();
int offset = 0;
while (ArrayUtils.equals(tarBytes, TAR_MAGIC_BYTES, offset + TAR_MAGIC_BYTES_OFFSET)) {
final TarHeader tarHeader = new TarHeader(Arrays.copyOfRange(tarBytes, offset, offset + TAR_BLOCK_SIZE));
headers.add(tarHeader);
offset += (((tarHeader.fileSize + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE) + 1) * TAR_BLOCK_SIZE;
}
return headers;
}
private static class TarHeader {
final String fileName;
final int fileSize;
public TarHeader(byte[] header) {
fileName = parseString(header, TAR_HEADER_FILE_NAME_OFFSET, TAR_HEADER_FILE_NAME_LENGTH);
fileSize = Integer.parseInt(parseString(header, TAR_HEADER_FILE_SIZE_OFFSET, TAR_HEADER_FILE_SIZE_LENGTH).trim(), 8);
}
private static String parseString(final byte[] data, final int offset, final int maxLength) {
int length = 0;
while (length < maxLength && offset + length < data.length && data[offset + length] != 0) {
length++;
}
return new String(data, offset, length, StandardCharsets.US_ASCII);
}
}
}