From 86d965a7f135e1bd8458605dc2538f52cc8e0574 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sat, 27 Feb 2021 23:50:51 +0100 Subject: [PATCH 01/14] initial history database --- omnipod-dash/build.gradle | 7 +++ .../dash/dagger/OmnipodDashHistoryModule.kt | 33 +++++++++++ .../omnipod/dash/dagger/OmnipodDashModule.kt | 2 +- .../pump/omnipod/dash/history/DashHistory.kt | 55 +++++++++++++++++++ .../dash/history/data/HistoryRecord.kt | 13 +++++ .../pump/omnipod/dash/history/data/Record.kt | 11 ++++ .../omnipod/dash/history/data/ResultStates.kt | 9 +++ .../dash/history/database/Converters.kt | 34 ++++++++++++ .../history/database/DashHistoryDatabase.kt | 31 +++++++++++ .../dash/history/database/HistoryRecordDao.kt | 36 ++++++++++++ .../history/database/HistoryRecordEntity.kt | 22 ++++++++ .../dash/history/mapper/HistoryMapper.kt | 32 +++++++++++ 12 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashHistoryModule.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/Record.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/ResultStates.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt diff --git a/omnipod-dash/build.gradle b/omnipod-dash/build.gradle index ea8c60af66..45c11a93bf 100644 --- a/omnipod-dash/build.gradle +++ b/omnipod-dash/build.gradle @@ -17,4 +17,11 @@ android { dependencies { implementation project(':core') implementation project(':omnipod-common') + + implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-rxjava2:$room_version" + kapt "androidx.room:room-compiler:$room_version" + + implementation 'com.github.guepardoapps:kulid:1.1.2.0' + } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashHistoryModule.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashHistoryModule.kt new file mode 100644 index 0000000000..172dd497b0 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashHistoryModule.kt @@ -0,0 +1,33 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.dagger + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.Reusable +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.DashHistory +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper +import javax.inject.Singleton + +@Module +class OmnipodDashHistoryModule { + + @Provides + @Singleton + internal fun provideDatabase(context: Context): DashHistoryDatabase = DashHistoryDatabase.build(context) + + @Provides + @Singleton + internal fun provideHistoryRecordDao(dashHistoryDatabase: DashHistoryDatabase): HistoryRecordDao = dashHistoryDatabase.historyRecordDao() + + @Provides + @Reusable // no state, let system decide when to reuse or create new. + internal fun provideHistoryMapper() = HistoryMapper() + + @Provides + @Singleton + internal fun provideDashHistory(dao: HistoryRecordDao, historyMapper: HistoryMapper) = + DashHistory(dao, historyMapper) + +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt index a67a8dd570..98780b10d9 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt @@ -16,7 +16,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.OmnipodDashOvervi import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.DashPodActivationWizardActivity import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.deactivation.DashPodDeactivationWizardActivity -@Module +@Module(includes = [OmnipodDashHistoryModule::class]) @Suppress("unused") abstract class OmnipodDashModule { // ACTIVITIES diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt new file mode 100644 index 0000000000..98319550ed --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt @@ -0,0 +1,55 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history + +import com.github.guepardoapps.kulid.ULID +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.HistoryRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBasalRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordEntity +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper +import io.reactivex.Completable +import io.reactivex.Single +import java.lang.System.currentTimeMillis +import javax.inject.Inject + +class DashHistory @Inject constructor( + private val dao: HistoryRecordDao, + private val historyMapper: HistoryMapper +) { + + fun markSuccess(id: String): Completable = dao.markResolved(id, ResolvedResult.SUCCESS, currentTimeMillis()) + + fun markFailure(id: String): Completable = dao.markResolved(id, ResolvedResult.FAILURE, currentTimeMillis()) + + fun createRecord( + commandType: OmnipodCommandType, + initialResult: InitialResult = InitialResult.UNCONFIRMED, + tempBasalRecord: TempBasalRecord? = null, + bolusRecord: BolusRecord? = null, + resolveResult: ResolvedResult? = null, + resolvedAt: Long? = null + ): Single { + val id = ULID.random() + return dao.save( + HistoryRecordEntity( + id = id, + createdAt = currentTimeMillis(), + commandType = commandType, + tempBasalRecord = tempBasalRecord, + bolusRecord = bolusRecord, + initialResult = initialResult, + resolvedResult = resolveResult, + resolvedAt = resolvedAt, + ) + ).toSingle { id } + } + + fun getRecords(): Single> = + dao.all().map { list -> list.map(historyMapper::entityToDomain) } + + fun getRecordsAfter(time: Long): Single> = dao.allSince(time) + +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt new file mode 100644 index 0000000000..5229248a7e --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data + +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType + +data class HistoryRecord( + val id: String, // ULID + val createdAt: Long, + val commandType: OmnipodCommandType, + val initialResult: InitialResult, + val record: Record?, + val resolvedResult: ResolvedResult?, + val resolvedAt: Long? +) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/Record.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/Record.kt new file mode 100644 index 0000000000..6344c9fcd7 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/Record.kt @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data + +sealed class Record + +data class BolusRecord(val amout: Double, val bolusType: BolusType): Record() + +data class TempBasalRecord(val duration: Long, val rate: Double): Record() + +enum class BolusType { + DEFAULT, SMB +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/ResultStates.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/ResultStates.kt new file mode 100644 index 0000000000..53dc608e60 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/ResultStates.kt @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data + +enum class InitialResult { + SUCCESS, FAILURE, UNCONFIRMED +} + +enum class ResolvedResult { + SUCCESS, FAILURE +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt new file mode 100644 index 0000000000..b37d71bf3f --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database + +import androidx.room.TypeConverter +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusType +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult + +class Converters { + + @TypeConverter + fun toBolusType(s: String) = enumValueOf(s) + + @TypeConverter + fun fromBolusType(bolusType: BolusType) = bolusType.name + + @TypeConverter + fun toInitialResult(s: String) = enumValueOf(s) + + @TypeConverter + fun fromInitialResult(initialResult: InitialResult) = initialResult.name + + @TypeConverter + fun toResolvedResult(s: String) = enumValueOf(s) + + @TypeConverter + fun fromResolvedResult(resolvedResult: ResolvedResult) = resolvedResult.name + + @TypeConverter + fun toOmnipodCommandType(s: String) = enumValueOf(s) + + @TypeConverter + fun fromOmnipodCommandType(omnipodCommandType: OmnipodCommandType) = omnipodCommandType.name +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt new file mode 100644 index 0000000000..259447afb0 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +@Database( + entities = [HistoryRecordEntity::class], + exportSchema = false, + version = DashHistoryDatabase.VERSION +) +@TypeConverters(Converters::class) +abstract class DashHistoryDatabase : RoomDatabase() { + + abstract fun historyRecordDao() : HistoryRecordDao + + companion object { + + const val VERSION = 1 + + @Synchronized + fun build(context: Context) = + Room.databaseBuilder(context.applicationContext, DashHistoryDatabase::class.java, "omnipod_dash_history_database.db") + .fallbackToDestructiveMigration() + .build() + + } + +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt new file mode 100644 index 0000000000..e5c425dd24 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordDao.kt @@ -0,0 +1,36 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult +import io.reactivex.Completable +import io.reactivex.Single + +@Dao +abstract class HistoryRecordDao { + + @Query("SELECT * from historyrecords") + abstract fun all(): Single> + + @Query("SELECT * from historyrecords") + abstract fun allBlocking(): List + + @Query("SELECT * from historyrecords WHERE createdAt <= :since") + abstract fun allSince(since: Long): Single> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun saveBlocking(historyRecordEntity: HistoryRecordEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun save(historyRecordEntity: HistoryRecordEntity): Completable + + @Delete + abstract fun delete(historyRecordEntity: HistoryRecordEntity): Completable + + @Query("UPDATE historyrecords SET resolvedResult = :resolvedResult, resolvedAt = :resolvedAt WHERE id = :id ") + abstract fun markResolved(id: String, resolvedResult: ResolvedResult, resolvedAt: Long): Completable + +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt new file mode 100644 index 0000000000..6539009117 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.PrimaryKey +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBasalRecord + +@Entity(tableName = "historyrecords") +data class HistoryRecordEntity( + @PrimaryKey val id: String, // ULID + val createdAt: Long, + val commandType: OmnipodCommandType, + val initialResult: InitialResult, + @Embedded(prefix = "tempBasalRecord_") val tempBasalRecord: TempBasalRecord?, + @Embedded(prefix = "bolusRecord_") val bolusRecord: BolusRecord?, + val resolvedResult: ResolvedResult?, + val resolvedAt: Long?) + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt new file mode 100644 index 0000000000..6ceeb6920c --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.HistoryRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBasalRecord +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordEntity + +class HistoryMapper { + + fun domainToEntity(historyRecord: HistoryRecord): HistoryRecordEntity = + HistoryRecordEntity( + id = historyRecord.id, + createdAt = historyRecord.createdAt, + commandType = historyRecord.commandType, + initialResult = historyRecord.initialResult, + tempBasalRecord = historyRecord.record as? TempBasalRecord, + bolusRecord = historyRecord.record as? BolusRecord, + resolvedResult = historyRecord.resolvedResult, + resolvedAt = historyRecord.resolvedAt + ) + + fun entityToDomain(entity: HistoryRecordEntity): HistoryRecord = + HistoryRecord(id = entity.id, + createdAt = entity.createdAt, + initialResult = entity.initialResult, + commandType = entity.commandType, + record = entity.bolusRecord ?: entity.tempBasalRecord, + resolvedResult = entity.resolvedResult, + resolvedAt = entity.resolvedAt + ) + +} \ No newline at end of file From 2d0d38f70f20aa85ade4d914820d6ccd109a69d2 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sun, 28 Feb 2021 03:30:13 +0100 Subject: [PATCH 02/14] database integration test working --- gradle/test_dependencies.gradle | 13 ++++- .../omnipod/dash/history/DashHistoryTest.kt | 58 +++++++++++++++++++ .../omnipod/dash/history/RxSchedulerRule.kt | 32 ++++++++++ .../pump/omnipod/dash/history/DashHistory.kt | 10 +++- .../dash/history/data/HistoryRecord.kt | 1 + .../dash/history/database/Converters.kt | 4 +- .../history/database/DashHistoryDatabase.kt | 3 +- 7 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt create mode 100644 omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/RxSchedulerRule.kt diff --git a/gradle/test_dependencies.gradle b/gradle/test_dependencies.gradle index 3a484c419c..881e535d9d 100644 --- a/gradle/test_dependencies.gradle +++ b/gradle/test_dependencies.gradle @@ -13,9 +13,16 @@ dependencies { testImplementation "org.skyscreamer:jsonassert:1.5.0" testImplementation "org.hamcrest:hamcrest-all:1.3" - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04' - androidTestImplementation "androidx.test.ext:junit:$androidx_junit" - androidTestImplementation "androidx.test:rules:$androidx_rules" + + // newer integration test libraries might not work + // https://stackoverflow.com/questions/64700104/attribute-androidforcequeryable-not-found-in-android-studio-when-running-espres + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0' + + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:rules:1.3.0' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' } diff --git a/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt b/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt new file mode 100644 index 0000000000..28467f7a59 --- /dev/null +++ b/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.guepardoapps.kulid.ULID +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao +import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper +import io.reactivex.schedulers.Schedulers +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class DashHistoryTest { + + private lateinit var dao: HistoryRecordDao + private lateinit var database: DashHistoryDatabase + private lateinit var dashHistory: DashHistory + + @get:Rule + val schedulerRule = RxSchedulerRule(Schedulers.trampoline()) + + @Before + fun setUp() { + val context = ApplicationProvider.getApplicationContext() + database = Room.inMemoryDatabaseBuilder( + context, DashHistoryDatabase::class.java).build() + dao = database.historyRecordDao() + dashHistory = DashHistory(dao, HistoryMapper()) + } + + @Test + fun testInsertSomething() { // needs to be camel case as runs on Android + dashHistory.getRecords().test().apply { + assertValue { it.isEmpty() } + } + + dashHistory.createRecord(commandType = OmnipodCommandType.SET_BOLUS).test().apply { + assertValue { ULID.isValid(it) } + } + + dashHistory.getRecords().test().apply { + assertValue { it.size == 1 } + } + + } + + @After + fun tearDown() { + database.close() + } +} \ No newline at end of file diff --git a/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/RxSchedulerRule.kt b/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/RxSchedulerRule.kt new file mode 100644 index 0000000000..6073957e5f --- /dev/null +++ b/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/RxSchedulerRule.kt @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.history + +import io.reactivex.Scheduler +import io.reactivex.android.plugins.RxAndroidPlugins +import io.reactivex.plugins.RxJavaPlugins +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +// TODO: move to core before the big merge +class RxSchedulerRule(val scheduler: Scheduler) : TestRule { + + override fun apply(base: Statement, description: Description) = + object : Statement() { + override fun evaluate() { + RxAndroidPlugins.reset() + RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler } + RxJavaPlugins.reset() + RxJavaPlugins.setIoSchedulerHandler { scheduler } + RxJavaPlugins.setNewThreadSchedulerHandler { scheduler } + RxJavaPlugins.setComputationSchedulerHandler { scheduler } + + try { + base.evaluate() + } finally { + RxJavaPlugins.reset() + RxAndroidPlugins.reset() + } + + } + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt index 98319550ed..e2ebf8f94d 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt @@ -20,9 +20,9 @@ class DashHistory @Inject constructor( private val historyMapper: HistoryMapper ) { - fun markSuccess(id: String): Completable = dao.markResolved(id, ResolvedResult.SUCCESS, currentTimeMillis()) + fun markSuccess(id: String): Completable = dao.markResolved(id, ResolvedResult.SUCCESS, currentTimeMillis()) // TODO pass time - fun markFailure(id: String): Completable = dao.markResolved(id, ResolvedResult.FAILURE, currentTimeMillis()) + fun markFailure(id: String): Completable = dao.markResolved(id, ResolvedResult.FAILURE, currentTimeMillis()) // TODO pass time fun createRecord( commandType: OmnipodCommandType, @@ -33,10 +33,14 @@ class DashHistory @Inject constructor( resolvedAt: Long? = null ): Single { val id = ULID.random() + + // TODO: verify that on OmnipodCommandType.SET_BOLUS bolusRecord is not null? + // TODO: verify that on SET_TEMPORARY_BASAL tempBasalRecord is not null + return dao.save( HistoryRecordEntity( id = id, - createdAt = currentTimeMillis(), + createdAt = currentTimeMillis(), // TODO pass time (as date, keep createdAt) commandType = commandType, tempBasalRecord = tempBasalRecord, bolusRecord = bolusRecord, diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt index 5229248a7e..3993d8de73 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt @@ -4,6 +4,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.Omnipod data class HistoryRecord( val id: String, // ULID + // TODO add date val createdAt: Long, val commandType: OmnipodCommandType, val initialResult: InitialResult, diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt index b37d71bf3f..2395695f1e 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/Converters.kt @@ -21,10 +21,10 @@ class Converters { fun fromInitialResult(initialResult: InitialResult) = initialResult.name @TypeConverter - fun toResolvedResult(s: String) = enumValueOf(s) + fun toResolvedResult(s: String?) = s?.let { enumValueOf(it) } @TypeConverter - fun fromResolvedResult(resolvedResult: ResolvedResult) = resolvedResult.name + fun fromResolvedResult(resolvedResult: ResolvedResult?) = resolvedResult?.name @TypeConverter fun toOmnipodCommandType(s: String) = enumValueOf(s) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt index 259447afb0..75b21716ba 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/DashHistoryDatabase.kt @@ -5,6 +5,7 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration @Database( entities = [HistoryRecordEntity::class], @@ -20,12 +21,10 @@ abstract class DashHistoryDatabase : RoomDatabase() { const val VERSION = 1 - @Synchronized fun build(context: Context) = Room.databaseBuilder(context.applicationContext, DashHistoryDatabase::class.java, "omnipod_dash_history_database.db") .fallbackToDestructiveMigration() .build() - } } \ No newline at end of file From 6dce871ed459329545e86662eb0a0507fbc56568 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sun, 28 Feb 2021 03:55:01 +0100 Subject: [PATCH 03/14] fix database --- .../omnipod/dash/history/DashHistoryTest.kt | 18 +++++++++++++++-- .../pump/omnipod/dash/history/DashHistory.kt | 20 ++++++++++++++----- .../dash/history/data/HistoryRecord.kt | 4 ++-- .../history/database/HistoryRecordEntity.kt | 3 ++- .../dash/history/mapper/HistoryMapper.kt | 4 +++- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt b/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt index 28467f7a59..9edd38f85e 100644 --- a/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt +++ b/omnipod-dash/src/androidTest/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistoryTest.kt @@ -36,19 +36,33 @@ class DashHistoryTest { } @Test - fun testInsertSomething() { // needs to be camel case as runs on Android + fun testInsertionAndConverters() { dashHistory.getRecords().test().apply { assertValue { it.isEmpty() } } - dashHistory.createRecord(commandType = OmnipodCommandType.SET_BOLUS).test().apply { + dashHistory.createRecord(commandType = OmnipodCommandType.CANCEL_BOLUS, 0L).test().apply { assertValue { ULID.isValid(it) } } dashHistory.getRecords().test().apply { assertValue { it.size == 1 } } + } + @Test + fun testExceptionOnBolusWithoutRecord() { + dashHistory.getRecords().test().apply { + assertValue { it.isEmpty() } + } + + dashHistory.createRecord(commandType = OmnipodCommandType.SET_BOLUS, 0L).test().apply { + assertError(IllegalArgumentException::class.java) + } + + dashHistory.getRecords().test().apply { + assertValue { it.isEmpty() } + } } @After diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt index e2ebf8f94d..d02d1141f9 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt @@ -2,6 +2,8 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.history import com.github.guepardoapps.kulid.ULID import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_BOLUS +import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_TEMPORARY_BASAL import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.HistoryRecord import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult @@ -20,12 +22,13 @@ class DashHistory @Inject constructor( private val historyMapper: HistoryMapper ) { - fun markSuccess(id: String): Completable = dao.markResolved(id, ResolvedResult.SUCCESS, currentTimeMillis()) // TODO pass time + fun markSuccess(id: String, date: Long): Completable = dao.markResolved(id, ResolvedResult.SUCCESS, currentTimeMillis()) - fun markFailure(id: String): Completable = dao.markResolved(id, ResolvedResult.FAILURE, currentTimeMillis()) // TODO pass time + fun markFailure(id: String, date: Long): Completable = dao.markResolved(id, ResolvedResult.FAILURE, currentTimeMillis()) fun createRecord( commandType: OmnipodCommandType, + date: Long, initialResult: InitialResult = InitialResult.UNCONFIRMED, tempBasalRecord: TempBasalRecord? = null, bolusRecord: BolusRecord? = null, @@ -34,13 +37,20 @@ class DashHistory @Inject constructor( ): Single { val id = ULID.random() - // TODO: verify that on OmnipodCommandType.SET_BOLUS bolusRecord is not null? - // TODO: verify that on SET_TEMPORARY_BASAL tempBasalRecord is not null + when { + commandType == SET_BOLUS && bolusRecord == null -> + Single.error(IllegalArgumentException("bolusRecord missing on SET_BOLUS")) + commandType == SET_TEMPORARY_BASAL && tempBasalRecord == null -> + Single.error(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL")) + else -> null + }?.let { return it } + return dao.save( HistoryRecordEntity( id = id, - createdAt = currentTimeMillis(), // TODO pass time (as date, keep createdAt) + date = date, + createdAt = currentTimeMillis(), commandType = commandType, tempBasalRecord = tempBasalRecord, bolusRecord = bolusRecord, diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt index 3993d8de73..56aa2dc6b2 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/data/HistoryRecord.kt @@ -4,8 +4,8 @@ import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.Omnipod data class HistoryRecord( val id: String, // ULID - // TODO add date - val createdAt: Long, + val createdAt: Long, // creation date of the record + val date: Long, // when event actually happened val commandType: OmnipodCommandType, val initialResult: InitialResult, val record: Record?, diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt index 6539009117..4eea309c5a 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/database/HistoryRecordEntity.kt @@ -12,7 +12,8 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBas @Entity(tableName = "historyrecords") data class HistoryRecordEntity( @PrimaryKey val id: String, // ULID - val createdAt: Long, + val createdAt: Long, // creation date of the record + val date: Long, // when event actually happened val commandType: OmnipodCommandType, val initialResult: InitialResult, @Embedded(prefix = "tempBasalRecord_") val tempBasalRecord: TempBasalRecord?, diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt index 6ceeb6920c..e3eeb39320 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/mapper/HistoryMapper.kt @@ -11,17 +11,19 @@ class HistoryMapper { HistoryRecordEntity( id = historyRecord.id, createdAt = historyRecord.createdAt, + date = historyRecord.date, commandType = historyRecord.commandType, initialResult = historyRecord.initialResult, tempBasalRecord = historyRecord.record as? TempBasalRecord, bolusRecord = historyRecord.record as? BolusRecord, resolvedResult = historyRecord.resolvedResult, - resolvedAt = historyRecord.resolvedAt + resolvedAt = historyRecord.resolvedAt, ) fun entityToDomain(entity: HistoryRecordEntity): HistoryRecord = HistoryRecord(id = entity.id, createdAt = entity.createdAt, + date = entity.date, initialResult = entity.initialResult, commandType = entity.commandType, record = entity.bolusRecord ?: entity.tempBasalRecord, From 76756fabaa4ada64a0ae687562e0418eb53f452e Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sun, 28 Feb 2021 14:18:17 +0100 Subject: [PATCH 04/14] a bit more readable --- .../pump/omnipod/dash/history/DashHistory.kt | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt index d02d1141f9..b5c1c5367d 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/history/DashHistory.kt @@ -39,25 +39,21 @@ class DashHistory @Inject constructor( when { commandType == SET_BOLUS && bolusRecord == null -> - Single.error(IllegalArgumentException("bolusRecord missing on SET_BOLUS")) + return Single.error(IllegalArgumentException("bolusRecord missing on SET_BOLUS")) commandType == SET_TEMPORARY_BASAL && tempBasalRecord == null -> - Single.error(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL")) - else -> null - }?.let { return it } + return Single.error(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL")) + } - - return dao.save( - HistoryRecordEntity( - id = id, - date = date, - createdAt = currentTimeMillis(), - commandType = commandType, - tempBasalRecord = tempBasalRecord, - bolusRecord = bolusRecord, - initialResult = initialResult, - resolvedResult = resolveResult, - resolvedAt = resolvedAt, - ) + return dao.save(HistoryRecordEntity( + id = id, + date = date, + createdAt = currentTimeMillis(), + commandType = commandType, + tempBasalRecord = tempBasalRecord, + bolusRecord = bolusRecord, + initialResult = initialResult, + resolvedResult = resolveResult, + resolvedAt = resolvedAt) ).toSingle { id } } From 81ad52ebce8d037c08d17af925c7bc2da4d452be Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Fri, 26 Feb 2021 23:56:39 +0100 Subject: [PATCH 05/14] implement SPS1 command I had to add a new dependency: `tink-android:1.5.0` for X25519 --- core/core_dependencies.gradle | 1 + omnipod-dash/build.gradle | 5 +-- .../driver/comm/OmnipodDashBleManagerImpl.kt | 2 + .../dash/driver/comm/command/BleCommand.kt | 6 +++ .../dash/driver/comm/ltk/LTKExchanger.kt | 44 ++++++++++++++----- .../dash/driver/comm/message/MessageIO.kt | 4 +- .../driver/comm/message/PayloadSplitter.kt | 4 +- .../dash/driver/comm/packet/BlePacket.kt | 8 +++- .../driver/comm/scan/BleDiscoveredDevice.kt | 1 - 9 files changed, 56 insertions(+), 19 deletions(-) diff --git a/core/core_dependencies.gradle b/core/core_dependencies.gradle index 552aa32c15..25ecf5b36c 100644 --- a/core/core_dependencies.gradle +++ b/core/core_dependencies.gradle @@ -47,6 +47,7 @@ dependencies { //CryptoUtil api 'com.madgag.spongycastle:core:1.58.0.0' + // Graphview cannot be upgraded api "com.jjoe64:graphview:4.0.1" diff --git a/omnipod-dash/build.gradle b/omnipod-dash/build.gradle index 45c11a93bf..34ca4406c5 100644 --- a/omnipod-dash/build.gradle +++ b/omnipod-dash/build.gradle @@ -21,7 +21,6 @@ dependencies { implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-rxjava2:$room_version" kapt "androidx.room:room-compiler:$room_version" - implementation 'com.github.guepardoapps:kulid:1.1.2.0' - -} \ No newline at end of file + implementation 'com.google.crypto.tink:tink-android:1.5.0' +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index 3dda0461dc..d08d9c50c9 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -5,6 +5,7 @@ import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context +import com.google.crypto.tink.subtle.X25519 import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig @@ -24,6 +25,7 @@ import org.apache.commons.lang3.NotImplementedException import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.TimeoutException +import javax.crypto.KeyAgreement import javax.inject.Inject import javax.inject.Singleton diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt index a51e738df4..186585676c 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt @@ -1,5 +1,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command +import info.nightscout.androidaps.utils.extensions.toHex + open class BleCommand(val data: ByteArray) { constructor(type: BleCommandType) : this(byteArrayOf(type.value)) @@ -17,6 +19,10 @@ open class BleCommand(val data: ByteArray) { return true } + override fun toString(): String { + return "Raw command: [${data.toHex()}]"; + } + override fun hashCode(): Int { return data.contentHashCode() } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt index 666c7c0538..235b6f2261 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt @@ -1,31 +1,41 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk +import com.google.crypto.tink.subtle.X25519 import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding import info.nightscout.androidaps.utils.extensions.hexStringToByteArray +import java.security.SecureRandom internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO) { + private val privateKey = X25519.generatePrivateKey() + private val nonce = ByteArray(16) + private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) + val nodeId = controllerId.increment() + private var seq: Byte = 1 + + init{ + val random = SecureRandom() + random.nextBytes(nonce) + } fun negotiateLTKAndNonce(): LTK? { // send SP1, SP2 - // TODO: get this from somewhere(preferences?) - var seq: Byte = 1 - val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) - val nodeId = controllerId.increment() - - var sp1sp2 = sp1sp2(nodeId.address, sp2(), seq, controllerId, nodeId) + var sp1sp2 = sp1sp2(nodeId.address, sp2()) msgIO.sendMesssage(sp1sp2.messagePacket) -/* - var sps1 = + seq++ + var sps1 = sps1() msgIO.sendMesssage(sps1.messagePacket) // send SPS1 + // read SPS1 val podSps1 = msgIO.receiveMessage() - + aapsLogger.info(LTag.PUMPBTCOMM, "Received message: %s", podSps1) +/* // send SPS2 var sps2 = PairMessage() msgIO.sendMesssage(sps2.messagePacket) @@ -46,7 +56,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI return GET_POD_STATUS_HEX_COMMAND.hexStringToByteArray() } - fun sp1sp2(sp1: ByteArray, sp2: ByteArray, seq: Byte, controllerId: Id, nodeId: Id): PairMessage { + fun sp1sp2(sp1: ByteArray, sp2: ByteArray): PairMessage { val payload = StringLengthPrefixEncoding.formatKeys( arrayOf("SP1=", ",SP2="), arrayOf(sp1, sp2), @@ -59,6 +69,20 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI ) } + fun sps1(): PairMessage { + val publicKey = X25519.publicFromPrivate(privateKey) + val payload = StringLengthPrefixEncoding.formatKeys( + arrayOf("SPS1="), + arrayOf(publicKey+nonce), + ) + return PairMessage( + sequenceNumber = seq, + source = controllerId, + destination = nodeId, + payload = payload, + ) + } + companion object { private val GET_POD_STATUS_HEX_COMMAND = "ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that. diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt index 7f742a4aca..0f04d55c0c 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt @@ -38,7 +38,7 @@ class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) { fun receiveMessage(): MessagePacket { val expectRTS = bleIO.receivePacket(CharacteristicType.CMD) - if (BleCommand(expectRTS) != BleCommandCTS()) { + if (BleCommand(expectRTS) != BleCommandRTS()) { throw UnexpectedCommandException(BleCommand(expectRTS)) } bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandCTS().data) @@ -53,7 +53,7 @@ class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) { } } if (joiner.oneExtra) { - var data = bleIO.receivePacket(CharacteristicType.DATA) + data = bleIO.receivePacket(CharacteristicType.DATA) val accumulateAction = joiner.accumulate(data) if (accumulateAction is PayloadJoinerActionReject) { bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandNack(accumulateAction.idx).data) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt index d74032e6fe..6a0c60139b 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt @@ -25,6 +25,7 @@ internal class PayloadSplitter(private val payload: ByteArray) { ret.add(LastOptionalPlusOneBlePacket( index = 1, payload = payload.copyOfRange(end, payload.size), + size = (payload.size-end).toByte(), )) } return ret @@ -53,9 +54,10 @@ internal class PayloadSplitter(private val payload: ByteArray) { payload = payload.copyOfRange(middleFragments * MiddleBlePacket.CAPACITY + FirstBlePacket.CAPACITY_WITH_MIDDLE_PACKETS, middleFragments * MiddleBlePacket.CAPACITY + FirstBlePacket.CAPACITY_WITH_MIDDLE_PACKETS + end), crc32 = crc32, )) - if (rest > 14) { + if (rest > LastBlePacket.CAPACITY) { ret.add(LastOptionalPlusOneBlePacket( index = (middleFragments + 2).toByte(), + size = (rest-LastBlePacket.CAPACITY).toByte(), payload = payload.copyOfRange(middleFragments * MiddleBlePacket.CAPACITY + FirstBlePacket.CAPACITY_WITH_MIDDLE_PACKETS + LastBlePacket.CAPACITY, payload.size), )) } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt index b9bd30a252..acdd237431 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt @@ -7,6 +7,7 @@ sealed class BlePacket { abstract fun asByteArray(): ByteArray companion object { + const val MAX_BLE_PACKET_LEN = 20 const val MAX_BLE_BUFFER_LEN = MAX_BLE_PACKET_LEN + 1 // we use this as the size allocated for the ByteBuffer } @@ -33,6 +34,7 @@ data class FirstBlePacket(val totalFragments: Byte, val payload: ByteArray, val } companion object { + internal const val CAPACITY_WITHOUT_MIDDLE_PACKETS = 13 // we are using all fields internal const val CAPACITY_WITH_MIDDLE_PACKETS = 18 // we are not using crc32 or size internal const val CAPACITY_WITH_THE_OPTIONAL_PLUS_ONE_PACKET = 18 @@ -46,6 +48,7 @@ data class MiddleBlePacket(val index: Byte, val payload: ByteArray) : BlePacket( } companion object { + internal const val CAPACITY = 19 } } @@ -66,14 +69,15 @@ data class LastBlePacket(val index: Byte, val size: Byte, val payload: ByteArray } companion object { + internal const val CAPACITY = 14 } } -data class LastOptionalPlusOneBlePacket(val index: Byte, val payload: ByteArray) : BlePacket() { +data class LastOptionalPlusOneBlePacket(val index: Byte, val payload: ByteArray, val size: Byte) : BlePacket() { override fun asByteArray(): ByteArray { - return byteArrayOf(index) + payload + return byteArrayOf(index, size) + payload } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.kt index 1b4a2cee2a..bf3d3dd74c 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.kt @@ -31,7 +31,6 @@ class BleDiscoveredDevice(val scanResult: ScanResult, private val scanRecord: Sc @Throws(DiscoveredInvalidPodException::class) private fun validatePodId() { - val scanRecord = scanResult.scanRecord val serviceUUIDs = scanRecord.serviceUuids val hexPodId = extractUUID16(serviceUUIDs[3]) + extractUUID16(serviceUUIDs[4]) val podId = hexPodId.toLong(16) From 1aa6d02893baf076e38064a16205f0bb11dc0b11 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sat, 27 Feb 2021 13:28:19 +0100 Subject: [PATCH 06/14] ble: implement message reading&joining Now are able to read the first message: ``` INFO[0005] Received SPS1 6b943ec06b594f8a0383f384a3c916da75e1c7846c3e1b73f72f86ee2dc48774b2b4e5ad62d798b76cfd06be1cd4c937 DEBU[0005] Donna LTK: b874cb3cbe487040442138452faeb02d284ac55f489f19593265ff52f7310f1f DEBU[0005] First key 58cb3b742dc48774000000001cd4c937 :: 16 DEBU[0005] CMACY: 16 DEBU[0005] Intermediar key 4c13eebc4cf09795a07c50bf13786c18 :: 16 DEBU[0005] Pod public 2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74 :: 32 DEBU[0005] Pod nonce 00000000000000000000000000000000 :: 16 DEBU[0005] Generated SPS1: 535053313d00302fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b7400000000000000000000000000000000 TRAC[0005] CMD notification return: 4/00 TRAC[0005] received CMD: 01 TRAC[0005] Sending message: 54570003000006e00000109100001092535053313d00302fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b7400000000000000000000000000000000 TRAC[0005] DATA notification return: 23/000354570003000006e000001091000010925350 TRAC[0005] DATA notification return: 23/0153313d00302fe57da347cd62431528daac5fbb TRAC[0005] DATA notification return: 23/02290730fff684afc4cfc2ed90995f58cb3b7400 TRAC[0005] DATA notification return: 23/030f7d02931d0000000000000000000000000000 TRAC[0005] DATA notification return: 23/0401000000000000000000000000000000000000 TRAC[0005] received CMD: 04 ``` --- .../driver/comm/OmnipodDashBleManagerImpl.kt | 7 +- .../dash/driver/comm/command/BleCommand.kt | 2 +- .../comm/exceptions/MessageIOException.kt | 3 + .../pump/omnipod/dash/driver/comm/io/BleIO.kt | 1 - .../dash/driver/comm/ltk/LTKExchanger.kt | 7 +- .../comm/message/CrcMismatchException.kt | 6 + .../comm/message/IncorrectPacketException.kt | 5 + .../dash/driver/comm/message/MessageIO.kt | 45 ++++--- .../dash/driver/comm/message/PayloadJoiner.kt | 111 +++++++++++++++--- .../driver/comm/message/PayloadSplitter.kt | 6 +- .../dash/driver/comm/packet/BlePacket.kt | 20 +++- 11 files changed, 157 insertions(+), 56 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/CrcMismatchException.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/IncorrectPacketException.kt diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index d08d9c50c9..1f8c9ed5b4 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -5,7 +5,6 @@ import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context -import com.google.crypto.tink.subtle.X25519 import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig @@ -25,7 +24,6 @@ import org.apache.commons.lang3.NotImplementedException import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.TimeoutException -import javax.crypto.KeyAgreement import javax.inject.Inject import javax.inject.Singleton @@ -112,6 +110,11 @@ class OmnipodDashBleManagerImpl @Inject constructor(private val context: Context TODO("not implemented") } + override fun getPodId(): Id { + // TODO: return something meaningful here + return Id.fromInt(4243) + } + companion object { private const val CONNECT_TIMEOUT_MS = 5000 diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt index 186585676c..e437bd23a9 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt @@ -20,7 +20,7 @@ open class BleCommand(val data: ByteArray) { } override fun toString(): String { - return "Raw command: [${data.toHex()}]"; + return "Raw command: [${data.toHex()}]" } override fun hashCode(): Int { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt new file mode 100644 index 0000000000..c5d3d0ead1 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions + +class MessageIOException(override val cause: Throwable) : Exception(cause) \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt index a763193b77..3340d1daf1 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt @@ -50,7 +50,6 @@ class BleIO(private val aapsLogger: AAPSLogger, private val chars: Map BleCommandSuccess() - is PayloadJoinerActionReject -> BleCommandFail() - } - bleIO.sendAndConfirmPacket(CharacteristicType.CMD, finalCmd.data) - val fullPayload = joiner.bytes() - return MessagePacket.parse(fullPayload) } } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt index d2f754e396..51af76eef1 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt @@ -1,31 +1,108 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io -import java.io.ByteArrayOutputStream +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.CrcMismatchException +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.IncorrectPacketException +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.crc32 +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.BlePacket +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.FirstBlePacket +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.LastBlePacket +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.LastOptionalPlusOneBlePacket +import java.lang.Integer.min +import java.nio.ByteBuffer +import java.util.* -sealed class PayloadJoinerAction - -class PayloadJoinerActionAccept : PayloadJoinerAction() -class PayloadJoinerActionReject(val idx: Byte) : PayloadJoinerAction() - -class PayloadJoiner { +@ExperimentalUnsignedTypes +class PayloadJoiner(private val firstPacket: ByteArray) { var oneExtra: Boolean = false + val fullFragments: Int + var crc: Long = 0 + private var expectedIndex = 0 + private val fragments: LinkedList = LinkedList() - private val payload = ByteArrayOutputStream() + init { + if (firstPacket.size < 2) { + throw IncorrectPacketException(0, firstPacket) + } + fullFragments = firstPacket[1].toInt() + when { + // Without middle packets + firstPacket.size < FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS -> + throw IncorrectPacketException(0, firstPacket) - fun start(payload: ByteArray): Int { - TODO("not implemented") + fullFragments == 0 -> { + crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUInt().toLong() + val rest = firstPacket[6] + val end = min(rest + 7, BlePacket.MAX_LEN) + oneExtra = rest + 7 > end + fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, end)) + if (end > firstPacket.size) { + throw IncorrectPacketException(0, firstPacket) + } + } + + // With middle packets + firstPacket.size < BlePacket.MAX_LEN -> + throw IncorrectPacketException(0, firstPacket) + + else -> { + fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS, BlePacket.MAX_LEN)) + } + } } - fun accumulate(payload: ByteArray): PayloadJoinerAction { - TODO("not implemented") + fun accumulate(packet: ByteArray) { + if (packet.size < 3) { // idx, size, at least 1 byte of payload + throw IncorrectPacketException((expectedIndex + 1).toByte(), packet) + } + val idx = packet[0].toInt() + if (idx != expectedIndex + 1) { + throw IncorrectPacketException((expectedIndex + 1).toByte(), packet) + } + expectedIndex++ + when { + idx < fullFragments -> { // this is a middle fragment + if (packet.size < BlePacket.MAX_LEN) { + throw IncorrectPacketException(idx.toByte(), packet) + } + fragments.add(packet.copyOfRange(1, BlePacket.MAX_LEN)) + } + + idx == fullFragments -> { // this is the last fragment + if (packet.size < LastBlePacket.HEADER_SIZE) { + throw IncorrectPacketException(idx.toByte(), packet) + } + crc = ByteBuffer.wrap(packet.copyOfRange(2, 6)).int.toUInt().toLong() + val rest = packet[1].toInt() + val end = min(rest, BlePacket.MAX_LEN) + if (packet.size < end) { + throw IncorrectPacketException(idx.toByte(), packet) + } + oneExtra = rest + LastBlePacket.HEADER_SIZE > end + fragments.add(packet.copyOfRange(LastBlePacket.HEADER_SIZE, BlePacket.MAX_LEN)) + } + + idx > fullFragments -> { // this is the extra fragment + val size = packet[1].toInt() + if (packet.size < LastOptionalPlusOneBlePacket.HEADER_SIZE + size) { + throw IncorrectPacketException(idx.toByte(), packet) + } + + fragments.add(packet.copyOfRange(LastOptionalPlusOneBlePacket.HEADER_SIZE, LastOptionalPlusOneBlePacket.HEADER_SIZE + size)) + } + } } - fun finalize(): PayloadJoinerAction { - TODO("not implemented") + fun finalize(): ByteArray { + val totalLen = fragments.fold(0, { acc, elem -> acc + elem.size }) + val bb = ByteBuffer.allocate(totalLen) + fragments.map { fragment -> bb.put(fragment) } + bb.flip() + val bytes = bb.array() + if (bytes.crc32() != crc) { + throw CrcMismatchException(bytes.crc32(), crc, bytes) + } + return bytes.copyOfRange(0, bytes.size) } - fun bytes(): ByteArray { - TODO("not implemented") - } } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt index 6a0c60139b..bd5e5811db 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadSplitter.kt @@ -25,7 +25,7 @@ internal class PayloadSplitter(private val payload: ByteArray) { ret.add(LastOptionalPlusOneBlePacket( index = 1, payload = payload.copyOfRange(end, payload.size), - size = (payload.size-end).toByte(), + size = (payload.size - end).toByte(), )) } return ret @@ -57,7 +57,7 @@ internal class PayloadSplitter(private val payload: ByteArray) { if (rest > LastBlePacket.CAPACITY) { ret.add(LastOptionalPlusOneBlePacket( index = (middleFragments + 2).toByte(), - size = (rest-LastBlePacket.CAPACITY).toByte(), + size = (rest - LastBlePacket.CAPACITY).toByte(), payload = payload.copyOfRange(middleFragments * MiddleBlePacket.CAPACITY + FirstBlePacket.CAPACITY_WITH_MIDDLE_PACKETS + LastBlePacket.CAPACITY, payload.size), )) } @@ -65,7 +65,7 @@ internal class PayloadSplitter(private val payload: ByteArray) { } } -private fun ByteArray.crc32(): Long { +internal fun ByteArray.crc32(): Long { val crc = CRC32() crc.update(this) return crc.value diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt index acdd237431..bd67c5a4d3 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt @@ -8,8 +8,8 @@ sealed class BlePacket { companion object { - const val MAX_BLE_PACKET_LEN = 20 - const val MAX_BLE_BUFFER_LEN = MAX_BLE_PACKET_LEN + 1 // we use this as the size allocated for the ByteBuffer + const val MAX_LEN = 20 + const val MAX_BLE_BUFFER_LEN = MAX_LEN + 1 // we use this as the size allocated for the ByteBuffer } } @@ -35,8 +35,11 @@ data class FirstBlePacket(val totalFragments: Byte, val payload: ByteArray, val companion object { - internal const val CAPACITY_WITHOUT_MIDDLE_PACKETS = 13 // we are using all fields - internal const val CAPACITY_WITH_MIDDLE_PACKETS = 18 // we are not using crc32 or size + internal const val HEADER_SIZE_WITHOUT_MIDDLE_PACKETS = 7 // we are using all fields + internal const val HEADER_SIZE_WITH_MIDDLE_PACKETS = 2 + + internal const val CAPACITY_WITHOUT_MIDDLE_PACKETS = MAX_LEN - HEADER_SIZE_WITHOUT_MIDDLE_PACKETS // we are using all fields + internal const val CAPACITY_WITH_MIDDLE_PACKETS = MAX_LEN - HEADER_SIZE_WITH_MIDDLE_PACKETS // we are not using crc32 or size internal const val CAPACITY_WITH_THE_OPTIONAL_PLUS_ONE_PACKET = 18 } } @@ -70,7 +73,8 @@ data class LastBlePacket(val index: Byte, val size: Byte, val payload: ByteArray companion object { - internal const val CAPACITY = 14 + internal const val HEADER_SIZE = 6 + internal const val CAPACITY = MAX_LEN - HEADER_SIZE } } @@ -79,5 +83,11 @@ data class LastOptionalPlusOneBlePacket(val index: Byte, val payload: ByteArray, override fun asByteArray(): ByteArray { return byteArrayOf(index, size) + payload } + + companion object { + + internal const val HEADER_SIZE = 2 + internal const val CAPACITY = MAX_LEN - HEADER_SIZE + } } From 39408ac53583bfe673a5e62a67f50b90d700c969 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sat, 27 Feb 2021 13:39:47 +0100 Subject: [PATCH 07/14] BLE LTK: define all the steps --- .../dash/driver/comm/ltk/LTKExchanger.kt | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt index 3641435a69..7b7fa8dcb1 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt @@ -6,8 +6,10 @@ import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding import info.nightscout.androidaps.utils.extensions.hexStringToByteArray +import info.nightscout.androidaps.utils.extensions.toHex import java.security.SecureRandom internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO) { @@ -17,6 +19,8 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) val nodeId = controllerId.increment() private var seq: Byte = 1 + private var ltk = ByteArray(0) + private var noncePrefix = ByteArray(0) init { val random = SecureRandom() @@ -36,28 +40,36 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI // read SPS1 val podSps1 = msgIO.receiveMessage() aapsLogger.info(LTag.PUMPBTCOMM, "Received message: %s", podSps1) -/* + processSps1FromPod(podSps1) + + seq++ + // send SPS2 - var sps2 = PairMessage() + val sps2 = sps2() msgIO.sendMesssage(sps2.messagePacket) // read SPS2 + val podSps2 = msgIO.receiveMessage() + validatePodSps2(podSps2) // send SP0GP0 - msgIO.sendMesssage(sps2.messagePacket) + msgIO.sendMesssage(sp0gp0().messagePacket) // read P0 + + //TODO: if we fail to read or validate p0 will lead to undefined state + // it could be that: + // - the pod answered with p0 and we did not receive/could not process the answer + // - the pod answered with some sort of error val p0 = msgIO.receiveMessage() -*/ - return null + validateP0(p0) + + return LTK( + ltk = ltk, + noncePrefix = noncePrefix, + ) } - private fun sp2(): ByteArray { - // This is GetPodStatus command, with page 0 parameter. - // We could replace that in the future with the serialized GetPodStatus() - return GET_POD_STATUS_HEX_COMMAND.hexStringToByteArray() - } - - fun sp1sp2(sp1: ByteArray, sp2: ByteArray): PairMessage { + private fun sp1sp2(sp1: ByteArray, sp2: ByteArray): PairMessage { val payload = StringLengthPrefixEncoding.formatKeys( arrayOf("SP1=", ",SP2="), arrayOf(sp1, sp2), @@ -70,7 +82,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI ) } - fun sps1(): PairMessage { + private fun sps1(): PairMessage { val publicKey = X25519.publicFromPrivate(privateKey) val payload = StringLengthPrefixEncoding.formatKeys( arrayOf("SPS1="), @@ -84,6 +96,34 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI ) } + private fun processSps1FromPod(msg: MessagePacket) { + aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS1 from pod: ${msg.payload.toHex()}") + + } + + private fun sps2(): PairMessage { + TODO("implement") + } + + private fun validatePodSps2(podSps2: MessagePacket) { + TODO("implement") + + } + + private fun sp2(): ByteArray { + // This is GetPodStatus command, with page 0 parameter. + // We could replace that in the future with the serialized GetPodStatus() + return GET_POD_STATUS_HEX_COMMAND.hexStringToByteArray() + } + + private fun sp0gp0(): PairMessage { + TODO("implement") + } + + private fun validateP0(p0: MessagePacket) { + TODO("implement") + + } companion object { private val GET_POD_STATUS_HEX_COMMAND = "ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that. From ddfbd2e7bdff41c9d01c7eee63b7f3b20fc94c36 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sat, 27 Feb 2021 18:33:04 +0100 Subject: [PATCH 08/14] dash ble tlk: start generating keys --- .../dash/driver/comm/ltk/LTKExchanger.kt | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt index 7b7fa8dcb1..455f0f1575 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt @@ -1,21 +1,32 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk +import com.google.crypto.tink.mac.AesCmacKeyManager +import com.google.crypto.tink.proto.AesCmac import com.google.crypto.tink.subtle.X25519 + import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding import info.nightscout.androidaps.utils.extensions.hexStringToByteArray import info.nightscout.androidaps.utils.extensions.toHex +import org.spongycastle.crypto.engines.AESEngine import java.security.SecureRandom internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO) { - private val privateKey = X25519.generatePrivateKey() - private val nonce = ByteArray(16) + private val pdmPrivate = X25519.generatePrivateKey() + private val pdmPublic = X25519.publicFromPrivate(pdmPrivate) + private var podPublic = ByteArray(PUBLIC_KEY_SIZE) + private var podNonce = ByteArray(NONCE_SIZE) + private val pdmNonce = ByteArray(NONCE_SIZE) + private val confPdm = ByteArray(CONF_SIZE) + private val confPod = ByteArray(CONF_SIZE) + private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) val nodeId = controllerId.increment() private var seq: Byte = 1 @@ -24,7 +35,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI init { val random = SecureRandom() - random.nextBytes(nonce) + random.nextBytes(pdmNonce) } fun negotiateLTKAndNonce(): LTK? { @@ -41,9 +52,9 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI val podSps1 = msgIO.receiveMessage() aapsLogger.info(LTag.PUMPBTCOMM, "Received message: %s", podSps1) processSps1FromPod(podSps1) - + // now we have all the data to generate: confPod, confPdm, ltk and noncePrefix + generateKeys() seq++ - // send SPS2 val sps2 = sps2() msgIO.sendMesssage(sps2.messagePacket) @@ -52,6 +63,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI val podSps2 = msgIO.receiveMessage() validatePodSps2(podSps2) + seq++ // send SP0GP0 msgIO.sendMesssage(sp0gp0().messagePacket) // read P0 @@ -83,7 +95,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI } private fun sps1(): PairMessage { - val publicKey = X25519.publicFromPrivate(privateKey) + val publicKey = X25519.publicFromPrivate(pdmPrivate) val payload = StringLengthPrefixEncoding.formatKeys( arrayOf("SPS1="), arrayOf(publicKey + nonce), @@ -98,7 +110,11 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private fun processSps1FromPod(msg: MessagePacket) { aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS1 from pod: ${msg.payload.toHex()}") - + if (msg.payload.size != 48) { + throw MessageIOException() + } + podPublic = msg.payload.copyOfRange(0, PUBLIC_KEY_SIZE) + podNonce = msg.payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE+ NONCE_SIZE) } private fun sps2(): PairMessage { @@ -124,8 +140,23 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI TODO("implement") } - companion object { + fun generateKeys() { + val curveLTK = X25519.computeSharedSecret(pdmPrivate, podPublic) + aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, donna key: ${curveLTK.toHex()}") + //first_key = data.pod_public[-4:] + data.pdm_public[-4:] + data.pod_nonce[-4:] + data.pdm_nonce[-4:] + + val firstKey = podPublic.copyOfRange(podPublic.size-4, podPublic.size) + + pdmPublic.copyOfRange(pdmPublic.size-4, pdmPublic.size) + + podNonce.copyOfRange(podNonce.size-4, podNonce.size) + + pdmNonce.copyOfRange(pdmNonce.size-4, pdmNonce.size) + aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, first key: ${firstKey.toHex()}") + } + + companion object { + private val PUBLIC_KEY_SIZE = 32 + private val NONCE_SIZE = 16 + private val CONF_SIZE = 16 private val GET_POD_STATUS_HEX_COMMAND = "ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that. } } \ No newline at end of file From ddb1c1834935fd1353dea1e41bfa20bd318d0093 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sat, 27 Feb 2021 20:45:01 +0100 Subject: [PATCH 09/14] ble ltk: implement encryption and message parsing --- .../CouldNotParseMessageException.kt | 5 ++ .../comm/exceptions/MessageIOException.kt | 5 +- .../dash/driver/comm/ltk/LTKExchanger.kt | 40 +++++++++---- .../dash/driver/comm/message/MessageIO.kt | 1 - .../dash/driver/comm/message/MessagePacket.kt | 58 ++++++++++++++++++- .../dash/driver/comm/message/PayloadJoiner.kt | 15 ++--- 6 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt new file mode 100644 index 0000000000..65df8175e6 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions + +import info.nightscout.androidaps.utils.extensions.toHex + +class CouldNotParseMessageException(val payload: ByteArray): Exception("Could not parse message payload: ${payload.toHex()}") \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt index c5d3d0ead1..ea4201bc02 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt @@ -1,3 +1,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions -class MessageIOException(override val cause: Throwable) : Exception(cause) \ No newline at end of file +class MessageIOException : Exception { + constructor(msg: String): super(msg) + constructor(cause: Throwable): super(cause) +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt index 455f0f1575..cd9dde9b41 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt @@ -1,9 +1,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk -import com.google.crypto.tink.mac.AesCmacKeyManager -import com.google.crypto.tink.proto.AesCmac import com.google.crypto.tink.subtle.X25519 - import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id @@ -15,6 +12,8 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message. import info.nightscout.androidaps.utils.extensions.hexStringToByteArray import info.nightscout.androidaps.utils.extensions.toHex import org.spongycastle.crypto.engines.AESEngine +import org.spongycastle.crypto.macs.CMac +import org.spongycastle.crypto.params.KeyParameter import java.security.SecureRandom internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO) { @@ -30,7 +29,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) val nodeId = controllerId.increment() private var seq: Byte = 1 - private var ltk = ByteArray(0) + private var ltk = ByteArray(CMAC_SIZE) private var noncePrefix = ByteArray(0) init { @@ -98,7 +97,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI val publicKey = X25519.publicFromPrivate(pdmPrivate) val payload = StringLengthPrefixEncoding.formatKeys( arrayOf("SPS1="), - arrayOf(publicKey + nonce), + arrayOf(publicKey + pdmNonce), ) return PairMessage( sequenceNumber = seq, @@ -111,10 +110,10 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private fun processSps1FromPod(msg: MessagePacket) { aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS1 from pod: ${msg.payload.toHex()}") if (msg.payload.size != 48) { - throw MessageIOException() + throw MessageIOException("Invalid payload size") } podPublic = msg.payload.copyOfRange(0, PUBLIC_KEY_SIZE) - podNonce = msg.payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE+ NONCE_SIZE) + podNonce = msg.payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE + NONCE_SIZE) } private fun sps2(): PairMessage { @@ -144,19 +143,36 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI fun generateKeys() { val curveLTK = X25519.computeSharedSecret(pdmPrivate, podPublic) aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, donna key: ${curveLTK.toHex()}") - //first_key = data.pod_public[-4:] + data.pdm_public[-4:] + data.pod_nonce[-4:] + data.pdm_nonce[-4:] - val firstKey = podPublic.copyOfRange(podPublic.size-4, podPublic.size) - + pdmPublic.copyOfRange(pdmPublic.size-4, pdmPublic.size) - + podNonce.copyOfRange(podNonce.size-4, podNonce.size) - + pdmNonce.copyOfRange(pdmNonce.size-4, pdmNonce.size) + //first_key = data.pod_public[-4:] + data.pdm_public[-4:] + data.pod_nonce[-4:] + data.pdm_nonce[-4:] + val firstKey = podPublic.copyOfRange(podPublic.size - 4, podPublic.size) + + pdmPublic.copyOfRange(pdmPublic.size - 4, pdmPublic.size)+ + podNonce.copyOfRange(podNonce.size - 4, podNonce.size)+ + pdmNonce.copyOfRange(pdmNonce.size - 4, pdmNonce.size) aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, first key: ${firstKey.toHex()}") + + val aesEngine = AESEngine() + val intermediateMac = CMac(aesEngine) + intermediateMac.init(KeyParameter(firstKey)) + intermediateMac.update(curveLTK, 0, curveLTK.size) + val intermediateKey = ByteArray(CMAC_SIZE) + intermediateMac.doFinal(intermediateKey, 0) + aapsLogger.debug(LTag.PUMPBTCOMM, "Intermediate key: ${intermediateKey.toHex()}") + + val ltkMac = CMac(aesEngine) + ltkMac.init(KeyParameter(firstKey)) + ltkMac.update(curveLTK, 0, curveLTK.size) + intermediateMac.doFinal(ltk, 0) + aapsLogger.debug(LTag.PUMPBTCOMM, "LTK: ${ltk.toHex()}") + } companion object { + private val PUBLIC_KEY_SIZE = 32 private val NONCE_SIZE = 16 private val CONF_SIZE = 16 + private val CMAC_SIZE = 16 private val GET_POD_STATUS_HEX_COMMAND = "ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that. } } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt index 88e6a5dde4..4b67f33167 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt @@ -36,7 +36,6 @@ class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) { bleIO.flushIncomingQueues() } - @kotlin.ExperimentalUnsignedTypes fun receiveMessage(): MessagePacket { val expectRTS = bleIO.receivePacket(CharacteristicType.CMD) if (BleCommand(expectRTS) != BleCommandRTS()) { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt index d9cd686b4c..3563745ffe 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt @@ -1,6 +1,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseMessageException import java.nio.ByteBuffer /*** @@ -69,9 +70,56 @@ data class MessagePacket( companion object { private val MAGIC_PATTERN = "TW" // all messages start with this string + private val HEADER_SIZE = 16 fun parse(payload: ByteArray): MessagePacket { - TODO("implement message header parsing") + if (payload.size < HEADER_SIZE) { + throw CouldNotParseMessageException(payload) + } + if (payload.copyOfRange(0, 2).toString() != "TW") { + throw info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseMessageException(payload) + } + val f1 = Flag(payload[2].toInt()) + val sas = f1.get(3) != 0 + val tfs = f1.get(4) != 0 + val version = ((f1.get(0) shl 2) or (f1.get(1) shl 1) or (f1.get(2) shl 0)).toShort() + val eqos = (f1.get(7) or (f1.get(6) shl 1) or (f1.get(5) shl 2)).toShort() + + val f2 = Flag(payload[3].toInt()) + val ack = f2.get(0) != 0 + val priority = f2.get(1) != 0 + val lastMessage = f2.get(2) != 0 + val gateway = f2.get(3) != 0 + val type = MessageType.byValue((f1.get(7) or (f1.get(6) shl 1) or (f1.get(5) shl 2) or (f1.get(4) shl 3)).toByte()) + if (version.toInt() != 0) { + throw CouldNotParseMessageException(payload) + } + val sequenceNumber = payload[4] + val ackNumber = payload[5] + val size = (payload[6].toInt() shl 3) or (payload[7].toInt() ushr 5) + if (size + HEADER_SIZE > payload.size) { + throw CouldNotParseMessageException(payload) + } + val payloadEnd = 16 + size + + if (type == MessageType.ENCRYPTED) 8 + else 0 + + return MessagePacket( + type = type, + ack = ack, + eqos = eqos, + priority = priority, + lastMessage = lastMessage, + gateway = gateway, + sas = sas, + tfs = tfs, + version = version, + sequenceNumber = payload[4], + ackNumber = payload[5], + source = Id(payload.copyOfRange(8, 12)), + destination = Id(payload.copyOfRange(12, 16)), + payload = payload.copyOfRange(16, payloadEnd), + ) } } } @@ -85,8 +133,12 @@ private class Flag(var value: Int = 0) { value = value or mask } - fun get(idx: Byte): Boolean { + fun get(idx: Byte): Int { val mask = 1 shl (7 - idx) - return value and mask != 0 + if (value and mask == 0) { + return 0 + } + return 1 + } } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt index 51af76eef1..9b383b5f22 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt @@ -11,7 +11,6 @@ import java.lang.Integer.min import java.nio.ByteBuffer import java.util.* -@ExperimentalUnsignedTypes class PayloadJoiner(private val firstPacket: ByteArray) { var oneExtra: Boolean = false @@ -30,8 +29,8 @@ class PayloadJoiner(private val firstPacket: ByteArray) { firstPacket.size < FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS -> throw IncorrectPacketException(0, firstPacket) - fullFragments == 0 -> { - crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUInt().toLong() + fullFragments == 0 -> { + crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUnsignedLong() val rest = firstPacket[6] val end = min(rest + 7, BlePacket.MAX_LEN) oneExtra = rest + 7 > end @@ -42,10 +41,10 @@ class PayloadJoiner(private val firstPacket: ByteArray) { } // With middle packets - firstPacket.size < BlePacket.MAX_LEN -> + firstPacket.size < BlePacket.MAX_LEN -> throw IncorrectPacketException(0, firstPacket) - else -> { + else -> { fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS, BlePacket.MAX_LEN)) } } @@ -72,7 +71,7 @@ class PayloadJoiner(private val firstPacket: ByteArray) { if (packet.size < LastBlePacket.HEADER_SIZE) { throw IncorrectPacketException(idx.toByte(), packet) } - crc = ByteBuffer.wrap(packet.copyOfRange(2, 6)).int.toUInt().toLong() + crc = ByteBuffer.wrap(packet.copyOfRange(2, 6)).int.toUnsignedLong() val rest = packet[1].toInt() val end = min(rest, BlePacket.MAX_LEN) if (packet.size < end) { @@ -105,4 +104,6 @@ class PayloadJoiner(private val firstPacket: ByteArray) { return bytes.copyOfRange(0, bytes.size) } -} \ No newline at end of file +} + +private fun Int.toUnsignedLong() = this.toLong() and 0xffffffffL \ No newline at end of file From ee0ac46c5a9d2ef6ca414327e2ba3d3b8060eceb Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 28 Feb 2021 18:21:00 +0100 Subject: [PATCH 10/14] Finish implementing the LTK exchange On the fake pod: ``` INFO[0067] LTK 21f4a9d825ce5e57bad5b4958c6ff95e ``` Logcat: ``` 2021-02-28 18:21:04.763 19530-22490/info.nightscout.androidaps I/PUMPCOMM: [AsyncTask #4]: [OmnipodDashBleManagerImpl.connect():86]: Got LTK: 21f4a9d825ce5e57bad5b4958c6ff95e `` --- .../driver/comm/OmnipodDashBleManagerImpl.kt | 14 +- .../CouldNotParseMessageException.kt | 2 +- .../comm/exceptions/MessageIOException.kt | 4 +- .../pump/omnipod/dash/driver/comm/ltk/LTK.kt | 3 +- .../dash/driver/comm/ltk/LTKExchanger.kt | 150 +++++++++++++----- .../dash/driver/comm/message/MessageIO.kt | 2 +- .../dash/driver/comm/message/MessagePacket.kt | 14 +- .../dash/driver/comm/message/PayloadJoiner.kt | 26 +-- .../message/StringLengthPrefixEncoding.kt | 31 +++- .../dash/driver/comm/packet/BlePacket.kt | 28 ++-- .../pump/omnipod/dash/util/Functions.kt | 2 +- 11 files changed, 191 insertions(+), 85 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index 1f8c9ed5b4..50b50d7e07 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -21,6 +21,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEven import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command import io.reactivex.Observable import org.apache.commons.lang3.NotImplementedException +import info.nightscout.androidaps.utils.extensions.toHex import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.TimeoutException @@ -96,8 +97,10 @@ class OmnipodDashBleManagerImpl @Inject constructor(private val context: Context val ltkExchanger = LTKExchanger(aapsLogger, msgIO) emitter.onNext(PodEvent.Pairing) - val ltk = ltkExchanger.negotiateLTKAndNonce() - aapsLogger.info(LTag.PUMPCOMM, "Got LTK and Nonce Prefix: ${ltk}") + val ltk = ltkExchanger.negotiateLTK() + + aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}") + emitter.onNext(PodEvent.Connected(PodScanner.POD_ID_NOT_ACTIVATED)) // TODO supply actual pod id emitter.onComplete() @@ -110,14 +113,9 @@ class OmnipodDashBleManagerImpl @Inject constructor(private val context: Context TODO("not implemented") } - override fun getPodId(): Id { - // TODO: return something meaningful here - return Id.fromInt(4243) - } - companion object { - private const val CONNECT_TIMEOUT_MS = 5000 + private const val CONNECT_TIMEOUT_MS = 7000 const val CONTROLLER_ID = 4242 // TODO read from preferences or somewhere else. } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt index 65df8175e6..5e10a0bb1e 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseMessageException.kt @@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.excepti import info.nightscout.androidaps.utils.extensions.toHex -class CouldNotParseMessageException(val payload: ByteArray): Exception("Could not parse message payload: ${payload.toHex()}") \ No newline at end of file +class CouldNotParseMessageException(val payload: ByteArray) : Exception("Could not parse message payload: ${payload.toHex()}") \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt index ea4201bc02..30cbac205b 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions class MessageIOException : Exception { - constructor(msg: String): super(msg) - constructor(cause: Throwable): super(cause) + constructor(msg: String) : super(msg) + constructor(cause: Throwable) : super(cause) } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt index 407d00a289..06c497ce18 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt @@ -1,8 +1,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk -data class LTK(val ltk: ByteArray, val noncePrefix: ByteArray) { +data class LTK(val ltk: ByteArray) { init { require(ltk.size == 16) - require(noncePrefix.size == 16) } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt index cd9dde9b41..330ae955cf 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt @@ -3,12 +3,14 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk import com.google.crypto.tink.subtle.X25519 import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys import info.nightscout.androidaps.utils.extensions.hexStringToByteArray import info.nightscout.androidaps.utils.extensions.toHex import org.spongycastle.crypto.engines.AESEngine @@ -23,21 +25,19 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private var podPublic = ByteArray(PUBLIC_KEY_SIZE) private var podNonce = ByteArray(NONCE_SIZE) private val pdmNonce = ByteArray(NONCE_SIZE) - private val confPdm = ByteArray(CONF_SIZE) - private val confPod = ByteArray(CONF_SIZE) - + private val pdmConf = ByteArray(CMAC_SIZE) + private val podConf = ByteArray(CMAC_SIZE) private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) val nodeId = controllerId.increment() private var seq: Byte = 1 private var ltk = ByteArray(CMAC_SIZE) - private var noncePrefix = ByteArray(0) init { val random = SecureRandom() random.nextBytes(pdmNonce) } - fun negotiateLTKAndNonce(): LTK? { + fun negotiateLTK(): LTK { // send SP1, SP2 var sp1sp2 = sp1sp2(nodeId.address, sp2()) msgIO.sendMesssage(sp1sp2.messagePacket) @@ -49,7 +49,6 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI // read SPS1 val podSps1 = msgIO.receiveMessage() - aapsLogger.info(LTag.PUMPBTCOMM, "Received message: %s", podSps1) processSps1FromPod(podSps1) // now we have all the data to generate: confPod, confPdm, ltk and noncePrefix generateKeys() @@ -67,22 +66,22 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI msgIO.sendMesssage(sp0gp0().messagePacket) // read P0 - //TODO: if we fail to read or validate p0 will lead to undefined state - // it could be that: + // TODO: failing to read or validate p0 will lead to undefined state + // It could be that: // - the pod answered with p0 and we did not receive/could not process the answer // - the pod answered with some sort of error + // But if sps2 conf value is incorrect, then we would probablysee this when receiving the pod podSps2(to test) val p0 = msgIO.receiveMessage() validateP0(p0) return LTK( ltk = ltk, - noncePrefix = noncePrefix, ) } private fun sp1sp2(sp1: ByteArray, sp2: ByteArray): PairMessage { val payload = StringLengthPrefixEncoding.formatKeys( - arrayOf("SP1=", ",SP2="), + arrayOf(SP1, SP2), arrayOf(sp1, sp2), ) return PairMessage( @@ -94,10 +93,9 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI } private fun sps1(): PairMessage { - val publicKey = X25519.publicFromPrivate(pdmPrivate) val payload = StringLengthPrefixEncoding.formatKeys( arrayOf("SPS1="), - arrayOf(publicKey + pdmNonce), + arrayOf(pdmPublic + pdmNonce), ) return PairMessage( sequenceNumber = seq, @@ -109,20 +107,41 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private fun processSps1FromPod(msg: MessagePacket) { aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS1 from pod: ${msg.payload.toHex()}") - if (msg.payload.size != 48) { + + val payload = parseKeys(arrayOf(SPS1), msg.payload)[0] + if (payload.size != 48) { throw MessageIOException("Invalid payload size") } - podPublic = msg.payload.copyOfRange(0, PUBLIC_KEY_SIZE) - podNonce = msg.payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE + NONCE_SIZE) + podPublic = payload.copyOfRange(0, PUBLIC_KEY_SIZE) + podNonce = payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE + NONCE_SIZE) } private fun sps2(): PairMessage { - TODO("implement") + val payload = StringLengthPrefixEncoding.formatKeys( + arrayOf(SPS2), + arrayOf(pdmConf), + ) + return PairMessage( + sequenceNumber = seq, + source = controllerId, + destination = nodeId, + payload = payload, + ) } - private fun validatePodSps2(podSps2: MessagePacket) { - TODO("implement") + private fun validatePodSps2(msg: MessagePacket) { + aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS2 from pod: ${msg.payload.toHex()}") + val payload = parseKeys(arrayOf(SPS2), msg.payload)[0] + aapsLogger.debug(LTag.PUMPBTCOMM, "SPS2 payload from pod: ${payload.toHex()}") + + if (payload.size != CMAC_SIZE) { + throw MessageIOException("Invalid payload size") + } + if (!podConf.contentEquals(payload)) { + aapsLogger.warn(LTag.PUMPBTCOMM, "Received invalid podConf. Expected: ${podConf.toHex()}. Got: ${payload.toHex()}") + throw MessageIOException("Invalid podConf value received") + } } private fun sp2(): ByteArray { @@ -132,39 +151,76 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI } private fun sp0gp0(): PairMessage { - TODO("implement") + val payload = SP0GP0.toByteArray() + return PairMessage( + sequenceNumber = seq, + source = controllerId, + destination = nodeId, + payload = payload, + ) } - private fun validateP0(p0: MessagePacket) { - TODO("implement") + private fun validateP0(msg: MessagePacket) { + aapsLogger.debug(LTag.PUMPBTCOMM, "Received P0 from pod: ${msg.payload.toHex()}") + val payload = parseKeys(arrayOf(P0), msg.payload)[0] + aapsLogger.debug(LTag.PUMPBTCOMM, "P0 payload from pod: ${payload.toHex()}") + if (!payload.contentEquals(UNKNOWN_P0_PAYLOAD)) { + throw MessageIOException("Invalid P0 payload received") + } } fun generateKeys() { val curveLTK = X25519.computeSharedSecret(pdmPrivate, podPublic) - aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, donna key: ${curveLTK.toHex()}") - //first_key = data.pod_public[-4:] + data.pdm_public[-4:] + data.pod_nonce[-4:] + data.pdm_nonce[-4:] val firstKey = podPublic.copyOfRange(podPublic.size - 4, podPublic.size) + - pdmPublic.copyOfRange(pdmPublic.size - 4, pdmPublic.size)+ - podNonce.copyOfRange(podNonce.size - 4, podNonce.size)+ + pdmPublic.copyOfRange(pdmPublic.size - 4, pdmPublic.size) + + podNonce.copyOfRange(podNonce.size - 4, podNonce.size) + pdmNonce.copyOfRange(pdmNonce.size - 4, pdmNonce.size) aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, first key: ${firstKey.toHex()}") - val aesEngine = AESEngine() - val intermediateMac = CMac(aesEngine) - intermediateMac.init(KeyParameter(firstKey)) - intermediateMac.update(curveLTK, 0, curveLTK.size) val intermediateKey = ByteArray(CMAC_SIZE) - intermediateMac.doFinal(intermediateKey, 0) - aapsLogger.debug(LTag.PUMPBTCOMM, "Intermediate key: ${intermediateKey.toHex()}") + aesCmac(firstKey, curveLTK, intermediateKey) - val ltkMac = CMac(aesEngine) - ltkMac.init(KeyParameter(firstKey)) - ltkMac.update(curveLTK, 0, curveLTK.size) - intermediateMac.doFinal(ltk, 0) - aapsLogger.debug(LTag.PUMPBTCOMM, "LTK: ${ltk.toHex()}") + val ltkData = byteArrayOf(2.toByte()) + + INTERMEDIAR_KEY_MAGIC_STRING + + podNonce + + pdmNonce + + byteArrayOf(0.toByte(), 1.toByte()) + aesCmac(intermediateKey, ltkData, ltk) + val confData = byteArrayOf(1.toByte()) + + INTERMEDIAR_KEY_MAGIC_STRING + + podNonce + + pdmNonce + + byteArrayOf(0.toByte(), 1.toByte()) + val confKey = ByteArray(CMAC_SIZE) + aesCmac(intermediateKey, confData, confKey) + + val pdmConfData = PDM_CONF_MAGIC_PREFIX + + pdmNonce + + podNonce + aesCmac(confKey, pdmConfData, pdmConf) + aapsLogger.debug(LTag.PUMPBTCOMM, "pdmConf: ${pdmConf.toHex()}") + + val podConfData = POD_CONF_MAGIC_PREFIX + + podNonce + + pdmNonce + aesCmac(confKey, podConfData, podConf) + aapsLogger.debug(LTag.PUMPBTCOMM, "podConf: ${podConf.toHex()}") + + if (BuildConfig.DEBUG) { + aapsLogger.debug(LTag.PUMPBTCOMM, "pdmPrivate: ${pdmPrivate.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "pdmPublic: ${pdmPublic.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "podPublic: ${podPublic.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "pdmNonce: ${pdmNonce.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "podNonce: ${podNonce.toHex()}") + + aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, donna key: ${curveLTK.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Intermediate key: ${intermediateKey.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "LTK: ${ltk.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Conf KEY: ${confKey.toHex()}") + } } companion object { @@ -172,7 +228,29 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI private val PUBLIC_KEY_SIZE = 32 private val NONCE_SIZE = 16 private val CONF_SIZE = 16 + private val CMAC_SIZE = 16 + + private val INTERMEDIAR_KEY_MAGIC_STRING = "TWIt".toByteArray() + private val PDM_CONF_MAGIC_PREFIX = "KC_2_U".toByteArray() + private val POD_CONF_MAGIC_PREFIX = "KC_2_V".toByteArray() + private val GET_POD_STATUS_HEX_COMMAND = "ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that. + + private val SP1 = "SP1=" + private val SP2 = ",SP2=" + private val SPS1 = "SPS1=" + private val SPS2 = "SPS2=" + private val SP0GP0 = "SP0,GP0" + private val P0 = "P0=" + private val UNKNOWN_P0_PAYLOAD = byteArrayOf(0xa5.toByte()) } +} + +private fun aesCmac(key: ByteArray, data: ByteArray, result: ByteArray) { + val aesEngine = AESEngine() + val mac = CMac(aesEngine) + mac.init(KeyParameter(key)) + mac.update(data, 0, data.size) + mac.doFinal(result, 0) } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt index 4b67f33167..0af9d00ece 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt @@ -47,7 +47,7 @@ class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) { for (i in 1 until joiner.fullFragments + 1) { joiner.accumulate(bleIO.receivePacket(CharacteristicType.DATA)) } - if (joiner.oneExtra) { + if (joiner.oneExtraPacket) { joiner.accumulate(bleIO.receivePacket(CharacteristicType.DATA)) } val fullPayload = joiner.finalize() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt index 3563745ffe..6a6d800823 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt @@ -76,8 +76,8 @@ data class MessagePacket( if (payload.size < HEADER_SIZE) { throw CouldNotParseMessageException(payload) } - if (payload.copyOfRange(0, 2).toString() != "TW") { - throw info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseMessageException(payload) + if (payload.copyOfRange(0, 2).decodeToString() != MAGIC_PATTERN) { + throw CouldNotParseMessageException(payload) } val f1 = Flag(payload[2].toInt()) val sas = f1.get(3) != 0 @@ -96,7 +96,7 @@ data class MessagePacket( } val sequenceNumber = payload[4] val ackNumber = payload[5] - val size = (payload[6].toInt() shl 3) or (payload[7].toInt() ushr 5) + val size = (payload[6].toInt() shl 3) or (payload[7].toUnsignedInt() ushr 5) if (size + HEADER_SIZE > payload.size) { throw CouldNotParseMessageException(payload) } @@ -114,8 +114,8 @@ data class MessagePacket( sas = sas, tfs = tfs, version = version, - sequenceNumber = payload[4], - ackNumber = payload[5], + sequenceNumber = sequenceNumber, + ackNumber = ackNumber, source = Id(payload.copyOfRange(8, 12)), destination = Id(payload.copyOfRange(12, 16)), payload = payload.copyOfRange(16, payloadEnd), @@ -141,4 +141,6 @@ private class Flag(var value: Int = 0) { return 1 } -} \ No newline at end of file +} + +internal fun Byte.toUnsignedInt() = this.toInt() and 0xff \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt index 9b383b5f22..2a6d772ac5 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/PayloadJoiner.kt @@ -13,14 +13,14 @@ import java.util.* class PayloadJoiner(private val firstPacket: ByteArray) { - var oneExtra: Boolean = false + var oneExtraPacket: Boolean = false val fullFragments: Int var crc: Long = 0 private var expectedIndex = 0 private val fragments: LinkedList = LinkedList() init { - if (firstPacket.size < 2) { + if (firstPacket.size < FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS) { throw IncorrectPacketException(0, firstPacket) } fullFragments = firstPacket[1].toInt() @@ -32,20 +32,20 @@ class PayloadJoiner(private val firstPacket: ByteArray) { fullFragments == 0 -> { crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUnsignedLong() val rest = firstPacket[6] - val end = min(rest + 7, BlePacket.MAX_LEN) - oneExtra = rest + 7 > end - fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, end)) + val end = min(rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, BlePacket.MAX_SIZE) + oneExtraPacket = rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS > end if (end > firstPacket.size) { throw IncorrectPacketException(0, firstPacket) } + fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, end)) } // With middle packets - firstPacket.size < BlePacket.MAX_LEN -> + firstPacket.size < BlePacket.MAX_SIZE -> throw IncorrectPacketException(0, firstPacket) else -> { - fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS, BlePacket.MAX_LEN)) + fragments.add(firstPacket.copyOfRange(FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS, BlePacket.MAX_SIZE)) } } } @@ -61,10 +61,10 @@ class PayloadJoiner(private val firstPacket: ByteArray) { expectedIndex++ when { idx < fullFragments -> { // this is a middle fragment - if (packet.size < BlePacket.MAX_LEN) { + if (packet.size < BlePacket.MAX_SIZE) { throw IncorrectPacketException(idx.toByte(), packet) } - fragments.add(packet.copyOfRange(1, BlePacket.MAX_LEN)) + fragments.add(packet.copyOfRange(1, BlePacket.MAX_SIZE)) } idx == fullFragments -> { // this is the last fragment @@ -73,12 +73,12 @@ class PayloadJoiner(private val firstPacket: ByteArray) { } crc = ByteBuffer.wrap(packet.copyOfRange(2, 6)).int.toUnsignedLong() val rest = packet[1].toInt() - val end = min(rest, BlePacket.MAX_LEN) + val end = min(rest + LastBlePacket.HEADER_SIZE, BlePacket.MAX_SIZE) + oneExtraPacket = rest + LastBlePacket.HEADER_SIZE > end if (packet.size < end) { throw IncorrectPacketException(idx.toByte(), packet) } - oneExtra = rest + LastBlePacket.HEADER_SIZE > end - fragments.add(packet.copyOfRange(LastBlePacket.HEADER_SIZE, BlePacket.MAX_LEN)) + fragments.add(packet.copyOfRange(LastBlePacket.HEADER_SIZE, packet.size)) } idx > fullFragments -> { // this is the extra fragment @@ -106,4 +106,4 @@ class PayloadJoiner(private val firstPacket: ByteArray) { } -private fun Int.toUnsignedLong() = this.toLong() and 0xffffffffL \ No newline at end of file +internal fun Int.toUnsignedLong() = this.toLong() and 0xffffffffL \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt index b23e689d9d..7ec6048aed 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt @@ -1,5 +1,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException +import info.nightscout.androidaps.utils.extensions.toHex import java.nio.ByteBuffer /*** @@ -9,8 +11,33 @@ class StringLengthPrefixEncoding { companion object { - fun parseKeys(keys: List): List { - TODO("not implemented") + private val LENGTH_BYTES = 2 + + fun parseKeys(keys: Array, payload: ByteArray): Array { + val ret = Array(keys.size, { ByteArray(0) }) + var remaining = payload + for ((index, key) in keys.withIndex()) { + when { + remaining.size < key.length -> + throw MessageIOException("Payload too short: ${payload.toHex()} for key: ${key}") + !(remaining.copyOfRange(0, key.length).decodeToString() == key) -> + throw MessageIOException("Key not found: ${key} in ${payload.toHex()}") + // last key can be empty, no length + index == keys.size - 1 && remaining.size == key.length -> + return ret + + remaining.size < key.length + LENGTH_BYTES -> + throw MessageIOException("Length not found: for ${key} in ${payload.toHex()}") + } + remaining = remaining.copyOfRange(key.length, remaining.size) + val length = (remaining[0].toUnsignedInt() shl 1) or remaining[1].toUnsignedInt() + if (length > remaining.size) { + throw MessageIOException("Payload too short, looking for length ${length} for ${key} in ${payload.toHex()}") + } + ret[index] = remaining.copyOfRange(LENGTH_BYTES, LENGTH_BYTES + length) + remaining = remaining.copyOfRange(LENGTH_BYTES + length, remaining.size) + } + return ret } fun formatKeys(keys: Array, payloads: Array): ByteArray { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt index bd67c5a4d3..eb020c5b28 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt @@ -8,8 +8,7 @@ sealed class BlePacket { companion object { - const val MAX_LEN = 20 - const val MAX_BLE_BUFFER_LEN = MAX_LEN + 1 // we use this as the size allocated for the ByteBuffer + const val MAX_SIZE = 20 } } @@ -17,7 +16,7 @@ data class FirstBlePacket(val totalFragments: Byte, val payload: ByteArray, val override fun asByteArray(): ByteArray { val bb = ByteBuffer - .allocate(MAX_BLE_BUFFER_LEN) + .allocate(MAX_SIZE) .put(0) // index .put(totalFragments) // # of fragments except FirstBlePacket and LastOptionalPlusOneBlePacket crc32?.let { @@ -27,9 +26,12 @@ data class FirstBlePacket(val totalFragments: Byte, val payload: ByteArray, val bb.put(size) } bb.put(payload) - val ret = ByteArray(bb.position()) + + val pos = bb.position() + val ret = ByteArray(MAX_SIZE) bb.flip() - bb.get(ret) + bb.get(ret, 0, pos) + return ret } @@ -38,8 +40,8 @@ data class FirstBlePacket(val totalFragments: Byte, val payload: ByteArray, val internal const val HEADER_SIZE_WITHOUT_MIDDLE_PACKETS = 7 // we are using all fields internal const val HEADER_SIZE_WITH_MIDDLE_PACKETS = 2 - internal const val CAPACITY_WITHOUT_MIDDLE_PACKETS = MAX_LEN - HEADER_SIZE_WITHOUT_MIDDLE_PACKETS // we are using all fields - internal const val CAPACITY_WITH_MIDDLE_PACKETS = MAX_LEN - HEADER_SIZE_WITH_MIDDLE_PACKETS // we are not using crc32 or size + internal const val CAPACITY_WITHOUT_MIDDLE_PACKETS = MAX_SIZE - HEADER_SIZE_WITHOUT_MIDDLE_PACKETS // we are using all fields + internal const val CAPACITY_WITH_MIDDLE_PACKETS = MAX_SIZE - HEADER_SIZE_WITH_MIDDLE_PACKETS // we are not using crc32 or size internal const val CAPACITY_WITH_THE_OPTIONAL_PLUS_ONE_PACKET = 18 } } @@ -60,34 +62,34 @@ data class LastBlePacket(val index: Byte, val size: Byte, val payload: ByteArray override fun asByteArray(): ByteArray { val bb = ByteBuffer - .allocate(MAX_BLE_BUFFER_LEN) + .allocate(MAX_SIZE) .put(index) .put(size) .putInt(crc32.toInt()) .put(payload) - val ret = ByteArray(bb.position()) + val pos = bb.position() + val ret = ByteArray(MAX_SIZE) bb.flip() - bb.get(ret) + bb.get(ret, 0, pos) return ret } companion object { internal const val HEADER_SIZE = 6 - internal const val CAPACITY = MAX_LEN - HEADER_SIZE + internal const val CAPACITY = MAX_SIZE - HEADER_SIZE } } data class LastOptionalPlusOneBlePacket(val index: Byte, val payload: ByteArray, val size: Byte) : BlePacket() { override fun asByteArray(): ByteArray { - return byteArrayOf(index, size) + payload + return byteArrayOf(index, size) + payload + ByteArray(MAX_SIZE - payload.size - 2) } companion object { internal const val HEADER_SIZE = 2 - internal const val CAPACITY = MAX_LEN - HEADER_SIZE } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/util/Functions.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/util/Functions.kt index 96d8fbf667..54ad9b345f 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/util/Functions.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/util/Functions.kt @@ -33,7 +33,7 @@ fun mapProfileToBasalProgram(profile: Profile): BasalProgram { if (previousBasalValue != null) { entries.add( BasalProgram.Segment( - (previousBasalValue!!.timeAsSeconds / 1800).toShort(), + (previousBasalValue.timeAsSeconds / 1800).toShort(), startSlotIndex, (PumpType.Omnipod_Dash.determineCorrectBasalSize(previousBasalValue.value) * 100).roundToInt() ) From b1f099c50693d56f0cb0b60eef54b01d35ba03cf Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 28 Feb 2021 18:25:08 +0100 Subject: [PATCH 11/14] rename LTK to Pair --- .../omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt | 2 +- .../omnipod/dash/driver/comm/{ltk => pair}/LTKExchanger.kt | 6 +++--- .../omnipod/dash/driver/comm/{ltk => pair}/PairMessage.kt | 2 +- .../dash/driver/comm/{ltk/LTK.kt => pair/PairResult.kt} | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/{ltk => pair}/LTKExchanger.kt (99%) rename omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/{ltk => pair}/PairMessage.kt (98%) rename omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/{ltk/LTK.kt => pair/PairResult.kt} (66%) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index 50b50d7e07..a658a6d5f1 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -13,7 +13,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command. import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.* import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType -import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk.LTKExchanger +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.LTKExchanger import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.status.ConnectionStatus diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt similarity index 99% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt index 330ae955cf..cddb97bfaf 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair import com.google.crypto.tink.subtle.X25519 import info.nightscout.androidaps.logging.AAPSLogger @@ -37,7 +37,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI random.nextBytes(pdmNonce) } - fun negotiateLTK(): LTK { + fun negotiateLTK(): PairResult { // send SP1, SP2 var sp1sp2 = sp1sp2(nodeId.address, sp2()) msgIO.sendMesssage(sp1sp2.messagePacket) @@ -74,7 +74,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI val p0 = msgIO.receiveMessage() validateP0(p0) - return LTK( + return PairResult( ltk = ltk, ) } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/PairMessage.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairMessage.kt similarity index 98% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/PairMessage.kt rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairMessage.kt index af7fea3adb..f36baea723 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/PairMessage.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairMessage.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt similarity index 66% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt index 06c497ce18..c2bdb921ef 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ltk/LTK.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt @@ -1,6 +1,6 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ltk +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair -data class LTK(val ltk: ByteArray) { +data class PairResult(val ltk: ByteArray) { init { require(ltk.size == 16) } From 57c2d2d9a1fd397b9aa86a511d3fe6ceb3e9ab75 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 28 Feb 2021 19:11:42 +0100 Subject: [PATCH 12/14] Address review comments: - move think dependency to core_dependencies and make it's version configurable: https://github.com/0pen-dash/AndroidAPS/pull/10#discussion_r584329468 - add message string to exceptions. Renames --- build.gradle | 1 + core/core_dependencies.gradle | 2 +- omnipod-dash/build.gradle | 1 - .../omnipod/dash/driver/comm/exceptions/MessageIOException.kt | 2 +- .../omnipod/dash/driver/comm/message/CrcMismatchException.kt | 4 ++-- .../plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt | 4 +++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index a3026de30d..fd870b3ad6 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ buildscript { commonscodec_version = '1.15' jodatime_version = '2.10.10' work_version = '2.5.0' + tink_version = '1.5.0' junit_version = '4.13.2' mockitoVersion = '3.7.7' diff --git a/core/core_dependencies.gradle b/core/core_dependencies.gradle index 25ecf5b36c..e5f68665f6 100644 --- a/core/core_dependencies.gradle +++ b/core/core_dependencies.gradle @@ -46,7 +46,7 @@ dependencies { //CryptoUtil api 'com.madgag.spongycastle:core:1.58.0.0' - + api "com.google.crypto.tink:tink-android:$tink_version" // Graphview cannot be upgraded api "com.jjoe64:graphview:4.0.1" diff --git a/omnipod-dash/build.gradle b/omnipod-dash/build.gradle index 34ca4406c5..bf7d1c99c1 100644 --- a/omnipod-dash/build.gradle +++ b/omnipod-dash/build.gradle @@ -22,5 +22,4 @@ dependencies { implementation "androidx.room:room-rxjava2:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation 'com.github.guepardoapps:kulid:1.1.2.0' - implementation 'com.google.crypto.tink:tink-android:1.5.0' } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt index 30cbac205b..5645cb009b 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt @@ -2,5 +2,5 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.excepti class MessageIOException : Exception { constructor(msg: String) : super(msg) - constructor(cause: Throwable) : super(cause) + constructor(cause: Throwable) : super("Caught Exception during Message I/O", cause) } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/CrcMismatchException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/CrcMismatchException.kt index e844cc48ab..343e82cd52 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/CrcMismatchException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/CrcMismatchException.kt @@ -2,5 +2,5 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message import info.nightscout.androidaps.utils.extensions.toHex -class CrcMismatchException(val expected: Long, val got: Long, val payload: ByteArray) : - Exception("CRC missmatch. Got: ${got}. Expected: ${expected}. Payload: ${payload.toHex()}") \ No newline at end of file +class CrcMismatchException(val expected: Long, val actual: Long, val payload: ByteArray) : + Exception("CRC mismatch. Actual: ${actual}. Expected: ${expected}. Payload: ${payload.toHex()}") \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt index c2bdb921ef..f542e1169d 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt @@ -1,7 +1,9 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair +import info.nightscout.androidaps.utils.extensions.toHex + data class PairResult(val ltk: ByteArray) { init { - require(ltk.size == 16) + require(ltk.size == 16) {"LTK length must be 16 bytes. Received LTK: ${ltk.toHex()}"} } } From a7ca5fd863b0dfbd649af612f36cfde72964ccf1 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 28 Feb 2021 19:44:10 +0100 Subject: [PATCH 13/14] format code: Ctrl+Alt+L --- .../omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt | 4 ++-- .../plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index a658a6d5f1..be03f69525 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -13,15 +13,15 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command. import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.* import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType -import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.LTKExchanger import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.LTKExchanger import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.status.ConnectionStatus import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command +import info.nightscout.androidaps.utils.extensions.toHex import io.reactivex.Observable import org.apache.commons.lang3.NotImplementedException -import info.nightscout.androidaps.utils.extensions.toHex import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.TimeoutException diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt index f542e1169d..2a659e63fd 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/PairResult.kt @@ -4,6 +4,6 @@ import info.nightscout.androidaps.utils.extensions.toHex data class PairResult(val ltk: ByteArray) { init { - require(ltk.size == 16) {"LTK length must be 16 bytes. Received LTK: ${ltk.toHex()}"} + require(ltk.size == 16) { "LTK length must be 16 bytes. Received LTK: ${ltk.toHex()}" } } } From c8bded99ab6c7afff9312b209468da19d24ef92b Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 28 Feb 2021 19:45:21 +0100 Subject: [PATCH 14/14] remove tailing comma --- .../pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt index cddb97bfaf..cc76bd232f 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt @@ -75,7 +75,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI validateP0(p0) return PairResult( - ltk = ltk, + ltk = ltk ) } @@ -88,7 +88,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI sequenceNumber = seq, source = controllerId, destination = nodeId, - payload = payload, + payload = payload ) } @@ -101,7 +101,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI sequenceNumber = seq, source = controllerId, destination = nodeId, - payload = payload, + payload = payload ) } @@ -125,7 +125,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI sequenceNumber = seq, source = controllerId, destination = nodeId, - payload = payload, + payload = payload ) } @@ -156,7 +156,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI sequenceNumber = seq, source = controllerId, destination = nodeId, - payload = payload, + payload = payload ) }