mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-25 11:26:47 +01:00
Xiaomi: Unify encrypted and plaintext logic
This commit is contained in:
parent
ba0ca1de75
commit
ec050d7a4f
@ -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<? extends ScanFilter> createBLEScanFilters() {
|
||||
final List<ScanFilter> 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<? extends DeviceSupport> 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,
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<? extends ScanFilter> 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<? extends DeviceSupport> 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);
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<? extends ScanFilter> 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<? extends DeviceSupport> getDeviceSupportClass() {
|
||||
return XiaomiPlaintextSupport.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateAuthKey(final String authKey) {
|
||||
return AUTH_KEY_PATTERN.matcher(authKey.trim()).matches();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<UUID, XiaomiBleUuidSet> UUIDS = new LinkedHashMap<UUID, XiaomiBleUuidSet>() {{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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);
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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";
|
||||
}
|
||||
}
|
@ -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<UUID, XiaomiBleUuids.XiaomiBleUuidSet> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user