diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/CRC32C.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/CRC32C.java new file mode 100644 index 000000000..d31d54953 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/CRC32C.java @@ -0,0 +1,46 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +import java.util.zip.Checksum; + +public final class CRC32C_stolen implements Checksum { + private static final int[] bqC = new int[]{0, (int) 4067132163L, (int) 3778769143L, 324072436, (int) 3348797215L, 904991772, 648144872, (int) 3570033899L, (int) 2329499855L, 2024987596, 1809983544, (int) 2575936315L, 1296289744, (int) 3207089363L, (int) 2893594407L, 1578318884, 274646895, (int) 3795141740L, (int) 4049975192L, 51262619, (int) 3619967088L, 632279923, 922689671, (int) 3298075524L, (int) 2592579488L, 1760304291, 2075979607, (int) 2312596564L, 1562183871, (int) 2943781820L, (int) 3156637768L, 1313733451, 549293790, (int) 3537243613L, (int) 3246849577L, 871202090, (int) 3878099393L, 357341890, 102525238, (int) 4101499445L, (int) 2858735121L, 1477399826, 1264559846, (int) 3107202533L, 1845379342, (int) 2677391885L, (int) 2361733625L, 2125378298, 820201905, (int) 3263744690L, (int) 3520608582L, 598981189, (int) 4151959214L, 85089709, 373468761, (int) 3827903834L, (int) 3124367742L, 1213305469, 1526817161, (int) 2842354314L, 2107672161, (int) 2412447074L, (int) 2627466902L, 1861252501, 1098587580, (int) 3004210879L, (int) 2688576843L, 1378610760, (int) 2262928035L, 1955203488, 1742404180, (int) 2511436119L, (int) 3416409459L, 969524848, 714683780, (int) 3639785095L, 205050476, (int) 4266873199L, (int) 3976438427L, 526918040, 1361435347, (int) 2739821008L, (int) 2954799652L, 1114974503, (int) 2529119692L, 1691668175, 2005155131, (int) 2247081528L, (int) 3690758684L, 697762079, 986182379, (int) 3366744552L, 476452099, (int) 3993867776L, (int) 4250756596L, 255256311, 1640403810, (int) 2477592673L, (int) 2164122517L, 1922457750, (int) 2791048317L, 1412925310, 1197962378, (int) 3037525897L, (int) 3944729517L, 427051182, 170179418, (int) 4165941337L, 746937522, (int) 3740196785L, (int) 3451792453L, 1070968646, 1905808397, (int) 2213795598L, (int) 2426610938L, 1657317369, (int) 3053634322L, 1147748369, 1463399397, (int) 2773627110L, (int) 4215344322L, 153784257, 444234805, (int) 3893493558L, 1021025245, (int) 3467647198L, (int) 3722505002L, 797665321, (int) 2197175160L, 1889384571, 1674398607, (int) 2443626636L, 1164749927, (int) 3070701412L, (int) 2757221520L, 1446797203, 137323447, (int) 4198817972L, (int) 3910406976L, 461344835, (int) 3484808360L, 1037989803, 781091935, (int) 3705997148L, (int) 2460548119L, 1623424788, 1939049696, (int) 2180517859L, 1429367560, (int) 2807687179L, (int) 3020495871L, 1180866812, 410100952, (int) 3927582683L, (int) 4182430767L, 186734380, (int) 3756733383L, 763408580, 1053836080, (int) 3434856499L, (int) 2722870694L, 1344288421, 1131464017, (int) 2971354706L, 1708204729, (int) 2545590714L, (int) 2229949006L, 1988219213, 680717673, (int) 3673779818L, (int) 3383336350L, 1002577565, (int) 4010310262L, 493091189, 238226049, (int) 4233660802L, (int) 2987750089L, 1082061258, 1395524158, (int) 2705686845L, 1972364758, (int) 2279892693L, (int) 2494862625L, 1725896226, 952904198, (int) 3399985413L, (int) 3656866545L, 731699698, (int) 4283874585L, 222117402, 510512622, (int) 3959836397L, (int) 3280807620L, 837199303, 582374963, (int) 3504198960L, 68661723, (int) 4135334616L, (int) 3844915500L, 390545967, 1230274059, (int) 3141532936L, (int) 2825850620L, 1510247935, (int) 2395924756L, 2091215383, 1878366691, (int) 2644384480L, (int) 3553878443L, 565732008, 854102364, (int) 3229815391L, 340358836, (int) 3861050807L, (int) 4117890627L, 119113024, 1493875044, (int) 2875275879L, (int) 3090270611L, 1247431312, (int) 2660249211L, 1828433272, 2141937292, (int) 2378227087L, (int) 3811616794L, 291187481, 34330861, (int) 4032846830L, 615137029, (int) 3603020806L, (int) 3314634738L, 939183345, 1776939221, (int) 2609017814L, (int) 2295496738L, 2058945313, (int) 2926798794L, 1545135305, 1330124605, (int) 3173225534L, (int) 4084100981L, 17165430, 307568514, (int) 3762199681L, 888469610, (int) 3332340585L, (int) 3587147933L, 665062302, 2042050490, (int) 2346497209L, (int) 2559330125L, 1793573966, (int) 3190661285L, 1279665062, 1595330642, (int) 2910671697L}; + private int crc = 0xFFFFFFFF; + + public CRC32C_stolen() { + } + + public long getValue() { + return ~this.crc; + } + + public void reset() { + this.crc = 0xFFFFFFFF; + } + + public void update(int var1) { + int var2 = this.crc; + this.crc = bqC[(var1 & 255 ^ var2) & 255] ^ var2 >>> 8; + } + + public void update(byte[] var1, int var2, int var3) { + for (int var4 = var2; var4 < var3 + var2; ++var4) { + this.crc = bqC[(this.crc ^ var1[var4]) & 255] ^ this.crc >>> 8; + } + } + + public void update(byte[] data){ + update(data, 0, data.length); + } + + public static int crc32c(byte[] data) + { + int crc = 0xffffffff; + for(int i = 0; i < data.length; i++){ + crc = (crc>>>8) ^ bqC[(crc ^ data[i]) & 0xFF]; + } + return ~crc; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java new file mode 100644 index 000000000..eb4962cd3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java @@ -0,0 +1,6 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter; + +public class FossilWatchAdapter extends WatchAdapter { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/CRC32C.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/CRC32C.java new file mode 100644 index 000000000..6fc098005 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/CRC32C.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests; + +public class CRC32C { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/Request.java similarity index 100% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/Request.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/Request.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/ConfigurationGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/ConfigurationGetRequest.java new file mode 100644 index 000000000..b574c9453 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/ConfigurationGetRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class ConfigurationGetRequest extends FileGetRequest { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/ConfigurationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/ConfigurationRequest.java new file mode 100644 index 000000000..e34c2a3fa --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/ConfigurationRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class ConfigurationRequest extends UploadFileRequest { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileGetRequest.java new file mode 100644 index 000000000..c3f32f8a2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileGetRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class FileGetRequest { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileLookupAndGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileLookupAndGetRequest.java new file mode 100644 index 000000000..6a34e1b17 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileLookupAndGetRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class FileLookupAndGetRequest extends FileLookupRequest { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileLookupRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileLookupRequest.java new file mode 100644 index 000000000..970a1a4dc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FileLookupRequest.java @@ -0,0 +1,6 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request; + +public class FileLookupRequest extends Request { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FilePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FilePutRequest.java new file mode 100644 index 000000000..640c4274e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FilePutRequest.java @@ -0,0 +1,209 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.CRC32C; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request; + +public class UploadFileRequest extends Request { + public enum UploadState{INITIALIZED, UPLOADING, CLOSING, UPLOADED, ERROR} + + public UploadState state; + + public ArrayList packets = new ArrayList<>(); + + private short handle; + + public int packetIndex = 0; + + private FossilWatchAdapter adapter; + + public UploadFileRequest(short handle, byte[] file, FossilWatchAdapter adapter) { + this.handle = handle; + this.adapter = adapter; + + int fileLength = file.length + 16; + ByteBuffer buffer = this.createBuffer(); + buffer.putShort(1, handle); + buffer.putInt(3, 0); + buffer.putInt(7, fileLength); + buffer.putInt(11, fileLength); + + this.data = buffer.array(); + + prepareFilePackets(file); + + state = UploadState.INITIALIZED; + } + + public short getHandle() { + return handle; + } + + @Override + public void handleResponse(BluetoothGattCharacteristic characteristic) { + byte[] value = characteristic.getValue(); + int responseType = value[0] & 0x0F; + log("response: " + responseType); + switch (responseType){ + case 3:{ + if(value.length != 5 || (value[0] & 0x0F) != 3){ + this.state = UploadState.ERROR; + log("wrong answer header"); + break; + } + state = UploadState.UPLOADING; + byte[] initialPacket = packets.get(0); + BtLEQueue queue = adapter.getDeviceSupport().getQueue(); + + new TransactionBuilder("file upload") + .write( + adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0004-957f-7d4a-34a6-74696673696d")), + initialPacket + ) + .queue(queue); + break; + } + case 8:{ + if(value.length == 4) return; + ByteBuffer buffer = ByteBuffer.wrap(value); + buffer.order(ByteOrder.LITTLE_ENDIAN); + short handle = buffer.getShort(1); + int crc = buffer.getInt(8); + byte status = value[3]; + + if(status != 0){ + this.state = UploadState.ERROR; + log("file error: " + status); + break; + } + + if(handle != this.handle){ + this.state = UploadState.ERROR; + log("wrong file handle"); + break; + } + + CRC32C realCrc = new CRC32C(); + byte[] data = packets.get(packetIndex); + realCrc.update(data, 1, data.length - 1); + + if(crc != (int) realCrc.getValue()){ + this.state = UploadState.ERROR; + log("wrong crc"); + // TODO CRC + // break; + } + + packetIndex++; + + if(packetIndex < packets.size()){ + byte[] initialPacket = packets.get(packetIndex); + + new TransactionBuilder("file upload") + .write( + adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0004-957f-7d4a-34a6-74696673696d")), + initialPacket + ) + .queue(adapter.getDeviceSupport().getQueue()); + break; + } else{ + ByteBuffer buffer2 = ByteBuffer.allocate(3); + buffer2.order(ByteOrder.LITTLE_ENDIAN); + buffer2.put((byte) 4); + buffer2.putShort(this.handle); + + new TransactionBuilder("file close") + .write( + adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")), + buffer2.array() + ) + .queue(adapter.getDeviceSupport().getQueue()); + + this.state = UploadState.CLOSING; + break; + } + } + case 4: { + if(value.length == 9) return; + if(value.length != 4 || (value[0] & 0x0F) != 4){ + this.state = UploadState.ERROR; + log("wrong closing header"); + break; + } + ByteBuffer buffer = ByteBuffer.wrap(value); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + short handle = buffer.getShort(1); + + if(handle != this.handle){ + this.state = UploadState.ERROR; + log("wrong file handle"); + break; + } + + byte status = buffer.get(3); + + if(status != 0){ + this.state = UploadState.ERROR; + log("wrong closing handle"); + break; + } + + this.state = UploadState.UPLOADED; + + log("uploaded file"); + + break; + } + } + } + + public boolean isFinished(){ + return this.state == UploadState.UPLOADED || this.state == UploadState.ERROR; + } + + private void prepareFilePackets(byte[] file) { + ByteBuffer buffer = ByteBuffer.allocate(file.length + 13 + 4); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + buffer.put((byte)0); + buffer.putShort(handle); + buffer.put((byte)2); + buffer.put((byte)0); + buffer.putInt(0); + buffer.putInt(file.length); + + buffer.put(file); + + CRC32C crc = new CRC32C(); + + crc.update(file); + buffer.putInt((int) crc.getValue()); + + packets.add(buffer.array()); + } + + @Override + public byte[] getStartSequence() { + return new byte[]{0x03}; + } + + @Override + public int getPayloadLength() { + return 15; + } + + @Override + public UUID getRequestUUID() { + return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d"); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/NotifcationFilterGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/NotifcationFilterGetRequest.java new file mode 100644 index 000000000..43f5fcd8a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/NotifcationFilterGetRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class NotifcationFilterGetRequest extends FileGetRequest { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/PlayNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/PlayNotificationRequest.java new file mode 100644 index 000000000..8c5bb2e38 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/PlayNotificationRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class PlayNotificationRequest extends UploadFileRequest { +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SendNotificationFilterRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SendNotificationFilterRequest.java new file mode 100644 index 000000000..9ad648f89 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SendNotificationFilterRequest.java @@ -0,0 +1,4 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil; + +public class SendNotificationFilterRequest extends UploadFileRequest { +}