Huami2021ChunkedEncoder: Remove dependency on BLE characteristic

This commit is contained in:
José Rebelo
2025-04-04 23:32:21 +01:00
parent 4b2c1089a9
commit 3ad5412d04
5 changed files with 32 additions and 35 deletions

View File

@@ -39,6 +39,7 @@
<w>drobnič</w> <w>drobnič</w>
<w>dulfo</w> <w>dulfo</w>
<w>elagin</w> <w>elagin</w>
<w>encryptable</w>
<w>eomäe</w> <w>eomäe</w>
<w>ericsson</w> <w>ericsson</w>
<w>exrizu</w> <w>exrizu</w>

View File

@@ -16,20 +16,17 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import java.util.function.Consumer;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils; import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
public class Huami2021ChunkedEncoder { public class Huami2021ChunkedEncoder {
private static final Logger LOG = LoggerFactory.getLogger(Huami2021ChunkedEncoder.class); private static final Logger LOG = LoggerFactory.getLogger(Huami2021ChunkedEncoder.class);
private final BluetoothGattCharacteristic characteristicChunked2021Write;
private byte writeHandle; private byte writeHandle;
// These must be volatile, since they are set by a different thread. Sometimes, GB might // These must be volatile, since they are set by a different thread. Sometimes, GB might
@@ -37,15 +34,9 @@ public class Huami2021ChunkedEncoder {
// to that thread later. // to that thread later.
private volatile int encryptedSequenceNr; private volatile int encryptedSequenceNr;
private volatile byte[] sharedSessionKey; private volatile byte[] sharedSessionKey;
private volatile int mMTU;
private final boolean force2021Protocol; public Huami2021ChunkedEncoder(final int mMTU) {
private volatile int mMTU = 23;
public Huami2021ChunkedEncoder(final BluetoothGattCharacteristic characteristicChunked2021Write,
final boolean force2021Protocol,
final int mMTU) {
this.characteristicChunked2021Write = characteristicChunked2021Write;
this.force2021Protocol = force2021Protocol;
this.mMTU = mMTU; this.mMTU = mMTU;
} }
@@ -58,9 +49,9 @@ public class Huami2021ChunkedEncoder {
this.mMTU = mMTU; this.mMTU = mMTU;
} }
public synchronized void write(final TransactionBuilder builder, public synchronized void write(final Consumer<byte[]> chunkWriter,
final short type, final short type,
byte[] data, final byte[] data,
final boolean extended_flags, final boolean extended_flags,
final boolean encrypt) { final boolean encrypt) {
if (encrypt && sharedSessionKey == null) { if (encrypt && sharedSessionKey == null) {
@@ -71,7 +62,7 @@ public class Huami2021ChunkedEncoder {
writeHandle++; writeHandle++;
int remaining = data.length; int remaining = data.length;
int length = data.length; final int length = data.length;
byte count = 0; byte count = 0;
int header_size = 10; int header_size = 10;
@@ -79,10 +70,11 @@ public class Huami2021ChunkedEncoder {
header_size++; header_size++;
} }
final byte[] dataToSend;
if (extended_flags && encrypt) { if (extended_flags && encrypt) {
byte[] messagekey = new byte[16]; final byte[] messageKey = new byte[16];
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
messagekey[i] = (byte) (sharedSessionKey[i] ^ writeHandle); messageKey[i] = (byte) (sharedSessionKey[i] ^ writeHandle);
} }
int encrypted_length = length + 8; int encrypted_length = length + 8;
int overflow = encrypted_length % 16; int overflow = encrypted_length % 16;
@@ -90,7 +82,7 @@ public class Huami2021ChunkedEncoder {
encrypted_length += (16 - overflow); encrypted_length += (16 - overflow);
} }
byte[] encryptable_payload = new byte[encrypted_length]; final byte[] encryptable_payload = new byte[encrypted_length];
System.arraycopy(data, 0, encryptable_payload, 0, length); System.arraycopy(data, 0, encryptable_payload, 0, length);
encryptable_payload[length] = (byte) (encryptedSequenceNr & 0xff); encryptable_payload[length] = (byte) (encryptedSequenceNr & 0xff);
encryptable_payload[length + 1] = (byte) ((encryptedSequenceNr >> 8) & 0xff); encryptable_payload[length + 1] = (byte) ((encryptedSequenceNr >> 8) & 0xff);
@@ -104,18 +96,19 @@ public class Huami2021ChunkedEncoder {
encryptable_payload[length + 7] = (byte) ((checksum >> 24) & 0xff); encryptable_payload[length + 7] = (byte) ((checksum >> 24) & 0xff);
remaining = encrypted_length; remaining = encrypted_length;
try { try {
data = CryptoUtils.encryptAES(encryptable_payload, messagekey); dataToSend = CryptoUtils.encryptAES(encryptable_payload, messageKey);
} catch (Exception e) { } catch (Exception e) {
LOG.error("error while encrypting", e); LOG.error("error while encrypting", e);
return; return;
} }
} else {
dataToSend = data;
} }
while (remaining > 0) { while (remaining > 0) {
int MAX_CHUNKLENGTH = mMTU - 3 - header_size; final int maxChunkLength = mMTU - 3 - header_size;
int copybytes = Math.min(remaining, MAX_CHUNKLENGTH); int copyBytes = Math.min(remaining, maxChunkLength);
byte[] chunk = new byte[copybytes + header_size]; byte[] chunk = new byte[copyBytes + header_size];
byte flags = 0; byte flags = 0;
if (encrypt) { if (encrypt) {
@@ -134,7 +127,7 @@ public class Huami2021ChunkedEncoder {
chunk[i++] = (byte) (type & 0xff); chunk[i++] = (byte) (type & 0xff);
chunk[i] = (byte) ((type >> 8) & 0xff); chunk[i] = (byte) ((type >> 8) & 0xff);
} }
if (remaining <= MAX_CHUNKLENGTH) { if (remaining <= maxChunkLength) {
flags |= 0x06; // last chunk? flags |= 0x06; // last chunk?
} }
chunk[0] = 0x03; chunk[0] = 0x03;
@@ -148,9 +141,9 @@ public class Huami2021ChunkedEncoder {
chunk[3] = count; chunk[3] = count;
} }
System.arraycopy(data, data.length - remaining, chunk, header_size, copybytes); System.arraycopy(dataToSend, dataToSend.length - remaining, chunk, header_size, copyBytes);
builder.write(characteristicChunked2021Write, chunk); chunkWriter.accept(chunk);
remaining -= copybytes; remaining -= copyBytes;
header_size = 4; header_size = 4;
if (extended_flags) { if (extended_flags) {

View File

@@ -396,11 +396,11 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
} }
characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE); characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) { if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(characteristicChunked2021Write, force2021Protocol(), mMTU); huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(mMTU);
} }
if (force2021Protocol()) { if (force2021Protocol()) {
if (characteristicChunked2021Write != null && characteristicChunked2021Read != null) { if (characteristicChunked2021Write != null && characteristicChunked2021Read != null) {
new InitOperation2021(authenticate, authFlags, cryptFlags, this, builder, huami2021ChunkedEncoder, huami2021ChunkedDecoder).perform(); new InitOperation2021(authenticate, authFlags, cryptFlags, this, builder, characteristicChunked2021Write, huami2021ChunkedEncoder, huami2021ChunkedDecoder).perform();
} else { } else {
LOG.warn("Chunked 2021 characteristics are null, will attempt to reconnect"); LOG.warn("Chunked 2021 characteristics are null, will attempt to reconnect");
builder.add(new SetDeviceStateAction(getDevice(), State.WAITING_FOR_RECONNECT, getContext())); builder.add(new SetDeviceStateAction(getDevice(), State.WAITING_FOR_RECONNECT, getContext()));
@@ -4091,7 +4091,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
} }
public void writeToChunked2021(TransactionBuilder builder, short type, byte[] data, boolean encrypt) { public void writeToChunked2021(TransactionBuilder builder, short type, byte[] data, boolean encrypt) {
huami2021ChunkedEncoder.write(builder, type, data, force2021Protocol(), encrypt); huami2021ChunkedEncoder.write(chunk -> builder.write(characteristicChunked2021Write, chunk), type, data, force2021Protocol(), encrypt);
} }
public void writeToChunked2021(final String taskName, short type, byte data, boolean encrypt) { public void writeToChunked2021(final String taskName, short type, byte data, boolean encrypt) {

View File

@@ -49,6 +49,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
private final byte[] privateEC = new byte[24]; private final byte[] privateEC = new byte[24];
private byte[] publicEC; private byte[] publicEC;
private final BluetoothGattCharacteristic characteristicChunked2021Write;
private final Huami2021ChunkedEncoder huami2021ChunkedEncoder; private final Huami2021ChunkedEncoder huami2021ChunkedEncoder;
private final Huami2021ChunkedDecoder huami2021ChunkedDecoder; private final Huami2021ChunkedDecoder huami2021ChunkedDecoder;
@@ -59,9 +60,11 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
final byte cryptFlags, final byte cryptFlags,
final HuamiSupport support, final HuamiSupport support,
final TransactionBuilder builder, final TransactionBuilder builder,
final BluetoothGattCharacteristic characteristicChunked2021Write,
final Huami2021ChunkedEncoder huami2021ChunkedEncoder, final Huami2021ChunkedEncoder huami2021ChunkedEncoder,
final Huami2021ChunkedDecoder huami2021ChunkedDecoder) { final Huami2021ChunkedDecoder huami2021ChunkedDecoder) {
super(needsAuth, authFlags, cryptFlags, support, builder); super(needsAuth, authFlags, cryptFlags, support, builder);
this.characteristicChunked2021Write = characteristicChunked2021Write;
this.huami2021ChunkedEncoder = huami2021ChunkedEncoder; this.huami2021ChunkedEncoder = huami2021ChunkedEncoder;
this.huami2021ChunkedDecoder = huami2021ChunkedDecoder; this.huami2021ChunkedDecoder = huami2021ChunkedDecoder;
this.huami2021ChunkedDecoder.setHuami2021Handler(this); this.huami2021ChunkedDecoder.setHuami2021Handler(this);
@@ -79,7 +82,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
sendPubKeyCommand[2] = 0x00; sendPubKeyCommand[2] = 0x00;
sendPubKeyCommand[3] = 0x02; sendPubKeyCommand[3] = 0x02;
System.arraycopy(publicEC, 0, sendPubKeyCommand, 4, 48); System.arraycopy(publicEC, 0, sendPubKeyCommand, 4, 48);
huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, sendPubKeyCommand, true, false); huami2021ChunkedEncoder.write(chunk -> builder.write(characteristicChunked2021Write, chunk), Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, sendPubKeyCommand, true, false);
} }
private void generateKeyPair() { private void generateKeyPair() {
@@ -148,7 +151,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
System.arraycopy(encryptedRandom1, 0, command, 1, 16); System.arraycopy(encryptedRandom1, 0, command, 1, 16);
System.arraycopy(encryptedRandom2, 0, command, 17, 16); System.arraycopy(encryptedRandom2, 0, command, 17, 16);
TransactionBuilder builder = createTransactionBuilder("Sending double encryted random to device"); TransactionBuilder builder = createTransactionBuilder("Sending double encryted random to device");
huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, command, true, false); huami2021ChunkedEncoder.write(chunk -> builder.write(characteristicChunked2021Write, chunk), Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, command, true, false);
huamiSupport.performImmediately(builder); huamiSupport.performImmediately(builder);
} }
} catch (final Exception e) { } catch (final Exception e) {

View File

@@ -290,7 +290,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE); characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) { if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(characteristicChunked2021Write, force2021Protocol(), getMTU()); huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(getMTU());
} }
if (characteristicChunked2021Write == null || characteristicChunked2021Read == null) { if (characteristicChunked2021Write == null || characteristicChunked2021Read == null) {
@@ -1150,7 +1150,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
public void writeToChunked2021(final TransactionBuilder builder, final short endpoint, final byte[] data, final boolean encryptIgnored) { public void writeToChunked2021(final TransactionBuilder builder, final short endpoint, final byte[] data, final boolean encryptIgnored) {
// Ensure communication for all services contains the encrypted flag reported by the service, since not all // Ensure communication for all services contains the encrypted flag reported by the service, since not all
// watches have the same services encrypted (eg. #3308). // watches have the same services encrypted (eg. #3308).
huami2021ChunkedEncoder.write(builder, endpoint, data, force2021Protocol(), mIsEncrypted.contains(endpoint)); huami2021ChunkedEncoder.write(chunk -> builder.write(characteristicChunked2021Write, chunk), endpoint, data, force2021Protocol(), mIsEncrypted.contains(endpoint));
} }
@Override @Override