From 5ce8b998d9f2530cbcb74abe92df6a16264295f8 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 26 Mar 2020 20:25:20 +0100 Subject: [PATCH] RS v3 encryption --- app/src/main/AndroidManifest.xml | 2 + .../dependencyInjection/ActivitiesModule.kt | 2 + .../pump/danaR/AbstractDanaRPlugin.java | 30 +- .../plugins/pump/danaR/DanaRFragment.kt | 22 ++ .../plugins/pump/danaRS/DanaRSPlugin.java | 7 + .../danaRS/activities/EnterPinActivity.kt | 99 ++++++ .../danaRS/comm/DanaRSMessageHashTable.kt | 2 +- ...cket_General_Initial_Screen_Information.kt | 1 - .../danaRS/comm/DanaRS_Packet_Notify_Alarm.kt | 14 +- .../pump/danaRS/encryption/BleEncryption.java | 2 +- .../plugins/pump/danaRS/services/BLEComm.kt | 310 +++++++++++++----- .../extensions/HexByteArrayConversion.kt | 32 ++ .../textValidator/DefaultEditTextValidator.kt | 23 +- .../ValidatingEditTextPreference.kt | 3 +- .../res/layout/danars_enter_pin_activity.xml | 90 +++++ app/src/main/res/values/strings.xml | 12 + app/src/main/res/values/validator.xml | 4 + .../comm/DanaRS_Packet_Notify_AlarmTest.kt | 2 +- 18 files changed, 549 insertions(+), 108 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/EnterPinActivity.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/extensions/HexByteArrayConversion.kt create mode 100644 app/src/main/res/layout/danars_enter_pin_activity.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 01df007470..c89a41390a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -84,6 +84,8 @@ + diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt index e7411ad56b..57c8aea06e 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt @@ -13,6 +13,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyL import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRHistoryActivity import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRUserOptionsActivity import info.nightscout.androidaps.plugins.pump.danaRS.activities.BLEScanActivity +import info.nightscout.androidaps.plugins.pump.danaRS.activities.EnterPinActivity import info.nightscout.androidaps.plugins.pump.danaRS.activities.PairingHelperActivity import info.nightscout.androidaps.plugins.pump.insight.activities.InsightAlertActivity import info.nightscout.androidaps.plugins.pump.insight.activities.InsightPairingActivity @@ -28,6 +29,7 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributeBolusProgressHelperActivity(): BolusProgressHelperActivity @ContributesAndroidInjector abstract fun contributeDanaRHistoryActivity(): DanaRHistoryActivity @ContributesAndroidInjector abstract fun contributeDanaRUserOptionsActivity(): DanaRUserOptionsActivity + @ContributesAndroidInjector abstract fun contributeEnterPinActivity(): EnterPinActivity @ContributesAndroidInjector abstract fun contributeErrorHelperActivity(): ErrorHelperActivity @ContributesAndroidInjector abstract fun contributesHistoryBrowseActivity(): HistoryBrowseActivity @ContributesAndroidInjector abstract fun contributesInsightAlertActivity(): InsightAlertActivity diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java index b448990575..6de5376b89 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java @@ -15,6 +15,8 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.events.EventConfigBuilderChange; +import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.interfaces.CommandQueueProvider; import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; @@ -42,6 +44,8 @@ import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 28.01.2018. @@ -50,6 +54,8 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP; public abstract class AbstractDanaRPlugin extends PumpPluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface { protected AbstractDanaRExecutionService sExecutionService; + protected CompositeDisposable disposable = new CompositeDisposable(); + protected boolean useExtendedBoluses = false; protected PumpDescription pumpDescription = new PumpDescription(); @@ -86,6 +92,28 @@ public abstract class AbstractDanaRPlugin extends PumpPluginBase implements Pump this.sp = sp; } + @Override protected void onStart() { + super.onStart(); + disposable.add(rxBus + .toObservable(EventConfigBuilderChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> danaRPump.setLastConnection(0)) + ); + disposable.add(rxBus + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (event.isChanged(getResourceHelper(), R.string.key_danar_bt_name)) + danaRPump.setLastConnection(0); + }) + ); + } + + @Override protected void onStop() { + super.onStop(); + disposable.clear(); + } + @Override public boolean isSuspended() { return danaRPump.getPumpSuspended(); @@ -488,6 +516,4 @@ public abstract class AbstractDanaRPlugin extends PumpPluginBase implements Pump public void timeDateOrTimeZoneChanged() { } - - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt index 2e7104c9e1..e9e001ced0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt @@ -11,6 +11,7 @@ import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.TDDStatsActivity import info.nightscout.androidaps.dialogs.ProfileViewerDialog import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventInitializationChanged import info.nightscout.androidaps.events.EventPumpStatusChanged import info.nightscout.androidaps.events.EventTempBasalChange import info.nightscout.androidaps.interfaces.ActivePluginProvider @@ -24,13 +25,16 @@ import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRHistoryActi import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRUserOptionsActivity import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRNewStatus import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin +import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin import info.nightscout.androidaps.queue.events.EventQueueChanged import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.OKDialog import info.nightscout.androidaps.utils.SetWarnColor import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.extensions.plusAssign import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import kotlinx.android.synthetic.main.danar_fragment.* @@ -43,8 +47,10 @@ class DanaRFragment : DaggerFragment() { @Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var activePlugin: ActivePluginProvider @Inject lateinit var danaRKoreanPlugin: DanaRKoreanPlugin + @Inject lateinit var danaRSPlugin: DanaRSPlugin @Inject lateinit var danaRPump: DanaRPump @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var sp: SP private var disposable: CompositeDisposable = CompositeDisposable() @@ -93,12 +99,28 @@ class DanaRFragment : DaggerFragment() { danaRPump.lastConnection = 0 commandQueue.readStatus("Clicked connect to pump", null) } + if (danaRSPlugin.isEnabled()) + danar_btconnection.setOnLongClickListener { + activity?.let { + OKDialog.showConfirmation(it, resourceHelper.gs(R.string.resetpairing), Runnable { + sp.remove(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName) + sp.remove(resourceHelper.gs(R.string.key_danars_v3_randompairingkey) + danaRSPlugin.mDeviceName) + sp.remove(resourceHelper.gs(R.string.key_danars_v3_pairingkey) + danaRSPlugin.mDeviceName) + sp.remove(resourceHelper.gs(R.string.key_danars_v3_randomsynckey) + danaRSPlugin.mDeviceName) + }) + } + true + } } @Synchronized override fun onResume() { super.onResume() loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + disposable += rxBus + .toObservable(EventInitializationChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { fabricPrivacy.logException(it) }) disposable += rxBus .toObservable(EventDanaRNewStatus::class.java) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java index 3e865cb0e4..759b473d75 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java @@ -27,6 +27,7 @@ import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.events.EventConfigBuilderChange; import info.nightscout.androidaps.interfaces.CommandQueueProvider; import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; @@ -157,6 +158,11 @@ public class DanaRSPlugin extends PumpPluginBase implements PumpInterface, DanaR .observeOn(Schedulers.io()) .subscribe(event -> context.unbindService(mConnection), fabricPrivacy::logException) ); + disposable.add(rxBus + .toObservable(EventConfigBuilderChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> danaRPump.setLastConnection(0)) + ); disposable.add(rxBus .toObservable(EventDanaRSDeviceChange.class) .observeOn(Schedulers.io()) @@ -191,6 +197,7 @@ public class DanaRSPlugin extends PumpPluginBase implements PumpInterface, DanaR private void loadAddress() { mDeviceAddress = sp.getString(R.string.key_danars_address, ""); mDeviceName = sp.getString(R.string.key_danars_name, ""); + danaRPump.setLastConnection(0); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/EnterPinActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/EnterPinActivity.kt new file mode 100644 index 0000000000..524dfc9976 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/EnterPinActivity.kt @@ -0,0 +1,99 @@ +package info.nightscout.androidaps.plugins.pump.danaRS.activities + +import android.os.Bundle +import android.util.Base64 +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity +import info.nightscout.androidaps.events.EventPumpStatusChanged +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin +import info.nightscout.androidaps.plugins.pump.danaRS.services.BLEComm +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.extensions.hexStringToByteArray +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import info.nightscout.androidaps.utils.textValidator.DefaultEditTextValidator +import info.nightscout.androidaps.utils.textValidator.EditTextValidator +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.danars_enter_pin_activity.* +import kotlinx.android.synthetic.main.okcancel.* +import javax.inject.Inject +import kotlin.experimental.xor + +class EnterPinActivity : NoSplashAppCompatActivity() { + + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var danaRSPlugin: DanaRSPlugin + @Inject lateinit var sp: SP + @Inject lateinit var bleComm: BLEComm + + private val disposable = CompositeDisposable() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.danars_enter_pin_activity) + + val p1 = DefaultEditTextValidator(rs_v3_pin1, this) + .setTestErrorString(resourceHelper.gs(R.string.error_mustbe12hexadidits), this) + .setCustomRegexp(resourceHelper.gs(R.string.twelvehexanumber), this) + .setTestType(EditTextValidator.TEST_REGEXP, this) + val p2 = DefaultEditTextValidator(rs_v3_pin2, this) + .setTestErrorString(resourceHelper.gs(R.string.error_mustbe8hexadidits), this) + .setCustomRegexp(resourceHelper.gs(R.string.eighthexanumber), this) + .setTestType(EditTextValidator.TEST_REGEXP, this) + + ok.setOnClickListener { + if (p1.testValidity(false) && p2.testValidity(false)) { + val result = checkPairingCheckSum( + rs_v3_pin1.text.toString().hexStringToByteArray(), + rs_v3_pin2.text.toString().substring(0..5).hexStringToByteArray(), + rs_v3_pin2.text.toString().substring(6..7).hexStringToByteArray()) + if (result) { + bleComm.finishV3Pairing() + finish() + } + else OKDialog.show(this, resourceHelper.gs(R.string.error), resourceHelper.gs(R.string.danar_invalidinput)) + } + } + cancel.setOnClickListener { finish() } + } + + override fun onResume() { + super.onResume() + disposable.add(rxBus + .toObservable(EventPumpStatusChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ if (it.status == EventPumpStatusChanged.Status.DISCONNECTED) finish() }) { fabricPrivacy.logException(it) } + ) + } + + override fun onPause() { + super.onPause() + disposable.clear() + } + + private fun checkPairingCheckSum(pairingKey: ByteArray, randomPairingKey: ByteArray, checksum: ByteArray): Boolean { + + // pairingKey ByteArray(6) + // randomPairingKey ByteArray(3) + // checksum ByteArray(1) + + var pairingKeyCheckSum: Byte = 0 + for (i in pairingKey.indices) + pairingKeyCheckSum = pairingKeyCheckSum xor pairingKey[i] + + sp.putString(resourceHelper.gs(R.string.key_danars_v3_pairingkey) + danaRSPlugin.mDeviceName, Base64.encodeToString(pairingKey, Base64.DEFAULT)) + + for (i in randomPairingKey.indices) + pairingKeyCheckSum = pairingKeyCheckSum xor randomPairingKey[i] + + sp.putString(resourceHelper.gs(R.string.key_danars_v3_randompairingkey) + danaRSPlugin.mDeviceName, Base64.encodeToString(randomPairingKey, Base64.DEFAULT)) + + return checksum[0] == pairingKeyCheckSum + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRSMessageHashTable.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRSMessageHashTable.kt index c589bbe633..224ecd9ae5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRSMessageHashTable.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRSMessageHashTable.kt @@ -69,7 +69,7 @@ class DanaRSMessageHashTable @Inject constructor( put(DanaRS_Packet_General_Delivery_Status(aapsLogger)) put(DanaRS_Packet_General_Get_Password(aapsLogger, danaRPump)) put(DanaRS_Packet_General_Initial_Screen_Information(aapsLogger, danaRPump)) - put(DanaRS_Packet_Notify_Alarm(aapsLogger, resourceHelper)) + put(DanaRS_Packet_Notify_Alarm(aapsLogger, resourceHelper, rxBus)) put(DanaRS_Packet_Notify_Delivery_Complete(aapsLogger, rxBus, resourceHelper, danaRSPlugin)) put(DanaRS_Packet_Notify_Delivery_Rate_Display(aapsLogger, rxBus, resourceHelper, danaRSPlugin)) put(DanaRS_Packet_Notify_Missed_Bolus_Alarm(aapsLogger)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Initial_Screen_Information.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Initial_Screen_Information.kt index 574791a005..03471e7408 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Initial_Screen_Information.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Initial_Screen_Information.kt @@ -11,7 +11,6 @@ class DanaRS_Packet_General_Initial_Screen_Information( ) : DanaRS_Packet() { init { - type = BleEncryption.DANAR_PACKET__TYPE_RESPONSE opCode = BleEncryption.DANAR_PACKET__OPCODE_REVIEW__INITIAL_SCREEN_INFORMATION aapsLogger.debug(LTag.PUMPCOMM, "New message") } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Alarm.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Alarm.kt index 7650ff3704..f461c65f3c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Alarm.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Alarm.kt @@ -4,12 +4,16 @@ import info.nightscout.androidaps.plugins.pump.danaRS.encryption.BleEncryption import info.nightscout.androidaps.R import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.utils.resources.ResourceHelper class DanaRS_Packet_Notify_Alarm( private val aapsLogger: AAPSLogger, - private val resourceHelper: ResourceHelper + private val resourceHelper: ResourceHelper, + private val rxBus: RxBusWrapper ) : DanaRS_Packet() { init { @@ -29,11 +33,11 @@ class DanaRS_Packet_Notify_Alarm( 0x03 -> // Occlusion errorString = resourceHelper.gs(R.string.occlusion) 0x04 -> // LOW BATTERY - errorString = resourceHelper.gs(R.string.lowbattery) + errorString = resourceHelper.gs(R.string.pumpshutdown) 0x05 -> // Shutdown errorString = resourceHelper.gs(R.string.lowbattery) 0x06 -> // Basal Compare - errorString = "BasalCompare ????" + errorString = resourceHelper.gs(R.string.basalcompare) 0x09 -> // Empty Reservoir errorString = resourceHelper.gs(R.string.emptyreservoir) 0x07, 0xFF -> // Blood sugar measurement alert @@ -41,7 +45,7 @@ class DanaRS_Packet_Notify_Alarm( 0x08, 0xFE -> // Remaining insulin level errorString = resourceHelper.gs(R.string.remaininsulinalert) 0xFD -> // Blood sugar check miss alarm - errorString = "Blood sugar check miss alarm ???" + errorString = resourceHelper.gs(R.string.missedbolus) } // No error no need to upload anything if (errorString == "") { @@ -49,6 +53,8 @@ class DanaRS_Packet_Notify_Alarm( aapsLogger.debug(LTag.PUMPCOMM, "Error detected: $errorString") return } + val notification = Notification(Notification.USERMESSAGE, errorString, Notification.URGENT) + rxBus.send(EventNewNotification(notification)) NSUpload.uploadError(errorString) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/encryption/BleEncryption.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/encryption/BleEncryption.java index edd36f583e..b4fea5d8be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/encryption/BleEncryption.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/encryption/BleEncryption.java @@ -140,7 +140,7 @@ public class BleEncryption { return encryptSecondLevelPacketJni(context, bytes); } - public byte[] ecryptSecondLevelPacket(byte[] bytes) { + public byte[] decryptSecondLevelPacket(byte[] bytes) { return decryptSecondLevelPacketJni(context, bytes); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.kt index 1260573c34..1e6a5f1642 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.kt @@ -4,6 +4,7 @@ import android.bluetooth.* import android.content.Context import android.content.Intent import android.os.SystemClock +import android.util.Base64 import info.nightscout.androidaps.R import info.nightscout.androidaps.events.EventPumpStatusChanged import info.nightscout.androidaps.logging.AAPSLogger @@ -14,11 +15,13 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin +import info.nightscout.androidaps.plugins.pump.danaRS.activities.EnterPinActivity import info.nightscout.androidaps.plugins.pump.danaRS.activities.PairingHelperActivity import info.nightscout.androidaps.plugins.pump.danaRS.comm.DanaRSMessageHashTable import info.nightscout.androidaps.plugins.pump.danaRS.comm.DanaRS_Packet import info.nightscout.androidaps.plugins.pump.danaRS.encryption.BleEncryption import info.nightscout.androidaps.plugins.pump.danaRS.events.EventDanaRSPairingSuccess +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP import okhttp3.internal.notify @@ -58,8 +61,17 @@ class BLEComm @Inject internal constructor( private var connectDeviceName: String? = null private var bluetoothGatt: BluetoothGatt? = null + private var v3Encryption: Boolean = false + set(newValue) { + bleEncryption.setEnhancedEncryption(newValue) + field = newValue + } + private var isEasyMode: Boolean = false + private var isUnitUD: Boolean = false + var isConnected = false var isConnecting = false + private var encryptedDataRead = false private var uartRead: BluetoothGattCharacteristic? = null private var uartWrite: BluetoothGattCharacteristic? = null @@ -106,6 +118,14 @@ class BLEComm @Inject internal constructor( fun disconnect(from: String) { aapsLogger.debug(LTag.PUMPBTCOMM, "disconnect from: $from") + if (!encryptedDataRead) { + // there was no response from pump after started encryption + // assume pairing keys are invalid + sp.remove(resourceHelper.gs(R.string.key_danars_v3_randompairingkey) + danaRSPlugin.mDeviceName) + sp.remove(resourceHelper.gs(R.string.key_danars_v3_pairingkey) + danaRSPlugin.mDeviceName) + sp.remove(resourceHelper.gs(R.string.key_danars_v3_randomsynckey) + danaRSPlugin.mDeviceName) + ToastUtils.showToastInUiThread(context, R.string.invalidpairing) + } // cancel previous scheduled disconnection to prevent closing upcoming connection scheduledDisconnection?.cancel(false) scheduledDisconnection = null @@ -118,6 +138,7 @@ class BLEComm @Inject internal constructor( setCharacteristicNotification(uartReadBTGattChar, false) bluetoothGatt?.disconnect() isConnected = false + encryptedDataRead = false SystemClock.sleep(2000) } @@ -142,19 +163,20 @@ class BLEComm @Inject internal constructor( } override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { - aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicRead: " + DanaRS_Packet.toHexString(characteristic.value)) - addToReadBuffer(characteristic.value) - readDataParsing() + // for v3 after initial handshake it's encrypted - useless + // aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicRead: " + DanaRS_Packet.toHexString(characteristic.value)) + readDataParsing(characteristic.value) } override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { - aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicChanged: " + DanaRS_Packet.toHexString(characteristic.value)) - addToReadBuffer(characteristic.value) - Thread(Runnable { readDataParsing() }).start() + // for v3 after initial handshake it's encrypted - useless + // aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicChanged: " + DanaRS_Packet.toHexString(characteristic.value)) + readDataParsing(characteristic.value) } override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { - aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicWrite: " + DanaRS_Packet.toHexString(characteristic.value)) + // for v3 after initial handshake it's encrypted - useless + // aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicWrite: " + DanaRS_Packet.toHexString(characteristic.value)) Thread(Runnable { synchronized(mSendQueue) { // after message sent, check if there is the rest of the message waiting and send it @@ -175,9 +197,10 @@ class BLEComm @Inject internal constructor( aapsLogger.error("BluetoothAdapter not initialized_ERROR") isConnecting = false isConnected = false + encryptedDataRead = false return } - bluetoothGatt!!.setCharacteristicNotification(characteristic, enabled) + bluetoothGatt?.setCharacteristicNotification(characteristic, enabled) } @Synchronized @@ -188,13 +211,15 @@ class BLEComm @Inject internal constructor( aapsLogger.error("BluetoothAdapter not initialized_ERROR") isConnecting = false isConnected = false + encryptedDataRead = false return@Runnable } characteristic.value = data characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE - aapsLogger.debug("writeCharacteristic:" + DanaRS_Packet.toHexString(data)) - bluetoothGatt!!.writeCharacteristic(characteristic) + //aapsLogger.debug("writeCharacteristic:" + DanaRS_Packet.toHexString(data)) + bluetoothGatt?.writeCharacteristic(characteristic) }).start() + waitMillis(50) } private val uartReadBTGattChar: BluetoothGattCharacteristic @@ -211,6 +236,7 @@ class BLEComm @Inject internal constructor( aapsLogger.error("BluetoothAdapter not initialized_ERROR") isConnecting = false isConnected = false + encryptedDataRead = false return null } return bluetoothGatt?.services @@ -243,6 +269,8 @@ class BLEComm @Inject internal constructor( close() isConnected = false isConnecting = false + v3Encryption = false + encryptedDataRead = false rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)) aapsLogger.debug(LTag.PUMPBTCOMM, "Device was disconnected " + gatt.device.name) //Device was disconnected } @@ -263,13 +291,20 @@ class BLEComm @Inject internal constructor( } } - private fun readDataParsing() { + private fun readDataParsing(receviedData: ByteArray) { + //aapsLogger.debug(LTag.PUMPBTCOMM, "readDataParsing") var startSignatureFound = false var packetIsValid = false var isProcessing: Boolean isProcessing = true var inputBuffer: ByteArray? = null + // decrypt 2nd level after successful connection + val incomingBuffer = if (v3Encryption && isConnected) + bleEncryption.decryptSecondLevelPacket(receviedData).also { encryptedDataRead = true } + else receviedData + addToReadBuffer(incomingBuffer) + while (isProcessing) { var length = 0 synchronized(readBuffer) { @@ -321,85 +356,163 @@ class BLEComm @Inject internal constructor( // decrypt the packet bleEncryption.getDecryptedPacket(inputBuffer)?.let { decryptedBuffer -> when (decryptedBuffer[0]) { - BleEncryption.DANAR_PACKET__TYPE_ENCRYPTION_RESPONSE.toByte() -> when (decryptedBuffer[1]) { - BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PUMP_CHECK.toByte() -> if (decryptedBuffer.size == 4 && decryptedBuffer[2] == 'O'.toByte() && decryptedBuffer[3] == 'K'.toByte()) { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (OK)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) - // Grab pairing key from preferences if exists - val pairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName, "") - aapsLogger.debug(LTag.PUMPBTCOMM, "Using stored pairing key: $pairingKey") - if (pairingKey.isNotEmpty()) { - val encodedPairingKey = DanaRS_Packet.hexToBytes(pairingKey) - val bytes = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__CHECK_PASSKEY, encodedPairingKey, null) - aapsLogger.debug(LTag.PUMPBTCOMM, ">>>>> " + "ENCRYPTION__CHECK_PASSKEY" + " " + DanaRS_Packet.toHexString(bytes)) - writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) - } else { - // Stored pairing key does not exists, request pairing - sendPairingRequest() - } - } else if (decryptedBuffer.size == 6 && decryptedBuffer[2] == 'P'.toByte() && decryptedBuffer[3] == 'U'.toByte() && decryptedBuffer[4] == 'M'.toByte() && decryptedBuffer[5] == 'P'.toByte()) { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (PUMP)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) - mSendQueue.clear() - rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, resourceHelper.gs(R.string.pumperror))) - NSUpload.uploadError(resourceHelper.gs(R.string.pumperror)) - val n = Notification(Notification.PUMPERROR, resourceHelper.gs(R.string.pumperror), Notification.URGENT) - rxBus.send(EventNewNotification(n)) - } else if (decryptedBuffer.size == 6 && decryptedBuffer[2] == 'B'.toByte() && decryptedBuffer[3] == 'U'.toByte() && decryptedBuffer[4] == 'S'.toByte() && decryptedBuffer[5] == 'Y'.toByte()) { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (BUSY)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) - mSendQueue.clear() - rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, resourceHelper.gs(R.string.pumpbusy))) - } else { - // ERROR in response, wrong serial number - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (ERROR)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) - mSendQueue.clear() - rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, resourceHelper.gs(R.string.connectionerror))) - sp.remove(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName) - val n = Notification(Notification.WRONGSERIALNUMBER, resourceHelper.gs(R.string.wrongpassword), Notification.URGENT) - rxBus.send(EventNewNotification(n)) - } + BleEncryption.DANAR_PACKET__TYPE_ENCRYPTION_RESPONSE.toByte() -> + when (decryptedBuffer[1]) { + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PUMP_CHECK.toByte() -> + if (decryptedBuffer.size == 4 && decryptedBuffer[2] == 'O'.toByte() && decryptedBuffer[3] == 'K'.toByte()) { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (OK)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + v3Encryption = false + // Grab pairing key from preferences if exists + val pairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName, "") + aapsLogger.debug(LTag.PUMPBTCOMM, "Using stored pairing key: $pairingKey") + if (pairingKey.isNotEmpty()) { + val encodedPairingKey = DanaRS_Packet.hexToBytes(pairingKey) + val bytes = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__CHECK_PASSKEY, encodedPairingKey, null) + aapsLogger.debug(LTag.PUMPBTCOMM, ">>>>> " + "ENCRYPTION__CHECK_PASSKEY" + " " + DanaRS_Packet.toHexString(bytes)) + writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) + } else { + // Stored pairing key does not exists, request pairing + sendPairingRequest() + } + } else if (decryptedBuffer.size == 9 && decryptedBuffer[2] == 'O'.toByte() && decryptedBuffer[3] == 'K'.toByte()) { + // v3 2nd layer encryption + v3Encryption = true + val model = decryptedBuffer[5] + // val protocol = decryptedBuffer[7] + if (model == 0x05.toByte()) { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK V3 (OK)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + // Dana RS Pump + val randomPairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_v3_randompairingkey) + danaRSPlugin.mDeviceName, "") + val pairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_v3_pairingkey) + danaRSPlugin.mDeviceName, "") + if (randomPairingKey.isNotEmpty() && pairingKey.isNotEmpty()) { + val randomSyncKey = String.format("%02x", decryptedBuffer[decryptedBuffer.size - 1]) + sp.putString(resourceHelper.gs(R.string.key_danars_v3_randomsynckey) + danaRSPlugin.mDeviceName, randomSyncKey) + val tPairingKey = Base64.decode(pairingKey, Base64.DEFAULT) + val tRandomPairingKey = Base64.decode(randomPairingKey, Base64.DEFAULT) + var tRandomSyncKey: Byte = 0 + if (randomSyncKey.isNotEmpty()) { + tRandomSyncKey = randomSyncKey.toInt(16).toByte() + } + bleEncryption.setPairingKeys(tPairingKey, tRandomPairingKey, tRandomSyncKey) + sendV3PairingInformation(0) + } else { + sendV3PairingInformation(1) + } + } else if (model == 0x06.toByte()) { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK V3 EASY (OK)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + // Dana RS Easy + val bytes: ByteArray = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__GET_EASYMENU_CHECK, null, null) + writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) + } + } else if (decryptedBuffer.size == 6 && decryptedBuffer[2] == 'P'.toByte() && decryptedBuffer[3] == 'U'.toByte() && decryptedBuffer[4] == 'M'.toByte() && decryptedBuffer[5] == 'P'.toByte()) { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (PUMP)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + mSendQueue.clear() + rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, resourceHelper.gs(R.string.pumperror))) + NSUpload.uploadError(resourceHelper.gs(R.string.pumperror)) + val n = Notification(Notification.PUMPERROR, resourceHelper.gs(R.string.pumperror), Notification.URGENT) + rxBus.send(EventNewNotification(n)) + } else if (decryptedBuffer.size == 6 && decryptedBuffer[2] == 'B'.toByte() && decryptedBuffer[3] == 'U'.toByte() && decryptedBuffer[4] == 'S'.toByte() && decryptedBuffer[5] == 'Y'.toByte()) { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (BUSY)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + mSendQueue.clear() + rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, resourceHelper.gs(R.string.pumpbusy))) + } else { + // ERROR in response, wrong serial number + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PUMP_CHECK (ERROR)" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + mSendQueue.clear() + rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, resourceHelper.gs(R.string.connectionerror))) + sp.remove(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName) + val n = Notification(Notification.WRONGSERIALNUMBER, resourceHelper.gs(R.string.wrongpassword), Notification.URGENT) + rxBus.send(EventNewNotification(n)) + } - BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__CHECK_PASSKEY.toByte() -> { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__CHECK_PASSKEY" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) - if (decryptedBuffer[2] == 0x00.toByte()) { - // Paring is not requested, sending time info + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__TIME_INFORMATION.toByte() -> { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__TIME_INFORMATION " + /*message.getMessageName() + " " + */DanaRS_Packet.toHexString(decryptedBuffer)) + if (v3Encryption) { + // decryptedBuffer[2] : 0x00 OK 0x01 Error, No pairing + if (decryptedBuffer[2] == 0x00.toByte()) { + val randomPairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_v3_randompairingkey) + danaRSPlugin.mDeviceName, "") + val pairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_v3_pairingkey) + danaRSPlugin.mDeviceName, "") + if (randomPairingKey.isNotEmpty() && pairingKey.isNotEmpty()) { + // expecting successful connect + isConnected = true + isConnecting = false + aapsLogger.debug(LTag.PUMPBTCOMM, "Connect !!") + } else { + context.startActivity(Intent(context, EnterPinActivity::class.java).also { it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }) + aapsLogger.debug(LTag.PUMPBTCOMM, "Request pairing keys !!") + } + } else { + sendV3PairingInformation(1) + } + + } else { + val size = decryptedBuffer.size + var pass: Int = (decryptedBuffer[size - 1].toInt() and 0x000000FF shl 8) + (decryptedBuffer[size - 2].toInt() and 0x000000FF) + pass = pass xor 3463 + danaRPump.rsPassword = Integer.toHexString(pass) + aapsLogger.debug(LTag.PUMPBTCOMM, "Pump user password: " + Integer.toHexString(pass)) + rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED)) + isConnected = true + isConnecting = false + aapsLogger.debug(LTag.PUMPBTCOMM, "RS connected and status read") + } + } + + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__CHECK_PASSKEY.toByte() -> { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__CHECK_PASSKEY" + " " + DanaRS_Packet.toHexString(decryptedBuffer)) + if (decryptedBuffer[2] == 0x00.toByte()) { + // Paring is not requested, sending time info + sendTimeInfo() + } else { + // Pairing on pump is requested + sendPairingRequest() + } + } + + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PASSKEY_REQUEST.toByte() -> { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PASSKEY_REQUEST " + DanaRS_Packet.toHexString(decryptedBuffer)) + if (decryptedBuffer[2] != 0x00.toByte()) { + disconnect("passkey request failed") + } + } + + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PASSKEY_RETURN.toByte() -> { + aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PASSKEY_RETURN " + DanaRS_Packet.toHexString(decryptedBuffer)) + // Paring is successful, sending time info + rxBus.send(EventDanaRSPairingSuccess()) sendTimeInfo() - } else { - // Pairing on pump is requested - sendPairingRequest() + val pairingKey = byteArrayOf(decryptedBuffer[2], decryptedBuffer[3]) + // store pairing key to preferences + sp.putString(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName, DanaRS_Packet.bytesToHex(pairingKey)) + aapsLogger.debug(LTag.PUMPBTCOMM, "Got pairing key: " + DanaRS_Packet.bytesToHex(pairingKey)) + } + + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__GET_PUMP_CHECK.toByte() -> { + if (decryptedBuffer[2] == 0x05.toByte()) { + // not easy mode, request time info + val bytes: ByteArray = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__TIME_INFORMATION, null, null) + writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) + } else { + // easy mode + val bytes: ByteArray = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__GET_EASYMENU_CHECK, null, null) + writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) + } + } + + BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__GET_EASYMENU_CHECK.toByte() -> { + isEasyMode = decryptedBuffer[2] == 0x01.toByte() + isUnitUD = decryptedBuffer[3] == 0x01.toByte() + + // request time information + if (v3Encryption) { + sendV3PairingInformation(1) + } else { + val bytes: ByteArray = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__TIME_INFORMATION, null, null) + writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) + } } } - BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PASSKEY_REQUEST.toByte() -> { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PASSKEY_REQUEST " + DanaRS_Packet.toHexString(decryptedBuffer)) - if (decryptedBuffer[2] != 0x00.toByte()) { - disconnect("passkey request failed") - } - } - - BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PASSKEY_RETURN.toByte() -> { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__PASSKEY_RETURN " + DanaRS_Packet.toHexString(decryptedBuffer)) - // Paring is successful, sending time info - rxBus.send(EventDanaRSPairingSuccess()) - sendTimeInfo() - val pairingKey = byteArrayOf(decryptedBuffer[2], decryptedBuffer[3]) - // store pairing key to preferences - sp.putString(resourceHelper.gs(R.string.key_danars_pairingkey) + danaRSPlugin.mDeviceName, DanaRS_Packet.bytesToHex(pairingKey)) - aapsLogger.debug(LTag.PUMPBTCOMM, "Got pairing key: " + DanaRS_Packet.bytesToHex(pairingKey)) - } - - BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__TIME_INFORMATION.toByte() -> { - aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< " + "ENCRYPTION__TIME_INFORMATION " + /*message.getMessageName() + " " + */DanaRS_Packet.toHexString(decryptedBuffer)) - val size = decryptedBuffer.size - var pass: Int = (decryptedBuffer[size - 1].toInt() and 0x000000FF shl 8) + (decryptedBuffer[size - 2].toInt() and 0x000000FF) - pass = pass xor 3463 - danaRPump.rsPassword = Integer.toHexString(pass) - aapsLogger.debug(LTag.PUMPBTCOMM, "Pump user password: " + Integer.toHexString(pass)) - rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED)) - isConnected = true - isConnecting = false - aapsLogger.debug(LTag.PUMPBTCOMM, "RS connected and status read") - } - } - else -> { // Retrieve message code from received buffer and last message sent val originalCommand = processedMessage?.command ?: 0xFFFF @@ -449,6 +562,8 @@ class BLEComm @Inject internal constructor( val params = message.requestParams aapsLogger.debug(LTag.PUMPBTCOMM, ">>>>> " + message.friendlyName + " " + DanaRS_Packet.toHexString(command) + " " + DanaRS_Packet.toHexString(params)) var bytes = bleEncryption.getEncryptedPacket(message.opCode, params, null) + if (v3Encryption) + bytes = bleEncryption.encryptSecondLevelPacket(bytes) // If there is another message not completely sent, add to queue only if (mSendQueue.size > 0) { // Split to parts per 20 bytes max @@ -509,13 +624,30 @@ class BLEComm @Inject internal constructor( } } + fun finishV3Pairing() { + val randomPairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_v3_randompairingkey) + danaRSPlugin.mDeviceName, "") + val pairingKey = sp.getString(resourceHelper.gs(R.string.key_danars_v3_pairingkey) + danaRSPlugin.mDeviceName, "") + if (randomPairingKey.isNotEmpty() && pairingKey.isNotEmpty()) { + val tPairingKey = Base64.decode(pairingKey, Base64.DEFAULT) + val tRandomPairingKey = Base64.decode(randomPairingKey, Base64.DEFAULT) + val tRandomSyncKey: Byte = 0 + bleEncryption.setPairingKeys(tPairingKey, tRandomPairingKey, tRandomSyncKey) + sendV3PairingInformation(0) + } + } + + // 0x00 Start encryption, 0x01 Request pairing + private fun sendV3PairingInformation(requestNewPairing: Int) { + val params = byteArrayOf(requestNewPairing.toByte()) + val bytes: ByteArray = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__TIME_INFORMATION, params, null) + aapsLogger.debug(LTag.PUMPBTCOMM, ">>>>> " + "ENCRYPTION__TIME_INFORMATION" + " " + DanaRS_Packet.toHexString(bytes)) + writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) + } + private fun sendPairingRequest() { // Start activity which is waiting 20sec // On pump pairing request is displayed and is waiting for conformation - val i = Intent() - i.setClass(context, PairingHelperActivity::class.java) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(i) + context.startActivity(Intent(context, PairingHelperActivity::class.java).also { it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }) val bytes = bleEncryption.getEncryptedPacket(BleEncryption.DANAR_PACKET__OPCODE_ENCRYPTION__PASSKEY_REQUEST, null, null) aapsLogger.debug(LTag.PUMPBTCOMM, ">>>>> " + "ENCRYPTION__PASSKEY_REQUEST" + " " + DanaRS_Packet.toHexString(bytes)) writeCharacteristicNoResponse(uartWriteBTGattChar, bytes) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/extensions/HexByteArrayConversion.kt b/app/src/main/java/info/nightscout/androidaps/utils/extensions/HexByteArrayConversion.kt new file mode 100644 index 0000000000..9852ecbbf4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/extensions/HexByteArrayConversion.kt @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.utils.extensions + +private val HEX_CHARS = "0123456789ABCDEF".toCharArray() + +fun ByteArray.toHex() : String{ + val result = StringBuffer() + + forEach { + val octet = it.toInt() + val firstIndex = (octet and 0xF0).ushr(4) + val secondIndex = octet and 0x0F + result.append(HEX_CHARS[firstIndex]) + result.append(HEX_CHARS[secondIndex]) + } + + return result.toString() +} + +fun String.hexStringToByteArray() : ByteArray { + + val result = ByteArray(length / 2) + + for (i in 0 until length step 2) { + val firstIndex = HEX_CHARS.indexOf(this[i]); + val secondIndex = HEX_CHARS.indexOf(this[i + 1]); + + val octet = firstIndex.shl(4).or(secondIndex) + result.set(i.shr(1), octet.toByte()) + } + + return result +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/textValidator/DefaultEditTextValidator.kt b/app/src/main/java/info/nightscout/androidaps/utils/textValidator/DefaultEditTextValidator.kt index 4c15100edd..21146a9a72 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/textValidator/DefaultEditTextValidator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/textValidator/DefaultEditTextValidator.kt @@ -15,6 +15,9 @@ class DefaultEditTextValidator : EditTextValidator { protected var testErrorString: String? = null protected var emptyAllowed = false protected lateinit var editTextView: EditText + private var tw: TextWatcher? = null + private var defaultEmptyErrorString: String? = null + protected var testType: Int protected var classType: String? = null protected var customRegexp: String? = null @@ -26,8 +29,6 @@ class DefaultEditTextValidator : EditTextValidator { protected var maxNumber = 0 protected var floatminNumber = 0f protected var floatmaxNumber = 0f - private var tw: TextWatcher? = null - private var defaultEmptyErrorString: String? = null @Suppress("unused") constructor(editTextView: EditText, context: Context) { @@ -167,44 +168,50 @@ class DefaultEditTextValidator : EditTextValidator { } @Suppress("unused") - fun setClassType(classType: String?, testErrorString: String?, context: Context) { + fun setClassType(classType: String?, testErrorString: String?, context: Context) : DefaultEditTextValidator{ testType = EditTextValidator.TEST_CUSTOM this.classType = classType this.testErrorString = testErrorString resetValidators(context) + return this } @Suppress("unused") - fun setCustomRegexp(customRegexp: String?, context: Context) { + fun setCustomRegexp(customRegexp: String?, context: Context) : DefaultEditTextValidator { testType = EditTextValidator.TEST_REGEXP this.customRegexp = customRegexp resetValidators(context) + return this } @Suppress("unused") - fun setEmptyAllowed(emptyAllowed: Boolean, context: Context) { + fun setEmptyAllowed(emptyAllowed: Boolean, context: Context) : DefaultEditTextValidator { this.emptyAllowed = emptyAllowed resetValidators(context) + return this } - fun setEmptyErrorString(emptyErrorString: String?) { + fun setEmptyErrorString(emptyErrorString: String?) : DefaultEditTextValidator { emptyErrorStringActual = if (!TextUtils.isEmpty(emptyErrorString)) { emptyErrorString } else { defaultEmptyErrorString } + return this } @Suppress("unused") - fun setTestErrorString(testErrorString: String?, context: Context) { + fun setTestErrorString(testErrorString: String?, context: Context) : DefaultEditTextValidator { this.testErrorString = testErrorString resetValidators(context) + return this } @Suppress("unused") - fun setTestType(testType: Int, context: Context) { + fun setTestType(testType: Int, context: Context) : DefaultEditTextValidator { this.testType = testType resetValidators(context) + return this } override fun testValidity(): Boolean { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/textValidator/ValidatingEditTextPreference.kt b/app/src/main/java/info/nightscout/androidaps/utils/textValidator/ValidatingEditTextPreference.kt index 190a3bea73..0d605c17e8 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/textValidator/ValidatingEditTextPreference.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/textValidator/ValidatingEditTextPreference.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.utils.textValidator +import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import androidx.core.content.res.TypedArrayUtils @@ -22,7 +23,7 @@ class ValidatingEditTextPreference(ctx: Context, attrs: AttributeSet, defStyleAt constructor(ctx: Context, attrs: AttributeSet, defStyle: Int) : this(ctx, attrs, defStyle, 0) - constructor(ctx: Context, attrs: AttributeSet) + @SuppressLint("RestrictedApi") constructor(ctx: Context, attrs: AttributeSet) : this(ctx, attrs, TypedArrayUtils.getAttr(ctx, R.attr.editTextPreferenceStyle, R.attr.editTextPreferenceStyle)) diff --git a/app/src/main/res/layout/danars_enter_pin_activity.xml b/app/src/main/res/layout/danars_enter_pin_activity.xml new file mode 100644 index 0000000000..0a232b6cc1 --- /dev/null +++ b/app/src/main/res/layout/danars_enter_pin_activity.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c6bcfbe4d..3ac901f44f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -417,6 +417,7 @@ Open Settings on Wear Pump Error Low Battery + Delivering less than preset basal rate Pump Shutdown Pump Battery Discharged DanaR Korean @@ -731,12 +732,16 @@ Pairing timed out Pairing danars_pairing_key_ + danars_v3_randompairing_key_ + danars_v3_pairing_key_ + danars_v3_randomsync_key_ danars_address danars_name No device found so far Empty reservoir Blood sugar measurement alert Remaining insulin level + Missed bolus DanaRS Dana Selected pump @@ -1736,4 +1741,11 @@ On each follower phone install Authenticator app that support RFC 6238 TOTP tokens. Popular free apps are:\n • Authy\n • Google Authenticator\n • LastPass Authenticator\n • FreeOTP Authenticator DO NOT SHARE this code online!\nUse it only to setup Authenticator App on follower phones. By reseting authenticator you make all already provisioned authenticators invalid. You will need to set up them again! + PIN1 + PIN2 + Press OK on the pump\nand enter 2 displayed numbers\nKeep display on pump ON by pressing minus button until you finish entering code. + 1: (12 digits) + 2: (8 digits) + Reset pairing information? + Invalid pairing information. Requesting new pairing diff --git a/app/src/main/res/values/validator.xml b/app/src/main/res/values/validator.xml index 15c40b743c..aef7116a48 100644 --- a/app/src/main/res/values/validator.xml +++ b/app/src/main/res/values/validator.xml @@ -16,9 +16,13 @@ Format not valid Must be 4 digit number Must be 6 digit number + Must be 12 characters of ABCDEF0123456789 + Must be 8 characters of ABCDEF0123456789 Not a minimum length Pin should be 3 to 6 digits, not same or in series ^\\d{4} + ^[A-F0-9]{12}$ + ^[A-F0-9]{8}$ \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_AlarmTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_AlarmTest.kt index 8aba3da2ba..58e0b738db 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_AlarmTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_AlarmTest.kt @@ -14,7 +14,7 @@ import org.powermock.modules.junit4.PowerMockRunner class DanaRS_Packet_Notify_AlarmTest : DanaRSTestBase() { @Test fun runTest() { - val packet = DanaRS_Packet_Notify_Alarm(aapsLogger, resourceHelper) + val packet = DanaRS_Packet_Notify_Alarm(aapsLogger, resourceHelper, rxBus) // test params Assert.assertEquals(null, packet.requestParams) // test message decoding