mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 01:55:50 +01:00
Garmin protocol: add AGPS data checks
This commit is contained in:
parent
22fafebd91
commit
91c2408170
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user