From 74dac3f5cdab93be3f9b2e01bcb3b180272f8d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 11 Jun 2023 15:45:41 +0100 Subject: [PATCH] Huami 2021: Handle chunked ACKs --- .../huami/Huami2021ChunkedDecoder.java | 30 ++++++++++--- .../service/devices/huami/HuamiSupport.java | 45 ++++++++++++++++++- .../huami/operations/InitOperation2021.java | 6 ++- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java index 19ed82cd3..3afd68774 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java @@ -33,6 +33,10 @@ public class Huami2021ChunkedDecoder { private int currentLength; ByteBuffer reassemblyBuffer; + // Keep track of last handle and count for acks + private byte lastHandle; + private byte lastCount; + private volatile byte[] sharedSessionKey; private Huami2021Handler huami2021Handler; @@ -52,16 +56,25 @@ public class Huami2021ChunkedDecoder { this.huami2021Handler = huami2021Handler; } - public void decode(final byte[] data) { + public byte getLastHandle() { + return lastHandle; + } + + public byte getLastCount() { + return lastCount; + } + + public boolean decode(final byte[] data) { int i = 0; if (data[i++] != 0x03) { - //LOG.warn("Ignoring non-chunked payload"); - return; + LOG.warn("Ignoring non-chunked payload"); + return false; } final byte flags = data[i++]; final boolean encrypted = ((flags & 0x08) == 0x08); final boolean firstChunk = ((flags & 0x01) == 0x01); final boolean lastChunk = ((flags & 0x02) == 0x02); + final boolean needsAck = ((flags & 0x04) == 0x04); if (force2021Protocol) { i++; // skip extended header @@ -69,9 +82,10 @@ public class Huami2021ChunkedDecoder { final byte handle = data[i++]; if (currentHandle != null && currentHandle != handle) { LOG.warn("ignoring handle {}, expected {}", handle, currentHandle); - return; + return false; } - byte count = data[i++]; + lastHandle = handle; + lastCount = data[i++]; if (firstChunk) { // beginning int full_length = (data[i++] & 0xff) | ((data[i++] & 0xff) << 8) | ((data[i++] & 0xff) << 16) | ((data[i++] & 0xff) << 24); currentLength = full_length; @@ -96,7 +110,7 @@ public class Huami2021ChunkedDecoder { LOG.warn("Got encrypted message, but there's no shared session key"); currentHandle = null; currentType = 0; - return; + return false; } byte[] messagekey = new byte[16]; @@ -110,7 +124,7 @@ public class Huami2021ChunkedDecoder { LOG.warn("error decrypting " + e); currentHandle = null; currentType = 0; - return; + return false; } } LOG.debug( @@ -128,5 +142,7 @@ public class Huami2021ChunkedDecoder { currentHandle = null; currentType = 0; } + + return needsAck; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index 759d95e5d..09de66761 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -2293,8 +2293,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements } else if (HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION.equals(characteristicUUID)) { handleConfigurationInfo(characteristic.getValue()); return true; - } else if (HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID) && huami2021ChunkedDecoder != null) { - huami2021ChunkedDecoder.decode(characteristic.getValue()); + } else if (HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID)) { + handleChunked(characteristic.getValue()); return true; } else if (HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_DATA.equals(characteristicUUID)) { handleRawSensorData(characteristic.getValue()); @@ -2455,6 +2455,47 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements } } + private void handleChunked(final byte[] value) { + switch (value[0]) { + case 0x03: + if (huami2021ChunkedDecoder != null) { + final boolean needsAck = huami2021ChunkedDecoder.decode(value); + if (needsAck) { + sendChunkedAck(); + } + } else { + LOG.warn("Got chunked payload, but decoder is null"); + } + return; + case 0x04: + final byte handle = value[2]; + final byte count = value[4]; + LOG.info("Got chunked ack, handle={}, count={}", handle, count); + // TODO: We should probably update the handle and count on the encoder + return; + default: + LOG.warn("Unhandled chunked payload of type {}", value[0]); + } + } + + public void sendChunkedAck() { + if (characteristicChunked2021Read == null) { + LOG.error("Chunked read characteristic is null, can't send ack"); + return; + } + + final byte handle = huami2021ChunkedDecoder.getLastHandle(); + final byte count = huami2021ChunkedDecoder.getLastCount(); + + try { + final TransactionBuilder builder = performInitialized("send chunked ack"); + builder.write(characteristicChunked2021Read, new byte[] {0x04, 0x00, handle, 0x01, count}); + builder.queue(getQueue()); + } catch (final Exception e) { + LOG.error("Failed to send chunked ack", e); + } + } + private void decodeAndUpdateAlarmStatus(byte[] response, boolean withTimes) { List alarms = DBHelper.getAlarms(gbDevice); int maxAlarms = 10; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java index fd5357b04..1ed4436a6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java @@ -127,7 +127,11 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler return super.onCharacteristicChanged(gatt, characteristic); } - this.huami2021ChunkedDecoder.decode(value); + final boolean needsAck = huami2021ChunkedDecoder.decode(value); + if (needsAck) { + huamiSupport.sendChunkedAck(); + } + return true; }