From ec050d7a4fa8ce451bc1c45cd03ccbf3e1a697ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 2 Dec 2023 11:25:32 +0000 Subject: [PATCH] Xiaomi: Unify encrypted and plaintext logic --- .../devices/xiaomi/XiaomiCoordinator.java | 37 +++++++ .../xiaomi/XiaomiEncryptedCoordinator.java | 50 ---------- .../xiaomi/XiaomiPlaintextCoordinator.java | 53 ---------- .../miband7pro/MiBand7ProCoordinator.java | 4 +- .../xiaomi/miband8/MiBand8Coordinator.java | 4 +- .../miwatch/MiWatchLiteCoordinator.java | 4 +- .../RedmiWatch3ActiveCoordinator.java | 4 +- .../XiaomiWatchS1ActiveCoordinator.java | 4 +- .../devices/xiaomi/XiaomiAuthService.java | 17 +++- .../devices/xiaomi/XiaomiBleUuids.java | 96 +++++++++++++++++++ .../xiaomi/XiaomiEncryptedSupport.java | 89 ----------------- .../xiaomi/XiaomiPlaintextSupport.java | 87 ----------------- .../service/devices/xiaomi/XiaomiSupport.java | 55 ++++++----- 13 files changed, 190 insertions(+), 314 deletions(-) delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiEncryptedCoordinator.java delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPlaintextCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleUuids.java delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java index b95413795..5f0359033 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java @@ -17,6 +17,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; import android.app.Activity; +import android.bluetooth.le.ScanFilter; +import android.os.ParcelUuid; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -25,7 +27,10 @@ import org.apache.commons.lang3.ArrayUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBException; @@ -49,9 +54,41 @@ import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiBleUuids; +import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.WorkoutSummaryParser; public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { + // On plaintext devices, user id is used as auth key - numeric + private static final Pattern AUTH_KEY_PATTERN = Pattern.compile("^[0-9]+$"); + + @NonNull + @Override + public Collection createBLEScanFilters() { + final List filters = new ArrayList<>(); + for (final UUID uuid : XiaomiBleUuids.UUIDS.keySet()) { + final ParcelUuid service = new ParcelUuid(uuid); + final ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build(); + filters.add(filter); + } + return filters; + } + + @NonNull + @Override + public Class getDeviceSupportClass() { + return XiaomiSupport.class; + } + + @Override + public boolean validateAuthKey(final String authKey) { + final byte[] authKeyBytes = authKey.trim().getBytes(); + // At this point we don't know if it's encrypted or not, so let's accept both: + return authKeyBytes.length == 32 || (authKey.startsWith("0x") && authKeyBytes.length == 34) + || AUTH_KEY_PATTERN.matcher(authKey.trim()).matches(); + } + @Override protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiEncryptedCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiEncryptedCoordinator.java deleted file mode 100644 index 07e29257e..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiEncryptedCoordinator.java +++ /dev/null @@ -1,50 +0,0 @@ -/* 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 . */ -package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; - -import android.bluetooth.le.ScanFilter; -import android.os.ParcelUuid; - -import androidx.annotation.NonNull; - -import java.util.Collection; -import java.util.Collections; - -import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiEncryptedSupport; - -public abstract class XiaomiEncryptedCoordinator extends XiaomiCoordinator { - @NonNull - @Override - public Collection createBLEScanFilters() { - final ParcelUuid service = new ParcelUuid(XiaomiEncryptedSupport.UUID_SERVICE_XIAOMI_FE95); - final ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build(); - return Collections.singletonList(filter); - } - - @NonNull - @Override - public Class getDeviceSupportClass() { - return XiaomiEncryptedSupport.class; - } - - @Override - public boolean validateAuthKey(final String authKey) { - final byte[] authKeyBytes = authKey.trim().getBytes(); - return authKeyBytes.length == 32 || (authKey.startsWith("0x") && authKeyBytes.length == 34); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPlaintextCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPlaintextCoordinator.java deleted file mode 100644 index d46242572..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPlaintextCoordinator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* 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 . */ -package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; - -import android.bluetooth.le.ScanFilter; -import android.os.ParcelUuid; - -import androidx.annotation.NonNull; - -import java.util.Collection; -import java.util.Collections; -import java.util.regex.Pattern; - -import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPlaintextSupport; - -public abstract class XiaomiPlaintextCoordinator extends XiaomiCoordinator { - // user id is used as auth key - numeric - private static final Pattern AUTH_KEY_PATTERN = Pattern.compile("^[0-9]+$"); - - @NonNull - @Override - public Collection createBLEScanFilters() { - final ParcelUuid service = new ParcelUuid(XiaomiPlaintextSupport.UUID_SERVICE); - final ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build(); - return Collections.singletonList(filter); - } - - @NonNull - @Override - public Class getDeviceSupportClass() { - return XiaomiPlaintextSupport.class; - } - - @Override - public boolean validateAuthKey(final String authKey) { - return AUTH_KEY_PATTERN.matcher(authKey.trim()).matches(); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java index 5fc2dadfd..5116ff122 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java @@ -26,10 +26,10 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst; -import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiEncryptedCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiInstallHandler; -public class MiBand7ProCoordinator extends XiaomiEncryptedCoordinator { +public class MiBand7ProCoordinator extends XiaomiCoordinator { @Override public boolean isExperimental() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java index 98fecb619..3a78f2582 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java @@ -25,10 +25,10 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; -import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiEncryptedCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiInstallHandler; -public class MiBand8Coordinator extends XiaomiEncryptedCoordinator { +public class MiBand8Coordinator extends XiaomiCoordinator { @Override public boolean isExperimental() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java index 41e237cb6..11a37aa41 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java @@ -25,11 +25,11 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiInstallHandler; -import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiPlaintextCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -public class MiWatchLiteCoordinator extends XiaomiPlaintextCoordinator { +public class MiWatchLiteCoordinator extends XiaomiCoordinator { @Override public boolean isExperimental() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java index c0de7da34..6a2586801 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java @@ -25,11 +25,11 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; -import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiEncryptedCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiInstallHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -public class RedmiWatch3ActiveCoordinator extends XiaomiEncryptedCoordinator { +public class RedmiWatch3ActiveCoordinator extends XiaomiCoordinator { @Override public boolean isExperimental() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java index 63470f1c0..3ed43f298 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java @@ -25,10 +25,10 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; -import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiEncryptedCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiInstallHandler; -public class XiaomiWatchS1ActiveCoordinator extends XiaomiEncryptedCoordinator { +public class XiaomiWatchS1ActiveCoordinator extends XiaomiCoordinator { @Override public int getDeviceNameResource() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java index bc3f79f76..9e8ed9aa3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java @@ -94,11 +94,11 @@ public class XiaomiAuthService extends AbstractXiaomiService { getSupport().sendCommand(builder, buildNonceCommand(nonce)); } - protected void startClearTextHandshake(final TransactionBuilder builder, String userId) { + protected void startClearTextHandshake(final TransactionBuilder builder) { builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.AUTHENTICATING, getSupport().getContext())); final XiaomiProto.Auth auth = XiaomiProto.Auth.newBuilder() - .setUserId(userId) + .setUserId(getUserId(getSupport().getDevice())) .build(); final XiaomiProto.Command command = XiaomiProto.Command.newBuilder() @@ -130,7 +130,7 @@ public class XiaomiAuthService extends AbstractXiaomiService { final TransactionBuilder builder = getSupport().createTransactionBuilder("auth step 2"); // TODO use sendCommand builder.write( - getSupport().getCharacteristic(XiaomiEncryptedSupport.UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), + getSupport().getCharacteristic(getSupport().characteristicCommandWrite.getCharacteristicUUID()), ArrayUtils.addAll(PAYLOAD_HEADER_AUTH, reply.toByteArray()) ); builder.queue(getSupport().getQueue()); @@ -301,6 +301,17 @@ public class XiaomiAuthService extends AbstractXiaomiService { return authKeyBytes; } + protected static String getUserId(final GBDevice device) { + final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()); + + final String authKey = sharedPrefs.getString("authkey", null); + if (StringUtils.isNotBlank(authKey)) { + return authKey; + } + + return "0000000000"; + } + protected static byte[] hmacSHA256(final byte[] key, final byte[] input) { try { final Mac mac = Mac.getInstance("HmacSHA256"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleUuids.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleUuids.java new file mode 100644 index 000000000..313e3d331 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleUuids.java @@ -0,0 +1,96 @@ +/* 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +public class XiaomiBleUuids { + public static final Map UUIDS = new LinkedHashMap() {{ + // all encrypted devices seem to share the same characteristics + // Mi Band 8 + // Redmi Watch 3 Active + // Xiaomi Watch S1 Active + put(UUID.fromString("0000fe95-0000-1000-8000-00805f9b34fb"), new XiaomiBleUuidSet( + true, + UUID.fromString("00000051-0000-1000-8000-00805f9b34fb"), + UUID.fromString("00000052-0000-1000-8000-00805f9b34fb"), + UUID.fromString("00000053-0000-1000-8000-00805f9b34fb"), + UUID.fromString("00000055-0000-1000-8000-00805f9b34fb") + )); + + // Mi Watch Lite + put(UUID.fromString("16186f00-0000-1000-8000-00807f9b34fb"), new XiaomiBleUuidSet( + false, + UUID.fromString("16186f01-0000-1000-8000-00807f9b34fb"), + UUID.fromString("16186f02-0000-1000-8000-00807f9b34fb"), + UUID.fromString("16186f03-0000-1000-8000-00807f9b34fb"), + UUID.fromString("16186f04-0000-1000-8000-00807f9b34fb") + )); + + // Mi Watch Color Sport + put(UUID.fromString("1314f000-1000-9000-7000-301291e21220"), new XiaomiBleUuidSet( + false, + UUID.fromString("1314f005-1000-9000-7000-301291e21220"), + UUID.fromString("1314f001-1000-9000-7000-301291e21220"), + UUID.fromString("1314f002-1000-9000-7000-301291e21220"), + UUID.fromString("1314f007-1000-9000-7000-301291e21220") + )); + }}; + + public static class XiaomiBleUuidSet { + private final boolean encrypted; + private final UUID characteristicCommandRead; + private final UUID characteristicCommandWrite; + private final UUID characteristicActivityData; + private final UUID characteristicDataUpload; + + public XiaomiBleUuidSet(final boolean encrypted, + final UUID characteristicCommandRead, + final UUID characteristicCommandWrite, + final UUID characteristicActivityData, + final UUID characteristicDataUpload) { + + this.encrypted = encrypted; + this.characteristicCommandRead = characteristicCommandRead; + this.characteristicCommandWrite = characteristicCommandWrite; + this.characteristicActivityData = characteristicActivityData; + this.characteristicDataUpload = characteristicDataUpload; + } + + protected boolean isEncrypted() { + return encrypted; + } + + protected UUID getCharacteristicCommandRead() { + return characteristicCommandRead; + } + + protected UUID getCharacteristicCommandWrite() { + return characteristicCommandWrite; + } + + protected UUID getCharacteristicActivityData() { + return characteristicActivityData; + } + + protected UUID getCharacteristicDataUpload() { + return characteristicDataUpload; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java deleted file mode 100644 index aa38cea1e..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java +++ /dev/null @@ -1,89 +0,0 @@ -/* 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 . */ -package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.UUID; - -import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; -import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; - -public class XiaomiEncryptedSupport extends XiaomiSupport { - private static final Logger LOG = LoggerFactory.getLogger(XiaomiEncryptedSupport.class); - - public static final String BASE_UUID = "0000%s-0000-1000-8000-00805f9b34fb"; - - public static final UUID UUID_SERVICE_XIAOMI_FE95 = UUID.fromString((String.format(BASE_UUID, "fe95"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0050 = UUID.fromString((String.format(BASE_UUID, "0050"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_COMMAND_READ = UUID.fromString((String.format(BASE_UUID, "0051"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE = UUID.fromString((String.format(BASE_UUID, "0052"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_ACTIVITY_DATA = UUID.fromString((String.format(BASE_UUID, "0053"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0054 = UUID.fromString((String.format(BASE_UUID, "0054"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_DATA_UPLOAD = UUID.fromString((String.format(BASE_UUID, "0055"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0056 = UUID.fromString((String.format(BASE_UUID, "0056"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0057 = UUID.fromString((String.format(BASE_UUID, "0057"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0058 = UUID.fromString((String.format(BASE_UUID, "0058"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0059 = UUID.fromString((String.format(BASE_UUID, "0059"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_005A = UUID.fromString((String.format(BASE_UUID, "005a"))); - - public static final UUID UUID_SERVICE_XIAOMI_FDAB = UUID.fromString((String.format(BASE_UUID, "fdab"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0001 = UUID.fromString((String.format(BASE_UUID, "0001"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0002 = UUID.fromString((String.format(BASE_UUID, "0002"))); - public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0003 = UUID.fromString((String.format(BASE_UUID, "0003"))); - - public XiaomiEncryptedSupport() { - super(); - addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); - addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE); - addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); - addSupportedService(GattService.UUID_SERVICE_HUMAN_INTERFACE_DEVICE); - addSupportedService(UUID_SERVICE_XIAOMI_FE95); - addSupportedService(UUID_SERVICE_XIAOMI_FDAB); - } - - @Override - protected boolean isEncrypted() { - return true; - } - - @Override - protected UUID getCharacteristicCommandRead() { - return UUID_CHARACTERISTIC_XIAOMI_COMMAND_READ; - } - - @Override - protected UUID getCharacteristicCommandWrite() { - return UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE; - } - - @Override - protected UUID getCharacteristicActivityData() { - return UUID_CHARACTERISTIC_XIAOMI_ACTIVITY_DATA; - } - - @Override - protected UUID getCharacteristicDataUpload() { - return UUID_CHARACTERISTIC_XIAOMI_DATA_UPLOAD; - } - - @Override - protected void startAuthentication(final TransactionBuilder builder) { - authService.startEncryptedHandshake(builder); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java deleted file mode 100644 index a975d1f60..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (C) 2023 Andreas Shimokawa - - 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 . */ -package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi; - -import android.content.SharedPreferences; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.UUID; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; - -public class XiaomiPlaintextSupport extends XiaomiSupport { - private static final Logger LOG = LoggerFactory.getLogger(XiaomiPlaintextSupport.class); - - public static final UUID UUID_SERVICE = UUID.fromString("16186f00-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_MAIN_READ = UUID.fromString("16186f01-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_MAIN_WRITE = UUID.fromString("16186f02-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_ACTIVITY_DATA = UUID.fromString("16186f03-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_DATA_UPLOAD = UUID.fromString("16186f04-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_UNK5 = UUID.fromString("16186f05-0000-1000-8000-00807f9b34fb"); - - public XiaomiPlaintextSupport() { - super(); - addSupportedService(UUID_SERVICE); - } - - @Override - protected boolean isEncrypted() { - return false; - } - - @Override - protected UUID getCharacteristicCommandRead() { - return UUID_CHARACTERISTIC_MAIN_READ; - } - - @Override - protected UUID getCharacteristicCommandWrite() { - return UUID_CHARACTERISTIC_MAIN_WRITE; - } - - @Override - protected UUID getCharacteristicActivityData() { - return UUID_CHARACTERISTIC_ACTIVITY_DATA; - } - - @Override - protected UUID getCharacteristicDataUpload() { - return UUID_CHARACTERISTIC_DATA_UPLOAD; - } - - @Override - protected void startAuthentication(final TransactionBuilder builder) { - final String userId = getUserId(gbDevice); - authService.startClearTextHandshake(builder, userId); - } - - protected static String getUserId(final GBDevice device) { - final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()); - - final String authKey = sharedPrefs.getString("authkey", null); - if (StringUtils.isNotBlank(authKey)) { - return authKey; - } - - return "0000000000"; - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java index 3a1ceacac..3c200499b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.content.Context; import android.location.Location; import android.net.Uri; +import android.widget.Toast; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -67,7 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.Xiao import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; -public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport { +public class XiaomiSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class); protected XiaomiCharacteristic characteristicCommandRead; @@ -105,26 +106,32 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport { public XiaomiSupport() { super(LOG); + for (final UUID uuid : XiaomiBleUuids.UUIDS.keySet()) { + addSupportedService(uuid); + } } - protected abstract boolean isEncrypted(); - - protected abstract UUID getCharacteristicCommandRead(); - - protected abstract UUID getCharacteristicCommandWrite(); - - protected abstract UUID getCharacteristicActivityData(); - - protected abstract UUID getCharacteristicDataUpload(); - - protected abstract void startAuthentication(final TransactionBuilder builder); - @Override protected final TransactionBuilder initializeDevice(final TransactionBuilder builder) { - final BluetoothGattCharacteristic btCharacteristicCommandRead = getCharacteristic(getCharacteristicCommandRead()); - final BluetoothGattCharacteristic btCharacteristicCommandWrite = getCharacteristic(getCharacteristicCommandWrite()); - final BluetoothGattCharacteristic btCharacteristicActivityData = getCharacteristic(getCharacteristicActivityData()); - final BluetoothGattCharacteristic btCharacteristicDataUpload = getCharacteristic(getCharacteristicDataUpload()); + XiaomiBleUuids.XiaomiBleUuidSet uuidSet = null; + for (Map.Entry xiaomiUuid : XiaomiBleUuids.UUIDS.entrySet()) { + if (getSupportedServices().contains(xiaomiUuid.getKey())) { + uuidSet = xiaomiUuid.getValue(); + break; + } + } + + if (uuidSet == null) { + GB.toast(getContext(), "Failed to find known Xiaomi service", Toast.LENGTH_LONG, GB.ERROR); + LOG.warn("Failed to find known Xiaomi service"); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext())); + return builder; + } + + final BluetoothGattCharacteristic btCharacteristicCommandRead = getCharacteristic(uuidSet.getCharacteristicCommandRead()); + final BluetoothGattCharacteristic btCharacteristicCommandWrite = getCharacteristic(uuidSet.getCharacteristicCommandWrite()); + final BluetoothGattCharacteristic btCharacteristicActivityData = getCharacteristic(uuidSet.getCharacteristicActivityData()); + final BluetoothGattCharacteristic btCharacteristicDataUpload = getCharacteristic(uuidSet.getCharacteristicDataUpload()); // FIXME unsetDynamicState unsets the fw version, which causes problems.. if (getDevice().getFirmwareVersion() == null && mFirmwareVersion != null) { @@ -138,15 +145,15 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport { } this.characteristicCommandRead = new XiaomiCharacteristic(this, btCharacteristicCommandRead, authService); - this.characteristicCommandRead.setEncrypted(isEncrypted()); + this.characteristicCommandRead.setEncrypted(uuidSet.isEncrypted()); this.characteristicCommandRead.setHandler(this::handleCommandBytes); this.characteristicCommandWrite = new XiaomiCharacteristic(this, btCharacteristicCommandWrite, authService); - this.characteristicCommandWrite.setEncrypted(isEncrypted()); + this.characteristicCommandWrite.setEncrypted(uuidSet.isEncrypted()); this.characteristicActivityData = new XiaomiCharacteristic(this, btCharacteristicActivityData, authService); this.characteristicActivityData.setHandler(healthService.getActivityFetcher()::addChunk); - this.characteristicActivityData.setEncrypted(isEncrypted()); + this.characteristicActivityData.setEncrypted(uuidSet.isEncrypted()); this.characteristicDataUpload = new XiaomiCharacteristic(this, btCharacteristicDataUpload, authService); - this.characteristicDataUpload.setEncrypted(isEncrypted()); + this.characteristicDataUpload.setEncrypted(uuidSet.isEncrypted()); this.characteristicDataUpload.setIncrementNonce(false); this.dataUploadService.setDataUploadCharacteristic(this.characteristicDataUpload); @@ -159,7 +166,11 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport { builder.notify(btCharacteristicActivityData, true); builder.notify(btCharacteristicDataUpload, true); - startAuthentication(builder); + if (uuidSet.isEncrypted()) { + authService.startEncryptedHandshake(builder); + } else { + authService.startClearTextHandshake(builder); + } return builder; }