diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java index eb960ca08..fc9142a9b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java @@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime; @@ -48,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; public class HuaweiPacket { @@ -664,6 +666,36 @@ public class HuaweiPacket { this.isEncrypted = this.attemptDecrypt(); // Helps with debugging return this; } + case Ephemeris.id: + switch (this.commandId) { + case Ephemeris.OperatorData.id: + return new Ephemeris.OperatorData.OperatorIncomingRequest(paramsProvider).fromPacket(this); + case Ephemeris.ParameterConsult.id: + return new Ephemeris.ParameterConsult.Response(paramsProvider).fromPacket(this); + case Ephemeris.FileStatus.id: + return new Ephemeris.FileStatus.Response(paramsProvider).fromPacket(this); + default: + this.isEncrypted = this.attemptDecrypt(); // Helps with debugging + return this; + } + case EphemerisFileUpload.id: + switch (this.commandId) { + case EphemerisFileUpload.FileList.id: + return new EphemerisFileUpload.FileList.FileListIncomingRequest(paramsProvider).fromPacket(this); + case EphemerisFileUpload.FileConsult.id: + return new EphemerisFileUpload.FileConsult.FileConsultIncomingRequest(paramsProvider).fromPacket(this); + case EphemerisFileUpload.QuerySingleFileInfo.id: + return new EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoIncomingRequest(paramsProvider).fromPacket(this); + case EphemerisFileUpload.DataRequest.id: + return new EphemerisFileUpload.DataRequest.DataRequestIncomingRequest(paramsProvider).fromPacket(this); + case EphemerisFileUpload.UploadData.id: + return new EphemerisFileUpload.UploadData.UploadDataResponse(paramsProvider).fromPacket(this); + case EphemerisFileUpload.UploadDone.id: + return new EphemerisFileUpload.UploadDone.UploadDoneIncomingRequest(paramsProvider).fromPacket(this); + default: + this.isEncrypted = this.attemptDecrypt(); // Helps with debugging + return this; + } default: this.isEncrypted = this.attemptDecrypt(); // Helps with debugging return this; @@ -864,6 +896,63 @@ public class HuaweiPacket { return retv; } + public List serializeFileChunk1c(byte[] fileChunk, short transferSize, int packetCount) throws SerializeException { + List retv = new ArrayList<>(); + int headerLength = 4; // Magic + (short)(bodyLength + 1) + 0x00 + int bodyHeaderLength = 2; // sID + cID + int footerLength = 2; //CRC16 + int subHeaderLength = 1; + + + ByteBuffer buffer = ByteBuffer.wrap(fileChunk); + + for (int i = 0; i < packetCount; i++) { + + short contentSize = (short) Math.min(transferSize, buffer.remaining()); + + ByteBuffer payload = ByteBuffer.allocate(contentSize + subHeaderLength); + payload.put((byte)i); + + byte[] packetContent = new byte[contentSize]; + buffer.get(packetContent); + payload.put(packetContent); + + byte[] new_payload = payload.array(); + + int bodyLength = bodyHeaderLength + new_payload.length; + + short packetSize = (short)(headerLength + bodyLength + footerLength); + ByteBuffer packet = ByteBuffer.allocate(packetSize); + + int start = packet.position(); + packet.put((byte) 0x5a); // Magic byte + packet.putShort((short) (bodyLength + 1)); // Length + + packet.put((byte) 0x00); + packet.put(this.serviceId); + packet.put(this.commandId); + + packet.put(new_payload); + + int length = packet.position() - start; + if (length != packetSize - footerLength) { + throw new HuaweiPacket.SerializeException(String.format(GBApplication.getLanguage(), "Packet lengths don't match! %d != %d", length, packetSize + headerLength)); + } + + byte[] complete = new byte[length]; + packet.position(start); + packet.get(complete, 0, length); + int crc16 = CheckSums.getCRC16(complete, 0x0000); + + packet.putShort((short) crc16); // CRC16 + + retv.add(packet.array()); + } + return retv; + } + + + public List serialize() throws CryptoException { // TODO: necessary for this to work: // - serviceId diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java new file mode 100644 index 000000000..96757ba78 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java @@ -0,0 +1,114 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; + +public class Ephemeris { + public static final int id = 0x1f; + + public static class OperatorData { + public static final int id = 0x01; + + public static class OperatorIncomingRequest extends HuaweiPacket { + public byte operationInfo; + public int operationTime; + + public OperatorIncomingRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + + //TODO: can contain many elements. + //List subContainers = container.getObjects(0x81); + HuaweiTLV subTlv = this.tlv.getObject(0x81); + + this.operationInfo = subTlv.getByte(0x02); + this.operationTime = subTlv.getInteger(0x03); + } + } + + public static class OperatorResponse extends HuaweiPacket { + + public OperatorResponse(ParamsProvider paramsProvider, int responseCode) { + super(paramsProvider); + + this.serviceId = Ephemeris.id; + this.commandId = id; + + this.tlv = new HuaweiTLV().put(0x7f, responseCode); + + this.complete = true; + } + } + } + + public static class ParameterConsult { + public static final byte id = 0x02; + + public static class Request extends HuaweiPacket { + + + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = Ephemeris.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x81); + } + } + + public static class Response extends HuaweiPacket { + public int consultDeviceTime; + public byte downloadVersion; + public String downloadTag; + + public Response (ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws HuaweiPacket.ParseException { + + //TODO: can contain many elements. + //List subContainers = container.getObjects(0x81); + HuaweiTLV subTlv = this.tlv.getObject(0x81); + + this.consultDeviceTime = subTlv.getByte(0x04) * 1000; + this.downloadVersion = subTlv.getByte(0x05); + this.downloadTag = subTlv.getString(0x06); + } + + } + } + + public static class FileStatus { + public static final byte id = 0x03; + + public static class Request extends HuaweiPacket { + + + public Request(ParamsProvider paramsProvider, byte status) { + super(paramsProvider); + this.serviceId = Ephemeris.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x1, status); + } + } + + public static class Response extends HuaweiPacket { + public int responseCode; + + public Response (ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws HuaweiPacket.ParseException { + this.responseCode = this.tlv.getInteger(0x7f); + } + + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java new file mode 100644 index 000000000..4420e1ed3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java @@ -0,0 +1,269 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; + +public class EphemerisFileUpload { + public static final int id = 0x1c; + + public static class FileList { + public static final int id = 0x01; + + public static class FileListIncomingRequest extends HuaweiPacket { + public byte fileType; + public String productId = ""; + + public FileListIncomingRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + + // 0x1 - filenames + // 0x2 - fileType + // 0x3 - productId + // 0x4 - issuerId + // 0x5 - cardType + this.fileType = this.tlv.getByte(0x02); + if (this.tlv.contains(0x3)) + productId = this.tlv.getString(0x3); + + } + } + + public static class FileListResponse extends HuaweiPacket { + + public FileListResponse(ParamsProvider paramsProvider, int responseCode, String files) { + super(paramsProvider); + + this.serviceId = EphemerisFileUpload.id; + this.commandId = id; + + if (responseCode != 0) { + this.tlv = new HuaweiTLV().put(0x7f, responseCode); + } else { + this.tlv = new HuaweiTLV().put(0x1, files); + } + + this.complete = true; + } + } + } + + public static class FileConsult { + public static final int id = 0x02; + + public static class FileConsultIncomingRequest extends HuaweiPacket { + public int responseCode = 0; + public String protocolVersion = null; + public byte bitmapEnable = 0; + public short transferSize = 0; + public int maxDataSize = 0; + public short timeOut = 0; + public byte fileType = 0; + + public FileConsultIncomingRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + + if (this.tlv.contains(0x7f)) + responseCode = this.tlv.getInteger(0x7f); + + // 0x1 - version + // 0x2 - bitmapEnable + // 0x3 - transferSize + // 0x4 - maxDataSize + // 0x5 - timeOut + // 0x6 - fileType + + if (this.tlv.contains(0x1)) + this.protocolVersion = this.tlv.getString(0x1); + if (this.tlv.contains(0x2)) + this.bitmapEnable = this.tlv.getByte(0x2); + if (this.tlv.contains(0x3)) + this.transferSize = this.tlv.getShort(0x3); + if (this.tlv.contains(0x4)) + this.maxDataSize = this.tlv.getInteger(0x4); + if (this.tlv.contains(0x5)) + this.timeOut = this.tlv.getShort(0x5); + if (this.tlv.contains(0x6)) + this.fileType = this.tlv.getByte(0x6); + } + } + + public static class FileConsultResponse extends HuaweiPacket { + + public FileConsultResponse(ParamsProvider paramsProvider, int responseCode) { + super(paramsProvider); + + this.serviceId = EphemerisFileUpload.id; + this.commandId = id; + + this.tlv = new HuaweiTLV().put(0x7f, responseCode); + + this.complete = true; + } + } + } + + public static class QuerySingleFileInfo { + public static final int id = 0x03; + + public static class QuerySingleFileInfoIncomingRequest extends HuaweiPacket { + public int responseCode = 0; + public String fileName = null; + + public QuerySingleFileInfoIncomingRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + + if (this.tlv.contains(0x7f)) + responseCode = this.tlv.getInteger(0x7f); + + if (this.tlv.contains(0x1)) + this.fileName = this.tlv.getString(0x1); + + } + } + + public static class QuerySingleFileInfoResponse extends HuaweiPacket { + + public QuerySingleFileInfoResponse(ParamsProvider paramsProvider, int responseCode, int fileSize, short crc) { + super(paramsProvider); + + this.serviceId = EphemerisFileUpload.id; + this.commandId = id; + + if(responseCode != 0) { + this.tlv = new HuaweiTLV().put(0x7f, responseCode); + } else { + this.tlv = new HuaweiTLV().put(0x2, fileSize).put(0x3, crc); + } + + this.complete = true; + } + } + } + + public static class DataRequest { + public static final int id = 0x04; + + public static class DataRequestIncomingRequest extends HuaweiPacket { + public int responseCode = 0; + public String fileName = null; + public int offset = -1; + public int len = -1; + public byte bitmap = (byte) 0xff; + + + public DataRequestIncomingRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + // 1 - fileName + // 2 - offset + // 3 - length + // 4 - fileBitmap + // 7f - error + + if (this.tlv.contains(0x7f)) + responseCode = this.tlv.getInteger(0x7f); + + if (this.tlv.contains(0x1)) + this.fileName = this.tlv.getString(0x1); + if (this.tlv.contains(0x2)) + this.offset = this.tlv.getInteger(0x2); + if (this.tlv.contains(0x3)) + this.len = this.tlv.getInteger(0x3); + if (this.tlv.contains(0x4)) + this.bitmap = this.tlv.getByte(0x4); + } + } + + public static class DataRequestResponse extends HuaweiPacket { + public DataRequestResponse(ParamsProvider paramsProvider, int responseCode, String filename, int offset) { + super(paramsProvider); + + this.serviceId = EphemerisFileUpload.id; + this.commandId = id; + + this.tlv = new HuaweiTLV().put(0x7f, responseCode); + if(responseCode == 100000) { + this.tlv.put(0x2, filename).put(0x3, offset); + } + + this.complete = true; + } + } + } + + public static class UploadData { + public static final int id = 0x05; + + public static class FileNextChunkSend extends HuaweiPacket { + public FileNextChunkSend(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = EphemerisFileUpload.id; + this.commandId = id; + this.complete = true; + } + } + + public static class UploadDataResponse extends HuaweiPacket { + public int responseCode = 0; + + public UploadDataResponse(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + this.tlv = new HuaweiTLV(); + this.tlv.parse(payload, 2, payload.length - 2); + this.responseCode = this.tlv.getInteger(0x7f); + } + } + } + + + public static class UploadDone { + public static final int id = 0x06; + + public static class UploadDoneIncomingRequest extends HuaweiPacket { + public byte uploadResult = 0; + + public UploadDoneIncomingRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + this.uploadResult = this.tlv.getByte(0x1); + } + } + + public static class UploadDoneResponse extends HuaweiPacket { + + public UploadDoneResponse(ParamsProvider paramsProvider, int responseCode) { + super(paramsProvider); + + this.serviceId = EphemerisFileUpload.id; + this.commandId = id; + + this.tlv = new HuaweiTLV().put(0x7f, responseCode); + + this.complete = true; + } + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java index d1dffbdbb..d71c2b40f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalTime; -import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -43,7 +42,6 @@ import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote; -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; @@ -51,6 +49,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual; @@ -59,7 +59,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; -import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadComplete; @@ -70,13 +69,11 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Send import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadHash; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceConfirm; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceOperation; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherDeviceRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicStatusRequest; import nodomain.freeyourgadget.gadgetbridge.util.GB; /** * Handles responses that are not a reply to a request - * */ public class AsynchronousResponse { private static final Logger LOG = LoggerFactory.getLogger(AsynchronousResponse.class); @@ -84,6 +81,7 @@ public class AsynchronousResponse { private final HuaweiSupportProvider support; private final Handler mFindPhoneHandler = new Handler(); private final static HashMap dayOfWeekMap = new HashMap<>(); + static { dayOfWeekMap.put(Calendar.MONDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_MO); dayOfWeekMap.put(Calendar.TUESDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TU); @@ -123,6 +121,8 @@ public class AsynchronousResponse { handleCameraRemote(response); handleApp(response); handleP2p(response); + handleEphemeris(response); + handleEphemerisUploadService(response); } catch (Request.ResponseParseException e) { LOG.error("Response parse exception", e); } @@ -226,13 +226,14 @@ public class AsynchronousResponse { /** * Handles asynchronous music packet, for the following events: - * - The app is opened on the band (sends back music info) - * - A button is clicked - * - Play - * - Pause - * - Previous - * - Next - * - The volume is adjusted + * - The app is opened on the band (sends back music info) + * - A button is clicked + * - Play + * - Pause + * - Previous + * - Next + * - The volume is adjusted + * * @param response Packet to be handled */ private void handleMusicControls(HuaweiPacket response) throws Request.ResponseParseException { @@ -420,12 +421,12 @@ public class AsynchronousResponse { } } - private void handleFileUpload(HuaweiPacket response) throws Request.ResponseParseException { + private void handleFileUpload(HuaweiPacket response) throws Request.ResponseParseException { if (response.serviceId == FileUpload.id) { if (response.commandId == FileUpload.FileInfoSend.id) { if (!(response instanceof FileUpload.FileInfoSend.Response)) throw new Request.ResponseTypeMismatchException(response, FileUpload.FileInfoSend.Response.class); - if(support.huaweiUploadManager.getFileUploadInfo() == null) { + if (support.huaweiUploadManager.getFileUploadInfo() == null) { LOG.error("Upload file info received but no file to upload"); } else { FileUpload.FileInfoSend.Response resp = (FileUpload.FileInfoSend.Response) response; @@ -440,24 +441,24 @@ public class AsynchronousResponse { } } } else if (response.commandId == FileUpload.FileHashSend.id) { - if (!(response instanceof FileUpload.FileHashSend.Response)) - throw new Request.ResponseTypeMismatchException(response, FileUpload.FileHashSend.Response.class); - if(support.huaweiUploadManager.getFileUploadInfo() == null) { - LOG.error("Upload file hash requested but no file to upload"); - } else { - FileUpload.FileHashSend.Response resp = (FileUpload.FileHashSend.Response) response; - support.huaweiUploadManager.getFileUploadInfo().setFileId(resp.fileId); - try { - SendFileUploadHash sendFileUploadHash = new SendFileUploadHash(support, support.huaweiUploadManager); - sendFileUploadHash.doPerform(); - } catch (IOException e) { - LOG.error("Could not send file upload hash request", e); - } - } - } else if (response.commandId == FileUpload.FileUploadConsultAck.id) { - if (!(response instanceof FileUpload.FileUploadConsultAck.Response)) + if (!(response instanceof FileUpload.FileHashSend.Response)) + throw new Request.ResponseTypeMismatchException(response, FileUpload.FileHashSend.Response.class); + if (support.huaweiUploadManager.getFileUploadInfo() == null) { + LOG.error("Upload file hash requested but no file to upload"); + } else { + FileUpload.FileHashSend.Response resp = (FileUpload.FileHashSend.Response) response; + support.huaweiUploadManager.getFileUploadInfo().setFileId(resp.fileId); + try { + SendFileUploadHash sendFileUploadHash = new SendFileUploadHash(support, support.huaweiUploadManager); + sendFileUploadHash.doPerform(); + } catch (IOException e) { + LOG.error("Could not send file upload hash request", e); + } + } + } else if (response.commandId == FileUpload.FileUploadConsultAck.id) { + if (!(response instanceof FileUpload.FileUploadConsultAck.Response)) throw new Request.ResponseTypeMismatchException(response, FileUpload.FileUploadConsultAck.Response.class); - if(support.huaweiUploadManager.getFileUploadInfo() == null) { + if (support.huaweiUploadManager.getFileUploadInfo() == null) { LOG.error("Upload file ask requested but no file to upload"); } else { FileUpload.FileUploadConsultAck.Response resp = (FileUpload.FileUploadConsultAck.Response) response; @@ -473,10 +474,10 @@ public class AsynchronousResponse { LOG.error("Could not send file upload ack request", e); } } - } else if (response.commandId == FileUpload.FileNextChunkParams.id) { - if (!(response instanceof FileUpload.FileNextChunkParams)) - throw new Request.ResponseTypeMismatchException(response, FileUpload.FileNextChunkParams.class); - if(support.huaweiUploadManager.getFileUploadInfo() == null) { + } else if (response.commandId == FileUpload.FileNextChunkParams.id) { + if (!(response instanceof FileUpload.FileNextChunkParams)) + throw new Request.ResponseTypeMismatchException(response, FileUpload.FileNextChunkParams.class); + if (support.huaweiUploadManager.getFileUploadInfo() == null) { LOG.error("Upload file next chunk requested but no file to upload"); } else { FileUpload.FileNextChunkParams resp = (FileUpload.FileNextChunkParams) response; @@ -493,8 +494,8 @@ public class AsynchronousResponse { LOG.error("Could not send fileupload next chunk request", e); } } - } else if (response.commandId == FileUpload.FileUploadResult.id) { - if(support.huaweiUploadManager.getFileUploadInfo() == null) { + } else if (response.commandId == FileUpload.FileUploadResult.id) { + if (support.huaweiUploadManager.getFileUploadInfo() == null) { LOG.error("Upload file result requested but no file to upload"); } else { try { @@ -510,7 +511,7 @@ public class AsynchronousResponse { LOG.error("Could not send file upload result request", e); } } - } + } } } @@ -541,7 +542,7 @@ public class AsynchronousResponse { if (response.commandId == 0x2) { try { byte status = response.getTlv().getByte(0x1); - if(status == (byte)0x66 || status == (byte)0x69) { + if (status == (byte) 0x66 || status == (byte) 0x69) { this.support.getHuaweiAppManager().requestAppList(); } } catch (HuaweiPacket.MissingTagException e) { @@ -553,15 +554,15 @@ public class AsynchronousResponse { } private void handleP2p(HuaweiPacket response) throws Request.ResponseParseException { - if (response.serviceId == P2P.id && response.commandId == P2P.P2PCommand.id) { - if (!(response instanceof P2P.P2PCommand.Response)) - throw new Request.ResponseTypeMismatchException(response, P2P.P2PCommand.class); - try { - this.support.getHuaweiP2PManager().handlePacket((P2P.P2PCommand.Response) response); - } catch (Exception e) { - LOG.error("Error in P2P service", e); - } - } + if (response.serviceId == P2P.id && response.commandId == P2P.P2PCommand.id) { + if (!(response instanceof P2P.P2PCommand.Response)) + throw new Request.ResponseTypeMismatchException(response, P2P.P2PCommand.class); + try { + this.support.getHuaweiP2PManager().handlePacket((P2P.P2PCommand.Response) response); + } catch (Exception e) { + LOG.error("Error in P2P service", e); + } + } } private void handleWeatherCheck(HuaweiPacket response) { @@ -589,6 +590,57 @@ public class AsynchronousResponse { } } + private void handleEphemeris(HuaweiPacket response) { + if (response.serviceId == Ephemeris.id && response.commandId == Ephemeris.OperatorData.id) { + if (!(response instanceof Ephemeris.OperatorData.OperatorIncomingRequest)) { + return; + } + byte operationInfo = ((Ephemeris.OperatorData.OperatorIncomingRequest) response).operationInfo; + int operationTime = ((Ephemeris.OperatorData.OperatorIncomingRequest) response).operationTime; + LOG.info("Ephemeris: operation: {} time: {}", operationInfo, operationTime); + support.getHuaweiEphemerisManager().handleOperatorRequest(operationInfo, operationTime); + } + } + + private void handleEphemerisUploadService(HuaweiPacket response) { + if (response.serviceId == EphemerisFileUpload.id) { + if (response.commandId == EphemerisFileUpload.FileList.id) { + if (!(response instanceof EphemerisFileUpload.FileList.FileListIncomingRequest)) { + return; + } + support.getHuaweiEphemerisManager().handleFileSendRequest(((EphemerisFileUpload.FileList.FileListIncomingRequest) response).fileType, ((EphemerisFileUpload.FileList.FileListIncomingRequest) response).productId); + } else if (response.commandId == EphemerisFileUpload.FileConsult.id) { + if (!(response instanceof EphemerisFileUpload.FileConsult.FileConsultIncomingRequest)) { + return; + } + EphemerisFileUpload.FileConsult.FileConsultIncomingRequest res = (EphemerisFileUpload.FileConsult.FileConsultIncomingRequest) response; + support.getHuaweiEphemerisManager().handleFileConsultIncomingRequest(res.responseCode, res.protocolVersion, res.bitmapEnable, res.transferSize, res.maxDataSize, res.timeOut, res.fileType); + } else if (response.commandId == EphemerisFileUpload.QuerySingleFileInfo.id) { + if (!(response instanceof EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoIncomingRequest)) { + return; + } + support.getHuaweiEphemerisManager().handleSingleFileIncomingRequest(((EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoIncomingRequest) response).fileName); + } else if (response.commandId == EphemerisFileUpload.DataRequest.id) { + if (!(response instanceof EphemerisFileUpload.DataRequest.DataRequestIncomingRequest)) { + return; + } + EphemerisFileUpload.DataRequest.DataRequestIncomingRequest res = (EphemerisFileUpload.DataRequest.DataRequestIncomingRequest) response; + support.getHuaweiEphemerisManager().handleDataRequestIncomingRequest(res.responseCode, res.fileName, res.offset, res.len, res.bitmap); + } else if (response.commandId == EphemerisFileUpload.UploadData.id) { + if (!(response instanceof EphemerisFileUpload.UploadData.UploadDataResponse)) { + return; + } + support.getHuaweiEphemerisManager().handleFileUploadResponse(((EphemerisFileUpload.UploadData.UploadDataResponse) response).responseCode); + } else if (response.commandId == EphemerisFileUpload.UploadDone.id) { + if (!(response instanceof EphemerisFileUpload.UploadDone.UploadDoneIncomingRequest)) { + return; + } + support.getHuaweiEphemerisManager().handleFileDoneRequest(((EphemerisFileUpload.UploadDone.UploadDoneIncomingRequest) response).uploadResult); + } + + } + } + private void handleCameraRemote(HuaweiPacket response) { if (response.serviceId == CameraRemote.id && response.commandId == CameraRemote.CameraRemoteStatus.id) { if (!(response instanceof CameraRemote.CameraRemoteStatus.Response)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java new file mode 100644 index 000000000..405001baf --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java @@ -0,0 +1,563 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei; + +import android.text.TextUtils; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisDataRequestResponse; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileConsultResponse; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileListResponse; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileStatusRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileUploadChunk; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileUploadDoneResponse; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisOperatorResponse; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisParameterConsultRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisSingleFileInfoResponse; +import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; +import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class HuaweiEphemerisManager { + private static final Logger LOG = LoggerFactory.getLogger(HuaweiEphemerisManager.class); + + public static class UploadParameters { + //upload file related data + private final String protocolVersion; + private final byte bitmapEnable; + private final short transferSize; + private final int maxDataSize; + private final short timeOut; + private final byte fileType; + + public UploadParameters(String protocolVersion, byte bitmapEnable, short transferSize, int maxDataSize, short timeOut, byte fileType) { + this.protocolVersion = protocolVersion; + this.bitmapEnable = bitmapEnable; + this.transferSize = transferSize; + this.maxDataSize = maxDataSize; + this.timeOut = timeOut; + this.fileType = fileType; + } + + public String getProtocolVersion() { + return protocolVersion; + } + + public byte getBitmapEnable() { + return bitmapEnable; + } + + public short getTransferSize() { + return transferSize; + } + + public int getMaxDataSize() { + return maxDataSize; + } + + public short getTimeOut() { + return timeOut; + } + + public byte getFileType() { + return fileType; + } + } + + public static class RequestInfo { + private int tagVersion = -1; + private String tagUUID = null; + private List tagFiles = null; + + private byte[] currentFileData = null; + private String currentFileName = null; + + UploadParameters uploadParameters = null; + + private List processedFiles = new ArrayList<>(); + + public RequestInfo(int tagVersion, String tagUUID, List tagFiles) { + this.tagVersion = tagVersion; + this.tagUUID = tagUUID; + this.tagFiles = tagFiles; + } + + public int getTagVersion() { + return tagVersion; + } + + public String getTagUUID() { + return tagUUID; + } + + public List getTagFiles() { + return tagFiles; + } + + public byte[] getCurrentFileData() { + return currentFileData; + } + + public String getCurrentFileName() { + return currentFileName; + } + + public UploadParameters getUploadParameters() { + return uploadParameters; + } + + public void setCurrentFileData(byte[] currentFileData) { + this.currentFileData = currentFileData; + } + + public void setCurrentFileName(String currentFileName) { + this.currentFileName = currentFileName; + } + + public void setUploadParameters(UploadParameters uploadParameters) { + this.uploadParameters = uploadParameters; + } + + public void addProcessedFile(String name) { + processedFiles.add(name); + } + + public boolean isAllProcessed() { + LOG.info("Ephemeris tagFiles: {}", tagFiles.toString()); + LOG.info("Ephemeris processed: {}", processedFiles.toString()); + return processedFiles.size() == tagFiles.size() && new HashSet<>(processedFiles).containsAll(tagFiles); + } + } + + private final HuaweiSupportProvider support; + + + private JsonObject availableDataConfig = null; + + + private RequestInfo currentRequest = null; + + + public HuaweiEphemerisManager(HuaweiSupportProvider support) { + this.support = support; + } + + private byte[] getZIPFileContent(File file, String name) { + byte[] ret = null; + try { + ZipFile zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.getName().equals(name)) { + InputStream inputStream = zipFile.getInputStream(entry); + ret = new byte[inputStream.available()]; + inputStream.read(ret); + inputStream.close(); + } + } + zipFile.close(); + } catch (ZipException e) { + LOG.error("zip exception", e); + } catch (IOException e) { + LOG.error("zip IO exception", e); + } + return ret; + } + + private boolean checkZIPFileExists(File file, String name) { + try { + ZipFile zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!entry.isDirectory() && entry.getName().equals(name)) { + return true; + } + } + zipFile.close(); + } catch (ZipException e) { + LOG.error("zip exception", e); + } catch (IOException e) { + LOG.error("zip IO exception", e); + } + return false; + } + + public void handleOperatorRequest(byte operationInfo, int operationTime) { + //TODO: + // 100007 - no network connection + if (operationInfo == 1) { + // NOTE: clear data on new request + currentRequest = null; + + int responseCode = 100000; + try { + File filesDir = support.getContext().getExternalFilesDir(null); + File file = new File(filesDir, "ephemeris.zip"); + if (!file.exists()) { + throw new Exception("Ephemeris file does not exists"); + } + byte[] time = getZIPFileContent(file, "time"); + byte[] config = getZIPFileContent(file, "ephemeris_config.json"); + + if (time == null || config == null) { + throw new Exception("Ephemeris no time or config in file"); + } + + long fileTime = Long.parseLong(new String(time)); + if (fileTime < (System.currentTimeMillis() - (7200 * 1000))) { // 2 hours. Maybe should be decreased. + throw new Exception("Ephemeris file old"); + } + + availableDataConfig = JsonParser.parseString(new String(config)).getAsJsonObject(); + + LOG.info("Ephemeris Time: {} ConfigData: {}", fileTime, availableDataConfig.toString()); + + } catch (Exception e) { + LOG.error("Ephemeris exception file or config processing", e); + availableDataConfig = null; + //responseCode = 100007; //no network connection + return; // NOTE: just ignore request if something wrong with data. + } + + try { + SendEphemerisOperatorResponse sendEphemerisOperatorResponse = new SendEphemerisOperatorResponse(this.support, responseCode); + sendEphemerisOperatorResponse.doPerform(); + + if (responseCode == 100000) { + SendEphemerisParameterConsultRequest sendEphemerisParameterConsultRequest = new SendEphemerisParameterConsultRequest(this.support); + sendEphemerisParameterConsultRequest.doPerform(); + } + + } catch (IOException e) { + LOG.error("Error send Ephemeris data"); + } + } + + // TODO: operation code == 2 send file download progress + // Currently we do not need this code because everything is downloaded already. Maybe needed in the future + // 0x2: 02 + // 0x3: operationTime + // 0x4: from 01 to 06 status + // in the container 0x81 + } + + public void handleParameterConsultResponse(int consultDeviceTime, byte downloadVersion, String downloadTag) { + LOG.info("consultDeviceTime: {}, downloadVersion: {}, downloadTag: {}", consultDeviceTime, downloadVersion, downloadTag); + byte status = 0x3; // ready to download + + try { + File filesDir = support.getContext().getExternalFilesDir(null); + File file = new File(filesDir, "ephemeris.zip"); + if (!file.exists()) { + throw new Exception("Ephemeris file does not exists"); + } + + if (availableDataConfig == null) { + throw new Exception("Ephemeris no config data"); + } + + JsonObject conf = availableDataConfig.getAsJsonObject(downloadTag); + + int version = conf.get("ver").getAsInt(); + if(version != downloadVersion) { + throw new Exception("Ephemeris version mismatch"); + } + String uuid = conf.get("uuid").getAsString(); + + if(uuid.isEmpty()) + throw new Exception("Ephemeris uuid is empty"); + + JsonArray filesJs = conf.get("files").getAsJsonArray(); + + List files = new ArrayList<>(); + for (int i = 0; i < filesJs.size(); i++) { + files.add(filesJs.get(i).getAsString()); + } + + if(files.isEmpty()) + throw new Exception("Ephemeris file list is empty"); + + for(String fl: files) { + if(!checkZIPFileExists(file, uuid + File.separator + fl)) { + throw new Exception("Ephemeris file does not exist in ZIP"); + } + } + + currentRequest = new RequestInfo(downloadVersion, uuid, files); + + } catch (Exception e) { + LOG.error("Ephemeris exception file or config processing", e); + availableDataConfig = null; + status = 0x5; //error or timeout + } + + try { + SendEphemerisFileStatusRequest sendEphemerisFileStatusRequest = new SendEphemerisFileStatusRequest(this.support, status); + sendEphemerisFileStatusRequest.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisFileStatusRequest"); + } + } + + //File transfer related + public void handleFileSendRequest(byte fileType, String productId) { + if(currentRequest == null) { + return; + } + + String fileList = ""; + int responseCode = 0; + + if(fileType == 0) { + //TODO: find all files that name contain productId and send + LOG.error("Currently not supported. File type: 0"); + } else if(fileType == 1){ + if(currentRequest.getTagVersion() == 0) { + //TODO: implement this type + //fileList = "gpslocation.dat"; + LOG.error("Currently not supported. File type 1. Tag version 0"); + } else if (currentRequest.getTagVersion() == 1 || currentRequest.getTagVersion() == 2 || currentRequest.getTagVersion() == 3){ + int i = 0; + while (i < currentRequest.getTagFiles().size()) { + fileList += currentRequest.getTagFiles().get(i); + if (i == currentRequest.getTagFiles().size() - 1) { + break; + } + fileList += ";"; + i++; + } + } else { + LOG.error("Unknown version"); + } + } else { + LOG.error("Unknown file id"); + } + + if(TextUtils.isEmpty(fileList)) { + responseCode = 100001; + cleanupUpload(true); + } + + try { + SendEphemerisFileListResponse sendEphemerisFileListResponse = new SendEphemerisFileListResponse(this.support, responseCode, fileList); + sendEphemerisFileListResponse.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisFileListResponse"); + } + } + + void handleFileConsultIncomingRequest(int responseCode, String protocolVersion, byte bitmapEnable, short transferSize, int maxDataSize, short timeOut, byte fileType) { + if(currentRequest == null) { + return; + } + + if (responseCode != 0) { + LOG.error("Error on handleFileConsultIncomingRequest: {}", responseCode); + cleanupUpload(true); + return; + } + + if (transferSize == 0) { + LOG.error("transfer size is 0"); + cleanupUpload(true); + return; + } + + currentRequest.setUploadParameters(new UploadParameters(protocolVersion, bitmapEnable, transferSize, maxDataSize, timeOut, fileType)); + + if((!TextUtils.isEmpty(protocolVersion) && fileType == 0) || fileType == 1) { + try { + SendEphemerisFileConsultResponse sendEphemerisFileConsultResponse = new SendEphemerisFileConsultResponse(this.support, 100000); + sendEphemerisFileConsultResponse.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisFileConsultResponse"); + } + } else { + // I don't know how to properly process error in this case. Just cleanup. + cleanupUpload(true); + } + } + + void handleSingleFileIncomingRequest(String filename) { + if(currentRequest == null || currentRequest.getUploadParameters() == null) { + cleanupUpload(true); + return; + } + + if(TextUtils.isEmpty(filename) || !currentRequest.getTagFiles().contains(filename)) { + cleanupUpload(true); + try { + SendEphemerisSingleFileInfoResponse sendEphemerisSingleFileInfoResponse = new SendEphemerisSingleFileInfoResponse(this.support, 100001, 0, (short) 0); + sendEphemerisSingleFileInfoResponse.doPerform(); + } catch (IOException e) { + LOG.error("Error to send sendEphemerisSingleFileInfoResponse"); + } + return; + } + + currentRequest.addProcessedFile(filename); + + int responseCode = 0; + try { + + File filesDir = support.getContext().getExternalFilesDir(null); + File file = new File(filesDir, "ephemeris.zip"); + if (!file.exists()) { + throw new Exception("Ephemeris handleSingleFileIncomingRequest file does not exists"); + } + + byte[] currentFileData = getZIPFileContent(file, currentRequest.getTagUUID() + File.separator + filename); + if (currentFileData == null || currentFileData.length == 0) { + throw new Exception("Ephemeris handleSingleFileIncomingRequest file is empty"); + } + currentRequest.setCurrentFileData(currentFileData); + currentRequest.setCurrentFileName(filename); + } catch (Exception e) { + LOG.error("Ephemeris exception handleSingleFileIncomingRequest processing", e); + cleanupUpload(true); + responseCode = 100001; + } + + short crc = 0; + if(currentRequest.getUploadParameters().getFileType() == 0) { + //TODO: implement this type + responseCode = 100001; + } else if(currentRequest.getUploadParameters().getFileType() == 1) { + crc = (short)CheckSums.getCRC16(currentRequest.getCurrentFileData(), 0x0000); + } + + int size = 0; + if(currentRequest.getCurrentFileData() == null || crc == 0) { + responseCode = 100001; + } else { + size = currentRequest.getCurrentFileData().length; + } + + try { + SendEphemerisSingleFileInfoResponse sendEphemerisSingleFileInfoResponse = new SendEphemerisSingleFileInfoResponse(this.support, responseCode, size, crc); + sendEphemerisSingleFileInfoResponse.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisSingleFileInfoResponse"); + } + } + + public static String getHexDigest(String str) { + if (TextUtils.isEmpty(str)) { + return ""; + } + if (str.contains(":")) { + str = str.replace(":", ""); + } + try { + byte[] data = CryptoUtils.digest(str.getBytes(StandardCharsets.UTF_8)); + return StringUtils.bytesToHex(data); + } catch (NoSuchAlgorithmException e) { + LOG.error("EncryptUtil getEncryption :{} ", e.getMessage()); + return ""; + } + } + + void handleDataRequestIncomingRequest(int responseCode, String fileName, int offset, int len, byte bitmap) { + if(currentRequest == null || currentRequest.getUploadParameters() == null) { + cleanupUpload(true); + return; + } + + if(offset == -1 || fileName == null || fileName.isEmpty()) { + cleanupUpload(true); + return; + } + + if(!currentRequest.getCurrentFileName().equals(fileName)) { + cleanupUpload(true); + return; + } + + int localResponseCode = 100000; + + String localFilename = ""; + + if(currentRequest.getUploadParameters().getFileType() == 0) { + //TODO: implement this type + localResponseCode = 100001; + } else if(currentRequest.getUploadParameters().getFileType() == 1) { + localFilename = getHexDigest(support.deviceMac) + fileName; + } else { + LOG.error("Unsupported type: {}", currentRequest.getUploadParameters().getFileType()); + localResponseCode = 100001; + } + + try { + SendEphemerisDataRequestResponse sendEphemerisDataRequestResponse = new SendEphemerisDataRequestResponse(this.support, localResponseCode, localFilename, offset); + sendEphemerisDataRequestResponse.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisDataRequestResponse"); + } + + if(localResponseCode == 100000) { + int dataSize = Math.min(currentRequest.getCurrentFileData().length - offset,currentRequest.getUploadParameters().getMaxDataSize()); + int packetsCount = (int) Math.ceil((double) dataSize / currentRequest.getUploadParameters().getTransferSize()); + byte[] chunk = new byte[dataSize]; + System.arraycopy(currentRequest.getCurrentFileData(), offset, chunk, 0, dataSize); + + try { + SendEphemerisFileUploadChunk sendEphemerisFileUploadChunk = new SendEphemerisFileUploadChunk(this.support, chunk, currentRequest.getUploadParameters().getTransferSize(), packetsCount); + sendEphemerisFileUploadChunk.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisFileUploadChunk"); + } + } + } + + void handleFileUploadResponse(int responseCode) { + LOG.info("handleFileUploadResponse {}", responseCode); + if(responseCode != 100000) { + cleanupUpload(true); + } + } + + void handleFileDoneRequest(byte uploadResult) { + LOG.info("handleFileDoneRequest"); + cleanupUpload(false); + try { + SendEphemerisFileUploadDoneResponse sendEphemerisFileUploadDoneResponse = new SendEphemerisFileUploadDoneResponse(this.support, 100000); + sendEphemerisFileUploadDoneResponse.doPerform(); + } catch (IOException e) { + LOG.error("Error to send SendEphemerisFileUploadDoneResponse"); + } + } + + void cleanupUpload(boolean force) { + if(currentRequest != null) { + currentRequest.setCurrentFileName(null); + currentRequest.setCurrentFileData(null); + currentRequest.setUploadParameters(null); + boolean isAllProcessed = currentRequest.isAllProcessed(); + LOG.info("Ephemeris is Done: {}", isAllProcessed); + if(isAllProcessed || force) { + currentRequest = null; + LOG.info("Ephemeris All files uploaded. {} Cleanup...", force?"Force":""); + } + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index 188a0d981..44a4b00f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -265,6 +265,8 @@ public class HuaweiSupportProvider { protected HuaweiWeatherManager huaweiWeatherManager = new HuaweiWeatherManager(this); + protected HuaweiEphemerisManager huaweiEphemerisManager = new HuaweiEphemerisManager(this); + //TODO: we need only one instance of manager and all it services. protected HuaweiP2PManager huaweiP2PManager = new HuaweiP2PManager(this); @@ -292,6 +294,10 @@ public class HuaweiSupportProvider { return huaweiP2PManager; } + public HuaweiEphemerisManager getHuaweiEphemerisManager() { + return huaweiEphemerisManager; + } + public HuaweiSupportProvider(HuaweiBRSupport support) { this.brSupport = support; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java new file mode 100644 index 000000000..1b39135d6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java @@ -0,0 +1,33 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisDataRequestResponse extends Request { + final int responseCode; + final String fileName; + final int offset; + + + public SendEphemerisDataRequestResponse(HuaweiSupportProvider support, int responseCode, String fileName, int offset) { + super(support); + this.serviceId = EphemerisFileUpload.id; + this.commandId = EphemerisFileUpload.FileList.id; + this.responseCode = responseCode; + this.fileName = fileName; + this.offset = offset; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws Request.RequestCreationException { + try { + return new EphemerisFileUpload.DataRequest.DataRequestResponse(this.paramsProvider, this.responseCode, this.fileName, this.offset).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new Request.RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java new file mode 100644 index 000000000..61015aa7f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java @@ -0,0 +1,28 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisFileConsultResponse extends Request { + final int responseCode; + + public SendEphemerisFileConsultResponse(HuaweiSupportProvider support, int responseCode) { + super(support); + this.serviceId = EphemerisFileUpload.id; + this.commandId = EphemerisFileUpload.FileConsult.id; + this.responseCode = responseCode; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new EphemerisFileUpload.FileConsult.FileConsultResponse(this.paramsProvider, this.responseCode).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java new file mode 100644 index 000000000..7b06e1214 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java @@ -0,0 +1,30 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisFileListResponse extends Request { + final int responseCode; + final String files; + + public SendEphemerisFileListResponse(HuaweiSupportProvider support, int responseCode, String files) { + super(support); + this.serviceId = EphemerisFileUpload.id; + this.commandId = EphemerisFileUpload.FileList.id; + this.responseCode = responseCode; + this.files = files; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new EphemerisFileUpload.FileList.FileListResponse(this.paramsProvider, this.responseCode, this.files).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java new file mode 100644 index 000000000..c00e8ad73 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java @@ -0,0 +1,38 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisFileStatusRequest extends Request { + private final Logger LOG = LoggerFactory.getLogger(SendEphemerisFileStatusRequest.class); + + private final byte status; + public SendEphemerisFileStatusRequest(HuaweiSupportProvider support, byte status) { + super(support); + this.serviceId = Ephemeris.id; + this.commandId = Ephemeris.FileStatus.id; + this.status = status; + } + + @Override + protected List createRequest() throws Request.RequestCreationException { + try { + return new Ephemeris.FileStatus.Request(this.paramsProvider, status).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new Request.RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (!(receivedPacket instanceof Ephemeris.FileStatus.Response)) + throw new ResponseTypeMismatchException(receivedPacket, Ephemeris.FileStatus.Response.class); + LOG.info("Ephemeris FileStatus Response code: {}", ((Ephemeris.FileStatus.Response) receivedPacket).responseCode); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java new file mode 100644 index 000000000..70081ad2a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java @@ -0,0 +1,36 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + + +public class SendEphemerisFileUploadChunk extends Request { + byte[] fileChunk; + short transferSize; + int packetCount; + public SendEphemerisFileUploadChunk(HuaweiSupportProvider support, byte[] fileChunk, short transferSize, int packetCount) { + super(support); + this.serviceId = EphemerisFileUpload.id; + this.commandId = EphemerisFileUpload.UploadData.id; + this.fileChunk = fileChunk; + this.transferSize = transferSize; + this.packetCount = packetCount; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new EphemerisFileUpload.UploadData.FileNextChunkSend(this.paramsProvider).serializeFileChunk1c( + fileChunk, + transferSize, + packetCount + ); + } catch(HuaweiPacket.SerializeException e) { + throw new RequestCreationException(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java new file mode 100644 index 000000000..20a040051 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java @@ -0,0 +1,28 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisFileUploadDoneResponse extends Request { + final int responseCode; + + public SendEphemerisFileUploadDoneResponse(HuaweiSupportProvider support, int responseCode) { + super(support); + this.serviceId = EphemerisFileUpload.id; + this.commandId = EphemerisFileUpload.UploadDone.id; + this.responseCode = responseCode; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new EphemerisFileUpload.UploadDone.UploadDoneResponse(this.paramsProvider, this.responseCode).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java new file mode 100644 index 000000000..1ba3b004b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java @@ -0,0 +1,27 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisOperatorResponse extends Request { + final int responseCode; + public SendEphemerisOperatorResponse(HuaweiSupportProvider support, int responseCode) { + super(support); + this.serviceId = Ephemeris.id; + this.commandId = Ephemeris.OperatorData.id; + this.responseCode = responseCode; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new Ephemeris.OperatorData.OperatorResponse(this.paramsProvider, this.responseCode).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java new file mode 100644 index 000000000..fe6855a64 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java @@ -0,0 +1,38 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisParameterConsultRequest extends Request { + private Logger LOG = LoggerFactory.getLogger(SendEphemerisParameterConsultRequest.class); + + public SendEphemerisParameterConsultRequest(HuaweiSupportProvider support) { + super(support); + this.serviceId = Ephemeris.id; + this.commandId = Ephemeris.ParameterConsult.id; + } + + @Override + protected List createRequest() throws Request.RequestCreationException { + try { + return new Ephemeris.ParameterConsult.Request(this.paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new Request.RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (!(receivedPacket instanceof Ephemeris.ParameterConsult.Response)) + throw new ResponseTypeMismatchException(receivedPacket, Ephemeris.ParameterConsult.Response.class); + + Ephemeris.ParameterConsult.Response resp = (Ephemeris.ParameterConsult.Response)receivedPacket; + supportProvider.getHuaweiEphemerisManager().handleParameterConsultResponse(resp.consultDeviceTime, resp.downloadVersion, resp.downloadTag); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java new file mode 100644 index 000000000..a1a8a117b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java @@ -0,0 +1,32 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendEphemerisSingleFileInfoResponse extends Request { + final int responseCode; + final int fileSize; + final short crc; + + public SendEphemerisSingleFileInfoResponse(HuaweiSupportProvider support, int responseCode, int fileSize, short crc) { + super(support); + this.serviceId = EphemerisFileUpload.id; + this.commandId = EphemerisFileUpload.FileList.id; + this.responseCode = responseCode; + this.fileSize = fileSize; + this.crc = crc; + this.addToResponse = false; + } + + @Override + protected List createRequest() throws Request.RequestCreationException { + try { + return new EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoResponse(this.paramsProvider, this.responseCode, this.fileSize, this.crc).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new Request.RequestCreationException(e); + } + } +}