mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-27 02:55:50 +01:00
Xiaomi: Watchface upload (wip, does not work)
This commit is contained in:
parent
82a264cd65
commit
c47e830056
@ -16,40 +16,64 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
|
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.AGPS_UIHH;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
|
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8.XiaomiFWHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||||
|
|
||||||
public class XiaomiInstallHandler implements InstallHandler {
|
public class XiaomiInstallHandler implements InstallHandler {
|
||||||
protected final Uri mUri;
|
protected final Uri mUri;
|
||||||
protected final Context mContext;
|
protected final Context mContext;
|
||||||
|
protected final XiaomiFWHelper helper;
|
||||||
|
|
||||||
public XiaomiInstallHandler(final Uri uri, final Context context) {
|
public XiaomiInstallHandler(final Uri uri, final Context context) {
|
||||||
this.mUri = uri;
|
this.mUri = uri;
|
||||||
this.mContext = context;
|
this.mContext = context;
|
||||||
|
this.helper = new XiaomiFWHelper(uri, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
// TODO
|
return helper.isValid();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
|
public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
|
||||||
// TODO
|
if (device.isBusy()) {
|
||||||
|
installActivity.setInfoText(device.getBusyTask());
|
||||||
|
installActivity.setInstallEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.isInitialized()) {
|
||||||
|
installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
|
||||||
|
installActivity.setInstallEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!helper.isValid()) {
|
||||||
|
installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
|
||||||
|
installActivity.setInstallEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final GenericItem installItem = new GenericItem();
|
||||||
|
installItem.setIcon(R.drawable.ic_watchface);
|
||||||
|
installItem.setName(mContext.getString(R.string.kind_watchface));
|
||||||
|
installItem.setDetails(helper.getDetails());
|
||||||
|
|
||||||
|
installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
|
||||||
|
installActivity.setInstallItem(installItem);
|
||||||
|
installActivity.setInstallEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartInstall(final GBDevice device) {
|
public void onStartInstall(final GBDevice device) {
|
||||||
// nothing to do
|
helper.unsetFwBytes(); // free up memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
/* Copyright (C) 2023 José Rebelo
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
|
||||||
|
|
||||||
|
public class XiaomiFWHelper {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(XiaomiFWHelper.class);
|
||||||
|
|
||||||
|
private final Uri uri;
|
||||||
|
private byte[] fw;
|
||||||
|
private boolean valid;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public XiaomiFWHelper(final Uri uri, final Context context) {
|
||||||
|
this.uri = uri;
|
||||||
|
|
||||||
|
final UriHelper uriHelper;
|
||||||
|
try {
|
||||||
|
uriHelper = UriHelper.get(uri, context);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to get uri helper for {}", uri, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int maxExpectedFileSize = 1024 * 1024 * 128; // 64MB
|
||||||
|
|
||||||
|
if (uriHelper.getFileSize() > maxExpectedFileSize) {
|
||||||
|
LOG.warn("Firmware size is larger than the maximum expected file size of {}", maxExpectedFileSize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (final InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
|
||||||
|
this.fw = FileUtils.readAll(in, maxExpectedFileSize);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to read bytes from {}", uri, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = parseFirmware();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetails() {
|
||||||
|
return name != null ? name : "UNKNOWN WATCHFACE";
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return fw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsetFwBytes() {
|
||||||
|
this.fw = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseFirmware() {
|
||||||
|
if (fw[0] != (byte) 0x5A || fw[1] != (byte) 0xA5) {
|
||||||
|
LOG.warn("File header not a watchface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = StringUtils.untilNullTerminator(fw, 0x28);
|
||||||
|
name = StringUtils.untilNullTerminator(fw, 0x68);
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
LOG.warn("id not found in {}", uri);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
LOG.warn("name not found in {}", uri);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Integer.parseInt(id);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.warn("Id {} not a number", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -35,15 +35,16 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class XiaomiCharacteristic {
|
public class XiaomiCharacteristic {
|
||||||
|
private final Logger LOG = LoggerFactory.getLogger(XiaomiCharacteristic.class);
|
||||||
|
|
||||||
public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0};
|
public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0};
|
||||||
|
|
||||||
// max chunk size, including headers
|
// max chunk size, including headers
|
||||||
public static final int MAX_WRITE_SIZE = 242;
|
public static final int MAX_WRITE_SIZE = 242;
|
||||||
|
|
||||||
private final Logger LOG;
|
|
||||||
|
|
||||||
private final XiaomiSupport mSupport;
|
private final XiaomiSupport mSupport;
|
||||||
|
|
||||||
private final BluetoothGattCharacteristic bluetoothGattCharacteristic;
|
private final BluetoothGattCharacteristic bluetoothGattCharacteristic;
|
||||||
@ -52,6 +53,7 @@ public class XiaomiCharacteristic {
|
|||||||
// Encryption
|
// Encryption
|
||||||
private final XiaomiAuthService authService;
|
private final XiaomiAuthService authService;
|
||||||
private boolean isEncrypted;
|
private boolean isEncrypted;
|
||||||
|
public boolean incrementNonce = true;
|
||||||
private short encryptedIndex = 0;
|
private short encryptedIndex = 0;
|
||||||
|
|
||||||
// Chunking
|
// Chunking
|
||||||
@ -68,6 +70,8 @@ public class XiaomiCharacteristic {
|
|||||||
|
|
||||||
private Handler handler = null;
|
private Handler handler = null;
|
||||||
|
|
||||||
|
private SendCallback callback;
|
||||||
|
|
||||||
public XiaomiCharacteristic(final XiaomiSupport support,
|
public XiaomiCharacteristic(final XiaomiSupport support,
|
||||||
final BluetoothGattCharacteristic bluetoothGattCharacteristic,
|
final BluetoothGattCharacteristic bluetoothGattCharacteristic,
|
||||||
@Nullable final XiaomiAuthService authService) {
|
@Nullable final XiaomiAuthService authService) {
|
||||||
@ -75,7 +79,6 @@ public class XiaomiCharacteristic {
|
|||||||
this.bluetoothGattCharacteristic = bluetoothGattCharacteristic;
|
this.bluetoothGattCharacteristic = bluetoothGattCharacteristic;
|
||||||
this.authService = authService;
|
this.authService = authService;
|
||||||
this.isEncrypted = authService != null;
|
this.isEncrypted = authService != null;
|
||||||
this.LOG = LoggerFactory.getLogger("XiaomiCharacteristic [" + bluetoothGattCharacteristic.getUuid().toString() + "]");
|
|
||||||
this.characteristicUUID = bluetoothGattCharacteristic.getUuid();
|
this.characteristicUUID = bluetoothGattCharacteristic.getUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,10 +90,18 @@ public class XiaomiCharacteristic {
|
|||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCallback(final SendCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
public void setEncrypted(final boolean encrypted) {
|
public void setEncrypted(final boolean encrypted) {
|
||||||
this.isEncrypted = encrypted;
|
this.isEncrypted = encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIncrementNonce(final boolean incrementNonce) {
|
||||||
|
this.incrementNonce = incrementNonce;
|
||||||
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
this.numChunks = 0;
|
this.numChunks = 0;
|
||||||
this.currentChunk = 0;
|
this.currentChunk = 0;
|
||||||
@ -127,6 +138,9 @@ public class XiaomiCharacteristic {
|
|||||||
LOG.debug("Got ack");
|
LOG.debug("Got ack");
|
||||||
currentPayload = null;
|
currentPayload = null;
|
||||||
waitingAck = false;
|
waitingAck = false;
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSend(payloadQueue.size());
|
||||||
|
}
|
||||||
sendNext(null);
|
sendNext(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -185,6 +199,9 @@ public class XiaomiCharacteristic {
|
|||||||
LOG.debug("Got chunked ack end");
|
LOG.debug("Got chunked ack end");
|
||||||
currentPayload = null;
|
currentPayload = null;
|
||||||
sendingChunked = false;
|
sendingChunked = false;
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSend(payloadQueue.size());
|
||||||
|
}
|
||||||
sendNext(null);
|
sendNext(null);
|
||||||
return;
|
return;
|
||||||
case 1:
|
case 1:
|
||||||
@ -207,6 +224,9 @@ public class XiaomiCharacteristic {
|
|||||||
LOG.warn("Got chunked nack for {}", currentPayload.getTaskName());
|
LOG.warn("Got chunked nack for {}", currentPayload.getTaskName());
|
||||||
currentPayload = null;
|
currentPayload = null;
|
||||||
sendingChunked = false;
|
sendingChunked = false;
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSend(payloadQueue.size());
|
||||||
|
}
|
||||||
sendNext(null);
|
sendNext(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -251,6 +271,8 @@ public class XiaomiCharacteristic {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG.debug("Will send {}", GB.hexdump(currentPayload.getBytesToSend()));
|
||||||
|
|
||||||
final boolean encrypt = isEncrypted && authService.isEncryptionInitialized();
|
final boolean encrypt = isEncrypted && authService.isEncryptionInitialized();
|
||||||
|
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
@ -262,10 +284,13 @@ public class XiaomiCharacteristic {
|
|||||||
// Prepend encrypted index for the nonce
|
// Prepend encrypted index for the nonce
|
||||||
currentPayload.setBytesToSend(
|
currentPayload.setBytesToSend(
|
||||||
ByteBuffer.allocate(2 + currentPayload.getBytesToSend().length).order(ByteOrder.LITTLE_ENDIAN)
|
ByteBuffer.allocate(2 + currentPayload.getBytesToSend().length).order(ByteOrder.LITTLE_ENDIAN)
|
||||||
.putShort(encryptedIndex++)
|
.putShort(encryptedIndex)
|
||||||
.put(currentPayload.getBytesToSend())
|
.put(currentPayload.getBytesToSend())
|
||||||
.array()
|
.array()
|
||||||
);
|
);
|
||||||
|
if (incrementNonce) {
|
||||||
|
encryptedIndex++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Sending {} - chunked", currentPayload.getTaskName());
|
LOG.debug("Sending {} - chunked", currentPayload.getTaskName());
|
||||||
@ -294,7 +319,10 @@ public class XiaomiCharacteristic {
|
|||||||
buf.put((byte) 2); // 2 for command
|
buf.put((byte) 2); // 2 for command
|
||||||
buf.put((byte) (encrypt ? 1 : 2));
|
buf.put((byte) (encrypt ? 1 : 2));
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
buf.putShort(encryptedIndex++);
|
buf.putShort(encryptedIndex);
|
||||||
|
if (incrementNonce) {
|
||||||
|
encryptedIndex++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
buf.put(currentPayload.getBytesToSend()); // it's already encrypted
|
buf.put(currentPayload.getBytesToSend()); // it's already encrypted
|
||||||
|
|
||||||
@ -364,4 +392,8 @@ public class XiaomiCharacteristic {
|
|||||||
return bytesToSend != null ? bytesToSend : bytes;
|
return bytesToSend != null ? bytesToSend : bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface SendCallback {
|
||||||
|
void onSend(int remaining);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiCalendarService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiCalendarService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiDataUploadService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiHealthService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiHealthService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiMusicService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiMusicService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiNotificationService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiNotificationService;
|
||||||
@ -79,6 +80,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
protected final XiaomiSystemService systemService = new XiaomiSystemService(this);
|
protected final XiaomiSystemService systemService = new XiaomiSystemService(this);
|
||||||
protected final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
|
protected final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
|
||||||
protected final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
|
protected final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
|
||||||
|
protected final XiaomiDataUploadService dataUploadService = new XiaomiDataUploadService(this);
|
||||||
|
|
||||||
private String mFirmwareVersion = null;
|
private String mFirmwareVersion = null;
|
||||||
|
|
||||||
@ -92,6 +94,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
put(XiaomiSystemService.COMMAND_TYPE, systemService);
|
put(XiaomiSystemService.COMMAND_TYPE, systemService);
|
||||||
put(XiaomiCalendarService.COMMAND_TYPE, calendarService);
|
put(XiaomiCalendarService.COMMAND_TYPE, calendarService);
|
||||||
put(XiaomiWatchfaceService.COMMAND_TYPE, watchfaceService);
|
put(XiaomiWatchfaceService.COMMAND_TYPE, watchfaceService);
|
||||||
|
put(XiaomiDataUploadService.COMMAND_TYPE, dataUploadService);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
public XiaomiSupport() {
|
public XiaomiSupport() {
|
||||||
@ -133,6 +136,8 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
this.characteristicActivityData.setEncrypted(isEncrypted());
|
this.characteristicActivityData.setEncrypted(isEncrypted());
|
||||||
this.characteristicDataUpload = new XiaomiCharacteristic(this, btCharacteristicDataUpload, authService);
|
this.characteristicDataUpload = new XiaomiCharacteristic(this, btCharacteristicDataUpload, authService);
|
||||||
this.characteristicDataUpload.setEncrypted(isEncrypted());
|
this.characteristicDataUpload.setEncrypted(isEncrypted());
|
||||||
|
this.characteristicDataUpload.setIncrementNonce(false);
|
||||||
|
this.dataUploadService.setDataUploadCharacteristic(this.characteristicDataUpload);
|
||||||
|
|
||||||
builder.requestMtu(247);
|
builder.requestMtu(247);
|
||||||
|
|
||||||
@ -314,6 +319,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInstallApp(final Uri uri) {
|
public void onInstallApp(final Uri uri) {
|
||||||
|
// TODO distinguish between fw and watchface
|
||||||
watchfaceService.installWatchface(uri);
|
watchfaceService.installWatchface(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,4 +448,8 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XiaomiDataUploadService getDataUploader() {
|
||||||
|
return this.dataUploadService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,182 @@
|
|||||||
|
/* Copyright (C) 2023 José Rebelo
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiCharacteristic;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||||
|
|
||||||
|
public class XiaomiDataUploadService extends AbstractXiaomiService {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(XiaomiDataUploadService.class);
|
||||||
|
|
||||||
|
public static final int COMMAND_TYPE = 22;
|
||||||
|
|
||||||
|
public static final int CMD_UPLOAD_START = 0;
|
||||||
|
|
||||||
|
public static final byte TYPE_WATCHFACE = 16;
|
||||||
|
public static final byte TYPE_FIRMWARE = 32;
|
||||||
|
public static final byte TYPE_NOTIFICATION_ICON = 50;
|
||||||
|
|
||||||
|
private XiaomiCharacteristic characteristic;
|
||||||
|
private Callback callback;
|
||||||
|
|
||||||
|
private byte currentType;
|
||||||
|
private byte[] currentBytes;
|
||||||
|
|
||||||
|
public XiaomiDataUploadService(final XiaomiSupport support) {
|
||||||
|
super(support);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(final XiaomiProto.Command cmd) {
|
||||||
|
switch (cmd.getSubtype()) {
|
||||||
|
case CMD_UPLOAD_START:
|
||||||
|
final XiaomiProto.DataUploadAck dataUploadAck = cmd.getDataUpload().getDataUploadAck();
|
||||||
|
LOG.debug("Got upload start, unknown2={}, unknown4={}", dataUploadAck.getUnknown2(), dataUploadAck.getUnknown4());
|
||||||
|
|
||||||
|
if (dataUploadAck.getUnknown2() != 0 || dataUploadAck.getUnknown4() != 0) {
|
||||||
|
LOG.warn("Unexpected response");
|
||||||
|
this.currentType = 0;
|
||||||
|
this.currentBytes = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doUpload(currentType, currentBytes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Unknown data upload command {}", cmd.getSubtype());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallback(@Nullable final Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestUpload(final byte type, final byte[] bytes) {
|
||||||
|
LOG.debug("Requesting upload for {} bytes of type {}", bytes.length, type);
|
||||||
|
|
||||||
|
this.currentType = type;
|
||||||
|
this.currentBytes = bytes;
|
||||||
|
|
||||||
|
getSupport().sendCommand(
|
||||||
|
"request upload",
|
||||||
|
XiaomiProto.Command.newBuilder()
|
||||||
|
.setType(COMMAND_TYPE)
|
||||||
|
.setSubtype(CMD_UPLOAD_START)
|
||||||
|
.setDataUpload(XiaomiProto.DataUpload.newBuilder().setDataUploadRequest(
|
||||||
|
XiaomiProto.DataUploadRequest.newBuilder()
|
||||||
|
.setType(type)
|
||||||
|
.setMd5Sum(ByteString.copyFrom(Objects.requireNonNull(CheckSums.md5(bytes))))
|
||||||
|
.setSize(bytes.length)
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUpload(final short type, final byte[] bytes) {
|
||||||
|
LOG.debug("Doing upload for {} bytes of type {}", bytes.length, type);
|
||||||
|
|
||||||
|
// type + md5 + size + bytes + crc32
|
||||||
|
final ByteBuffer buf1 = ByteBuffer.allocate(2 + 16 + 4 + bytes.length).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
final byte[] md5 = CheckSums.md5(bytes);
|
||||||
|
if (md5 == null) {
|
||||||
|
onUploadFinish(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf1.put((byte) 0);
|
||||||
|
buf1.put((byte) type);
|
||||||
|
buf1.put(md5);
|
||||||
|
buf1.putInt(bytes.length);
|
||||||
|
buf1.put(bytes);
|
||||||
|
|
||||||
|
final ByteBuffer buf2 = ByteBuffer.allocate(buf1.capacity() + 4).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf2.put(buf1.array());
|
||||||
|
buf2.putInt(CheckSums.getCRC32(buf1.array()));
|
||||||
|
|
||||||
|
final byte[] payload = buf2.array();
|
||||||
|
final int partSize = 2044; // 2 + 2 at beginning of each for total and progress
|
||||||
|
final int totalParts = (int) Math.ceil(payload.length / (float) partSize);
|
||||||
|
|
||||||
|
characteristic.setCallback(remainingParts -> {
|
||||||
|
final int totalBytes = totalParts * 4 + payload.length;
|
||||||
|
int progressBytes = totalParts * 4 + payload.length;
|
||||||
|
if (remainingParts > 1) {
|
||||||
|
progressBytes -= (remainingParts - 1) * partSize;
|
||||||
|
}
|
||||||
|
if (remainingParts > 0) {
|
||||||
|
progressBytes -= (payload.length % partSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int progressPercent = Math.round((100.0f * progressBytes) / totalBytes);
|
||||||
|
|
||||||
|
LOG.debug("Data upload progress: {} parts remaining ({}%)", remainingParts, progressPercent);
|
||||||
|
|
||||||
|
if (remainingParts > 0) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onUploadProgress(progressPercent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onUploadFinish(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i * partSize < payload.length; i++) {
|
||||||
|
final int startIndex = i * partSize;
|
||||||
|
final int endIndex = Math.min((i + 1) * partSize, payload.length);
|
||||||
|
LOG.debug("Uploading part {} of {}, from {} to {}", (i + 1), totalParts, startIndex, endIndex);
|
||||||
|
final byte[] chunkToSend = new byte[4 + endIndex - startIndex];
|
||||||
|
BLETypeConversions.writeUint16(chunkToSend, 0, totalParts);
|
||||||
|
BLETypeConversions.writeUint16(chunkToSend, 2, i + 1);
|
||||||
|
System.arraycopy(payload, startIndex, chunkToSend, 4, endIndex - startIndex);
|
||||||
|
characteristic.write("upload part " + (i + 1) + " of " + totalParts, chunkToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataUploadCharacteristic(final XiaomiCharacteristic characteristic) {
|
||||||
|
this.characteristic = characteristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUploadFinish(final boolean success) {
|
||||||
|
this.currentType = 0;
|
||||||
|
this.currentBytes = null;
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onUploadFinish(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
characteristic.setCallback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onUploadFinish(boolean success);
|
||||||
|
|
||||||
|
void onUploadProgress(int progress);
|
||||||
|
}
|
||||||
|
}
|
@ -27,14 +27,18 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8.XiaomiFWHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class XiaomiWatchfaceService extends AbstractXiaomiService {
|
public class XiaomiWatchfaceService extends AbstractXiaomiService implements XiaomiDataUploadService.Callback {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiWatchfaceService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(XiaomiWatchfaceService.class);
|
||||||
|
|
||||||
public static final int COMMAND_TYPE = 4;
|
public static final int COMMAND_TYPE = 4;
|
||||||
@ -48,6 +52,9 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService {
|
|||||||
private final Set<UUID> userWatchfaces = new HashSet<>();
|
private final Set<UUID> userWatchfaces = new HashSet<>();
|
||||||
private UUID activeWatchface = null;
|
private UUID activeWatchface = null;
|
||||||
|
|
||||||
|
// Not null if we're installing a firmware
|
||||||
|
private XiaomiFWHelper fwHelper = null;
|
||||||
|
|
||||||
public XiaomiWatchfaceService(final XiaomiSupport support) {
|
public XiaomiWatchfaceService(final XiaomiSupport support) {
|
||||||
super(support);
|
super(support);
|
||||||
}
|
}
|
||||||
@ -69,18 +76,22 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService {
|
|||||||
requestWatchfaceList();
|
requestWatchfaceList();
|
||||||
return;
|
return;
|
||||||
case CMD_WATCHFACE_INSTALL:
|
case CMD_WATCHFACE_INSTALL:
|
||||||
|
final int installStatus = cmd.getWatchface().getInstallStatus();
|
||||||
|
if (installStatus != 0) {
|
||||||
|
LOG.warn("Invalid watchface install status {} for {}", installStatus, fwHelper.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Watchface install status 0, uploading");
|
||||||
|
setDeviceBusy();
|
||||||
|
getSupport().getDataUploader().setCallback(this);
|
||||||
|
getSupport().getDataUploader().requestUpload(XiaomiDataUploadService.TYPE_WATCHFACE, fwHelper.getBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.warn("Unknown watchface command {}", cmd.getSubtype());
|
LOG.warn("Unknown watchface command {}", cmd.getSubtype());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSendConfiguration(final String config, final Prefs prefs) {
|
|
||||||
// TODO set watchface
|
|
||||||
return super.onSendConfiguration(config, prefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestWatchfaceList() {
|
public void requestWatchfaceList() {
|
||||||
getSupport().sendCommand("request watchface list", COMMAND_TYPE, CMD_WATCHFACE_LIST);
|
getSupport().sendCommand("request watchface list", COMMAND_TYPE, CMD_WATCHFACE_LIST);
|
||||||
}
|
}
|
||||||
@ -120,21 +131,24 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setWatchface(final UUID uuid) {
|
public void setWatchface(final UUID uuid) {
|
||||||
if (!allWatchfaces.contains(uuid)) {
|
final String id = toWatchfaceId(uuid);
|
||||||
LOG.warn("Unknown watchface {}", uuid);
|
|
||||||
return;
|
// TODO for now we need to allow when installing a watchface
|
||||||
}
|
//if (!allWatchfaces.contains(uuid)) {
|
||||||
|
// LOG.warn("Unknown watchface {}", uuid);
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
activeWatchface = uuid;
|
activeWatchface = uuid;
|
||||||
|
|
||||||
LOG.debug("Set watchface to {}", uuid);
|
LOG.debug("Set watchface to {}", id);
|
||||||
|
|
||||||
getSupport().sendCommand(
|
getSupport().sendCommand(
|
||||||
"set watchface to " + uuid,
|
"set watchface to " + uuid,
|
||||||
XiaomiProto.Command.newBuilder()
|
XiaomiProto.Command.newBuilder()
|
||||||
.setType(COMMAND_TYPE)
|
.setType(COMMAND_TYPE)
|
||||||
.setSubtype(CMD_WATCHFACE_SET)
|
.setSubtype(CMD_WATCHFACE_SET)
|
||||||
.setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceId(toWatchfaceId(uuid)))
|
.setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceId(id))
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -144,42 +158,58 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void deleteWatchface(final UUID uuid) {
|
public void deleteWatchface(final UUID uuid) {
|
||||||
|
final String id = toWatchfaceId(uuid);
|
||||||
|
|
||||||
if (!userWatchfaces.contains(uuid)) {
|
if (!userWatchfaces.contains(uuid)) {
|
||||||
LOG.warn("Refusing to delete non-user watchface {}", uuid);
|
LOG.warn("Refusing to delete non-user watchface {}", id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allWatchfaces.contains(uuid)) {
|
if (!allWatchfaces.contains(uuid)) {
|
||||||
LOG.warn("Refusing to delete unknown watchface {}", uuid);
|
LOG.warn("Refusing to delete unknown watchface {}", id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uuid.equals(activeWatchface)) {
|
if (uuid.equals(activeWatchface)) {
|
||||||
LOG.warn("Refusing to delete active watchface {}", uuid);
|
LOG.warn("Refusing to delete active watchface {}", id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Delete watchface {}", uuid);
|
LOG.debug("Delete watchface {}", id);
|
||||||
|
|
||||||
allWatchfaces.remove(uuid);
|
allWatchfaces.remove(uuid);
|
||||||
userWatchfaces.remove(uuid);
|
userWatchfaces.remove(uuid);
|
||||||
|
|
||||||
getSupport().sendCommand(
|
getSupport().sendCommand(
|
||||||
"delete watchface " + uuid,
|
"delete watchface " + id,
|
||||||
XiaomiProto.Command.newBuilder()
|
XiaomiProto.Command.newBuilder()
|
||||||
.setType(COMMAND_TYPE)
|
.setType(COMMAND_TYPE)
|
||||||
.setSubtype(CMD_WATCHFACE_DELETE)
|
.setSubtype(CMD_WATCHFACE_DELETE)
|
||||||
.setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceId(toWatchfaceId(uuid)))
|
.setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceId(id))
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteWatchface(final String watchfaceId) {
|
public void installWatchface(final Uri uri) {
|
||||||
deleteWatchface(toWatchfaceUUID(watchfaceId));
|
fwHelper = new XiaomiFWHelper(uri, getSupport().getContext());
|
||||||
|
if (!fwHelper.isValid()) {
|
||||||
|
fwHelper = null;
|
||||||
|
LOG.warn("watchface is not valid");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installWatchface(final Uri uri) {
|
getSupport().sendCommand(
|
||||||
// TODO
|
"install watchface " + fwHelper.getId(),
|
||||||
|
XiaomiProto.Command.newBuilder()
|
||||||
|
.setType(COMMAND_TYPE)
|
||||||
|
.setSubtype(CMD_WATCHFACE_INSTALL)
|
||||||
|
.setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceInstallStart(
|
||||||
|
XiaomiProto.WatchfaceInstallStart.newBuilder()
|
||||||
|
.setId(fwHelper.getId())
|
||||||
|
.setSize(fwHelper.getBytes().length)
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UUID toWatchfaceUUID(final String id) {
|
public static UUID toWatchfaceUUID(final String id) {
|
||||||
@ -201,4 +231,58 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService {
|
|||||||
.replaceAll("f", "")
|
.replaceAll("f", "")
|
||||||
.replaceAll("F", "");
|
.replaceAll("F", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUploadFinish(final boolean success) {
|
||||||
|
LOG.debug("Watchface upload finished: {}", success);
|
||||||
|
|
||||||
|
getSupport().getDataUploader().setCallback(null);
|
||||||
|
|
||||||
|
final String notificationMessage = success ?
|
||||||
|
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_complete) :
|
||||||
|
getSupport().getContext().getString(R.string.updatefirmwareoperation_write_failed);
|
||||||
|
|
||||||
|
GB.updateInstallNotification(notificationMessage, false, 100, getSupport().getContext());
|
||||||
|
|
||||||
|
unsetDeviceBusy();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
setWatchface(fwHelper.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
fwHelper = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUploadProgress(final int progressPercent) {
|
||||||
|
try {
|
||||||
|
final TransactionBuilder builder = getSupport().createTransactionBuilder("send data upload progress");
|
||||||
|
builder.add(new SetProgressAction(
|
||||||
|
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_in_progress),
|
||||||
|
true,
|
||||||
|
progressPercent,
|
||||||
|
getSupport().getContext()
|
||||||
|
));
|
||||||
|
builder.queue(getSupport().getQueue());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to update progress notification", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDeviceBusy() {
|
||||||
|
final GBDevice device = getSupport().getDevice();
|
||||||
|
device.setBusyTask(getSupport().getContext().getString(R.string.updating_firmware));
|
||||||
|
device.sendDeviceUpdateIntent(getSupport().getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unsetDeviceBusy() {
|
||||||
|
final GBDevice device = getSupport().getDevice();
|
||||||
|
if (device != null && device.isConnected()) {
|
||||||
|
if (device.isBusy()) {
|
||||||
|
device.unsetBusyTask();
|
||||||
|
device.sendDeviceUpdateIntent(getSupport().getContext());
|
||||||
|
}
|
||||||
|
device.sendDeviceUpdateIntent(getSupport().getContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,22 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
public class CheckSums {
|
public class CheckSums {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(CheckSums.class);
|
||||||
|
|
||||||
public static int getCRC8(byte[] seq) {
|
public static int getCRC8(byte[] seq) {
|
||||||
int len = seq.length;
|
int len = seq.length;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -152,4 +161,17 @@ public class CheckSums {
|
|||||||
|
|
||||||
return 65535 & i2;
|
return 65535 & i2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static byte[] md5(final byte[] data) {
|
||||||
|
final MessageDigest md;
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("MD5");
|
||||||
|
} catch (final NoSuchAlgorithmException e) {
|
||||||
|
LOG.error("Failed to get md5 digest", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
md.update(data);
|
||||||
|
return md.digest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user