Zepp OS: Allow upload of UIHH AGPS bundles

This commit is contained in:
José Rebelo
2025-04-07 13:14:15 +01:00
parent 2579b97435
commit f000171535
4 changed files with 94 additions and 21 deletions
+1
View File
@@ -161,6 +161,7 @@
<w>trofimov</w> <w>trofimov</w>
<w>tysiak</w> <w>tysiak</w>
<w>uart</w> <w>uart</w>
<w>uihh</w>
<w>uint</w> <w>uint</w>
<w>utsob</w> <w>utsob</w>
<w>uuids</w> <w>uuids</w>
@@ -28,6 +28,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
@@ -45,6 +46,10 @@ public class UIHHContainer {
return files; return files;
} }
public List<FileType> getFileTypes() {
return files.stream().map(FileEntry::getType).collect(Collectors.toList());
}
public byte[] getFile(final FileType fileType) { public byte[] getFile(final FileType fileType) {
for (final FileEntry file : files) { for (final FileEntry file : files) {
if (file.getType() == fileType) { if (file.getType() == fileType) {
@@ -91,8 +96,8 @@ public class UIHHContainer {
return null; return null;
} }
final int crc32 = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, 12, 12 + 4)); final int crc32 = BLETypeConversions.toUint32(bytes, 12);
final int length = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, 22, 22 + 4)); final int length = BLETypeConversions.toUint32(bytes, 22);
if (length + 32 != bytes.length) { if (length + 32 != bytes.length) {
LOG.error("Length mismatch between header and bytes: {}/{}", length, bytes.length); LOG.error("Length mismatch between header and bytes: {}/{}", length, bytes.length);
@@ -122,14 +127,14 @@ public class UIHHContainer {
final FileType type = FileType.fromValue(bytes[i]); final FileType type = FileType.fromValue(bytes[i]);
if (type == null) { if (type == null) {
LOG.error("Unknown type byte {} at position {}", String.format("0x%x", bytes[i], i)); LOG.error("Unknown type byte {} at position {}", String.format("0x%x", bytes[i]), i);
return null; return null;
} }
i++; i++;
final int fileLength = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, i, i + 4)); final int fileLength = BLETypeConversions.toUint32(bytes, i);
i += 4; i += 4;
final int fileCrc32 = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, i, i + 4)); final int fileCrc32 = BLETypeConversions.toUint32(bytes, i);
i += 4; i += 4;
if (i + fileLength > bytes.length) { if (i + fileLength > bytes.length) {
@@ -21,6 +21,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.UIHHContainer; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.UIHHContainer;
import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile; import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
@@ -29,18 +31,22 @@ import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
public class ZeppOsAgpsFile { public class ZeppOsAgpsFile {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsFile.class); private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsFile.class);
private final byte[] zipBytes; private final byte[] fileBytes;
public ZeppOsAgpsFile(final byte[] zipBytes) { public ZeppOsAgpsFile(final byte[] fileBytes) {
this.zipBytes = zipBytes; this.fileBytes = fileBytes;
} }
public boolean isValid() { public boolean isValid() {
if (!GBZipFile.isZipFile(zipBytes)) { if (GBZipFile.isZipFile(fileBytes)) {
return false; return isValidAsEpoZip();
} else {
return isValidAsUihh();
} }
}
final GBZipFile zipFile = new GBZipFile(zipBytes); private boolean isValidAsEpoZip() {
final GBZipFile zipFile = new GBZipFile(fileBytes);
try { try {
final byte[] manifestBin = zipFile.getFileFromZip("META-INF/MANIFEST.MF"); final byte[] manifestBin = zipFile.getFileFromZip("META-INF/MANIFEST.MF");
@@ -61,22 +67,64 @@ public class ZeppOsAgpsFile {
LOG.error("Failed to parse read MANIFEST or check file", e); LOG.error("Failed to parse read MANIFEST or check file", e);
} }
return false; return true;
}
private boolean isValidAsUihh() {
final UIHHContainer uihh = UIHHContainer.fromRawBytes(fileBytes);
if (uihh == null) {
return false;
}
final List<UIHHContainer.FileType> fileTypes = uihh.getFileTypes();
final List<UIHHContainer.FileType> expectedFileTypes = Arrays.asList(
UIHHContainer.FileType.GPS_ALM_BIN,
UIHHContainer.FileType.GLN_ALM_BIN,
UIHHContainer.FileType.LLE_BDS_LLE,
UIHHContainer.FileType.LLE_GPS_LLE,
UIHHContainer.FileType.LLE_GLO_LLE,
UIHHContainer.FileType.LLE_GAL_LLE,
UIHHContainer.FileType.LLE_QZSS_LLE
);
if (fileTypes.size() != expectedFileTypes.size()) {
LOG.warn("uihh file types mismatch - expected {}, found {}", expectedFileTypes.size(), fileTypes.size());
return false;
}
for (final UIHHContainer.FileType fileType : expectedFileTypes) {
if (!fileTypes.contains(fileType)) {
LOG.warn("uihh is missing file type {}", fileType);
return false;
}
}
return true;
} }
public byte[] getUihhBytes() { public byte[] getUihhBytes() {
final UIHHContainer uihh = new UIHHContainer(); if (GBZipFile.isZipFile(fileBytes)) {
// EPO zip - repackage into UIHH
final UIHHContainer uihh = new UIHHContainer();
final GBZipFile zipFile = new GBZipFile(zipBytes); final GBZipFile zipFile = new GBZipFile(fileBytes);
try { try {
uihh.addFile(UIHHContainer.FileType.AGPS_EPO_GR_3, zipFile.getFileFromZip("EPO_GR_3.DAT")); uihh.addFile(UIHHContainer.FileType.AGPS_EPO_GR_3, zipFile.getFileFromZip("EPO_GR_3.DAT"));
uihh.addFile(UIHHContainer.FileType.AGPS_EPO_GAL_7, zipFile.getFileFromZip("EPO_GAL_7.DAT")); uihh.addFile(UIHHContainer.FileType.AGPS_EPO_GAL_7, zipFile.getFileFromZip("EPO_GAL_7.DAT"));
uihh.addFile(UIHHContainer.FileType.AGPS_EPO_BDS_3, zipFile.getFileFromZip("EPO_BDS_3.DAT")); uihh.addFile(UIHHContainer.FileType.AGPS_EPO_BDS_3, zipFile.getFileFromZip("EPO_BDS_3.DAT"));
} catch (final ZipFileException e) { } catch (final ZipFileException e) {
throw new IllegalStateException("Failed to read file from zip", e); throw new IllegalStateException("Failed to read file from zip", e);
}
return uihh.toRawBytes();
} else {
final UIHHContainer uihhContainer = UIHHContainer.fromRawBytes(fileBytes);
if (uihhContainer != null) {
return fileBytes;
}
} }
return uihh.toRawBytes(); throw new IllegalStateException("Unknown file bytes - this should never happen");
} }
} }
@@ -0,0 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
public class UIHHContainerTest extends TestBase {
@Test
@Ignore("helper test for development, remove this while debugging")
public void localTest() throws IOException {
byte[] bytes = Files.readAllBytes(new File("/storage/downloads/uihh.bin").toPath());
final UIHHContainer uihhContainer = UIHHContainer.fromRawBytes(bytes);
}
}