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>dulfo</w>
<w>elagin</w>
<w>encryptable</w>
<w>eomäe</w>
<w>ericsson</w>
<w>exrizu</w>

View File

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

View File

@@ -396,11 +396,11 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(characteristicChunked2021Write, force2021Protocol(), mMTU);
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(mMTU);
}
if (force2021Protocol()) {
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 {
LOG.warn("Chunked 2021 characteristics are null, will attempt to reconnect");
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) {
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) {

View File

@@ -49,6 +49,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
private final byte[] privateEC = new byte[24];
private byte[] publicEC;
private final BluetoothGattCharacteristic characteristicChunked2021Write;
private final Huami2021ChunkedEncoder huami2021ChunkedEncoder;
private final Huami2021ChunkedDecoder huami2021ChunkedDecoder;
@@ -59,9 +60,11 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
final byte cryptFlags,
final HuamiSupport support,
final TransactionBuilder builder,
final BluetoothGattCharacteristic characteristicChunked2021Write,
final Huami2021ChunkedEncoder huami2021ChunkedEncoder,
final Huami2021ChunkedDecoder huami2021ChunkedDecoder) {
super(needsAuth, authFlags, cryptFlags, support, builder);
this.characteristicChunked2021Write = characteristicChunked2021Write;
this.huami2021ChunkedEncoder = huami2021ChunkedEncoder;
this.huami2021ChunkedDecoder = huami2021ChunkedDecoder;
this.huami2021ChunkedDecoder.setHuami2021Handler(this);
@@ -79,7 +82,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
sendPubKeyCommand[2] = 0x00;
sendPubKeyCommand[3] = 0x02;
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() {
@@ -148,7 +151,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
System.arraycopy(encryptedRandom1, 0, command, 1, 16);
System.arraycopy(encryptedRandom2, 0, command, 17, 16);
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);
}
} 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);
if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(characteristicChunked2021Write, force2021Protocol(), getMTU());
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(getMTU());
}
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) {
// Ensure communication for all services contains the encrypted flag reported by the service, since not all
// 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