From 5211d4ddc66fcc8a0b062ed130350f546927af5e Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sat, 6 Mar 2021 11:02:21 +0100 Subject: [PATCH] dash ble: implement sessions and sending commands Tested with the fake pod: ``` DEBU[0185] got command. CRC: 82b2. Type: 7 :: GET_VERSION DEBU[0185] got command. CRC: 0003. Type: 3 :: SET_UNIQUE_ID DEBU[0186] got command. CRC: 019a. Type: 19 :: PROGRAM_ALERTS DEBU[0186] got command. CRC: 0385. Type: 19 :: PROGRAM_ALERTS DEBU[0187] got command. CRC: 81f3. Type: 1a :: PROGRAM_INSULIN DEBU[0187] got command. CRC: 8178. Type: e :: GET_STATUS ``` --- .../driver/comm/OmnipodDashBleManagerImpl.kt | 79 ++++++++++----- .../driver/comm/callbacks/BleCommCallbacks.kt | 2 +- .../dash/driver/comm/command/BleCommand.kt | 21 ++-- .../driver/comm/endecrypt/CryptSequence.kt | 14 +-- .../dash/driver/comm/endecrypt/EnDecrypt.kt | 4 +- .../dash/driver/comm/endecrypt/Nonce.kt | 23 +++++ .../exceptions/FailedToConnectException.kt | 3 +- .../pump/omnipod/dash/driver/comm/io/BleIO.kt | 2 +- .../dash/driver/comm/message/MessageIO.kt | 3 +- .../dash/driver/comm/message/MessagePacket.kt | 11 ++- .../dash/driver/comm/message/PayloadJoiner.kt | 10 +- .../message/StringLengthPrefixEncoding.kt | 15 +-- .../dash/driver/comm/pair/LTKExchanger.kt | 10 +- .../dash/driver/comm/pair/PairResult.kt | 2 +- .../driver/comm/session/EapAkaAttribute.kt | 2 +- .../dash/driver/comm/session/EapSqn.kt | 16 ++- .../dash/driver/comm/session/Session.kt | 98 +++++++++++++++++++ .../driver/comm/session/SessionEstablisher.kt | 37 ++++--- .../dash/driver/comm/session/SessionKeys.kt | 6 +- .../omnipod/dash/driver/event/PodEvent.kt | 2 +- .../dash/ui/OmnipodDashOverviewFragment.kt | 9 +- .../action/DashDeactivatePodViewModel.kt | 4 +- 22 files changed, 270 insertions(+), 103 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/Nonce.kt create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.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 aa87822628..6357672cc9 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 @@ -7,22 +7,24 @@ import android.bluetooth.BluetoothProfile import android.content.Context 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.callbacks.BleCommCallbacks import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt 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.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.session.EapSqn +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.Session import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.SessionEstablisher +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.SessionKeys 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 java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.TimeoutException @@ -38,6 +40,8 @@ class OmnipodDashBleManagerImpl @Inject constructor( private val bluetoothManager: BluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter + private var sessionKeys: SessionKeys? = null + private var msgIO: MessageIO? = null @Throws( FailedToConnectException::class, @@ -60,12 +64,8 @@ class OmnipodDashBleManagerImpl @Inject constructor( ) val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets) aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to $podAddress") - var autoConnect = true - if (BuildConfig.DEBUG) { - autoConnect = false - // TODO: remove this in the future - // it's easier to start testing from scratch on each run. - } + val autoConnect = false // TODO: check what to use here + val gatt = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE) bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS) val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT) @@ -81,9 +81,39 @@ class OmnipodDashBleManagerImpl @Inject constructor( return bleIO } - override fun sendCommand(cmd: Command): Observable { - // TODO - return Observable.error(NotImplementedException("sendCommand is not yet implemented")) + override fun sendCommand(cmd: Command): Observable = Observable.create { emitter -> + try { + val keys = sessionKeys + val mIO = msgIO + if (keys == null || mIO == null) { + //TODO handle reconnects + throw Exception("Not connected") + } + emitter.onNext(PodEvent.CommandSending(cmd)) + // TODO switch to RX + emitter.onNext(PodEvent.CommandSent(cmd)) + + val enDecrypt = EnDecrypt( + aapsLogger, + keys.nonce, + keys.ck, + ) + + val session = Session( + aapsLogger = aapsLogger, + msgIO = mIO, + myId = Id.fromInt(CONTROLLER_ID), + podId = Id.fromInt(CONTROLLER_ID).increment(), + sessionKeys = keys, + enDecrypt = enDecrypt + ) + val response = session.sendCommand(cmd) + emitter.onNext(PodEvent.ResponseReceived(response)) + + emitter.onComplete() + } catch (ex: Exception) { + emitter.tryOnError(ex) + } } override fun getStatus(): ConnectionStatus { @@ -107,7 +137,6 @@ class OmnipodDashBleManagerImpl @Inject constructor( // emit PodEvent.AlreadyConnected, complete the observable and return from this method try { - // TODO: this is wrong and I know it aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation") val podScanner = PodScanner(aapsLogger, bluetoothAdapter) @@ -123,24 +152,30 @@ class OmnipodDashBleManagerImpl @Inject constructor( val bleIO = connect(podAddress) emitter.onNext(PodEvent.BluetoothConnected(podAddress)) - val msgIO = MessageIO(aapsLogger, bleIO) - val ltkExchanger = LTKExchanger(aapsLogger, msgIO) + val mIO = MessageIO(aapsLogger, bleIO) + val myId = Id.fromInt(CONTROLLER_ID) + val podId = myId.increment() + + val ltkExchanger = LTKExchanger(aapsLogger, mIO) emitter.onNext(PodEvent.Pairing) val ltk = ltkExchanger.negotiateLTK() - aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}") - emitter.onNext(PodEvent.EstablishingSession) - val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk) - val sessionKeys = eapAkaExchanger.negotiateSessionKeys() - aapsLogger.info(LTag.PUMPCOMM, "CK: ${sessionKeys.ck.toHex()}") - aapsLogger.info(LTag.PUMPCOMM, "noncePrefix: ${sessionKeys.noncePrefix.toHex()}") - aapsLogger.info(LTag.PUMPCOMM, "SQN: ${sessionKeys.sqn.toHex()}") + val eapSqn = EapSqn(1) + aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}") + val eapAkaExchanger = SessionEstablisher(aapsLogger, mIO, ltk, eapSqn) + val keys = eapAkaExchanger.negotiateSessionKeys() + aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}") + aapsLogger.info(LTag.PUMPCOMM, "msgSequenceNumber: ${keys.msgSequenceNumber}") + aapsLogger.info(LTag.PUMPCOMM, "Nonce: ${keys.nonce}") - emitter.onNext(PodEvent.Connected(ltk.podId.toLong())) // TODO supply actual pod id + sessionKeys = keys + msgIO = mIO + + emitter.onNext(PodEvent.Connected(ltk.podId.toLong())) emitter.onComplete() } catch (ex: Exception) { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt index c1d50aa890..72282849bc 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt @@ -30,7 +30,7 @@ class BleCommCallbacks( override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { super.onConnectionStateChange(gatt, status, newState) - aapsLogger.debug(LTag.PUMPBTCOMM, "OnConnectionStateChange discovered with status/state$status/$newState") + aapsLogger.debug(LTag.PUMPBTCOMM, "OnConnectionStateChange with status/state: $status/$newState") if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) { connected.countDown() } 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 e437bd23a9..46c19cf6c5 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 @@ -2,6 +2,15 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command import info.nightscout.androidaps.utils.extensions.toHex +class BleCommandRTS : BleCommand(BleCommandType.RTS) + +class BleCommandCTS : BleCommand(BleCommandType.CTS) + +class BleCommandAbort : BleCommand(BleCommandType.ABORT) + +class BleCommandSuccess : BleCommand(BleCommandType.SUCCESS) + +class BleCommandFail : BleCommand(BleCommandType.FAIL) open class BleCommand(val data: ByteArray) { constructor(type: BleCommandType) : this(byteArrayOf(type.value)) @@ -26,14 +35,4 @@ open class BleCommand(val data: ByteArray) { override fun hashCode(): Int { return data.contentHashCode() } -} - -class BleCommandRTS : BleCommand(BleCommandType.RTS) - -class BleCommandCTS : BleCommand(BleCommandType.CTS) - -class BleCommandAbort : BleCommand(BleCommandType.ABORT) - -class BleCommandSuccess : BleCommand(BleCommandType.SUCCESS) - -class BleCommandFail : BleCommand(BleCommandType.FAIL) +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/CryptSequence.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/CryptSequence.kt index edfc259215..35d638c91c 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/CryptSequence.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/CryptSequence.kt @@ -1,24 +1,16 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt import java.nio.ByteBuffer class CryptSequence(var sqn: Long) { - fun incrementForEapAka():ByteArray { - sqn++ - return ByteBuffer.allocate(8) - .putLong(sqn) - .array() - .copyOfRange(2, 8) - } - - fun incrementForEnDecrypt(podReceiving: Boolean):ByteArray{ + fun incrementForEnDecrypt(fromPdmToPod: Boolean): ByteArray { sqn++ val ret = ByteBuffer.allocate(8) .putLong(sqn) .array() .copyOfRange(3, 8) - if (podReceiving) { + if (fromPdmToPod) { ret[0] = (ret[0].toInt() and 127).toByte() } else { ret[0] = (ret[0].toInt() or 128).toByte() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/EnDecrypt.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/EnDecrypt.kt index a08b79bf8a..0d359b9952 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/EnDecrypt.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/EnDecrypt.kt @@ -18,7 +18,7 @@ class EnDecrypt(private val aapsLogger: AAPSLogger, private val nonce: Nonce, pr val payload = msg.payload val header = msg.asByteArray().copyOfRange(0, 16) - val n = nonce.increment() + val n = nonce.increment(false) aapsLogger.debug(LTag.PUMPBTCOMM, "Decrypt header ${header.toHex()} payload: ${payload.toHex()}") aapsLogger.debug(LTag.PUMPBTCOMM, "Decrypt NONCE ${n.toHex()}") cipher.init( @@ -38,7 +38,7 @@ class EnDecrypt(private val aapsLogger: AAPSLogger, private val nonce: Nonce, pr val payload = headerMessage.payload val header = headerMessage.asByteArray(true).copyOfRange(0, 16) - val n = nonce.increment() + val n = nonce.increment(true) aapsLogger.debug(LTag.PUMPBTCOMM, "Encrypt header ${header.toHex()} payload: ${payload.toHex()}") aapsLogger.debug(LTag.PUMPBTCOMM, "Encrypt NONCE ${n.toHex()}") val encryptedPayload = ByteArray(payload.size + MAC_SIZE) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/Nonce.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/Nonce.kt new file mode 100644 index 0000000000..a9662820cc --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/endecrypt/Nonce.kt @@ -0,0 +1,23 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt + +import java.nio.ByteBuffer + +data class Nonce(val prefix: ByteArray, var sqn: Long) { + init { + require(prefix.size == 8) { "Nonce prefix should be 8 bytes long" } + } + + fun increment(podReceiving: Boolean): ByteArray { + sqn++ + val ret = ByteBuffer.allocate(8) + .putLong(sqn) + .array() + .copyOfRange(3, 8) + if (podReceiving) { + ret[0] = (ret[0].toInt() and 127).toByte() + } else { + ret[0] = (ret[0].toInt() or 128).toByte() + } + return prefix + ret + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt index 4c138d0b2e..07aba43b33 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt @@ -1,6 +1,5 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions open class FailedToConnectException : Exception { - constructor() : super() - constructor(message: String?) : super(message) + constructor(message: String?) : super("Failed to connect: ${message ?: ""}") } 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 f06661da0e..9e6e994aa4 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 @@ -123,6 +123,6 @@ class BleIO( companion object { - private const val DEFAULT_IO_TIMEOUT_MS = 1000 + private const val DEFAULT_IO_TIMEOUT_MS = 10000 } } 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 5c2601311f..5308c88a71 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 @@ -12,7 +12,7 @@ import info.nightscout.androidaps.utils.extensions.toHex class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) { - fun sendMesssage(msg: MessagePacket) { + fun sendMessage(msg: MessagePacket) { bleIO.flushIncomingQueues() bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandRTS().data) val expectCTS = bleIO.receivePacket(CharacteristicType.CMD) @@ -33,7 +33,6 @@ class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) { throw UnexpectedCommandException(BleCommand(expectSuccess)) } // TODO: handle NACKS/FAILS/etc - bleIO.flushIncomingQueues() } fun receiveMessage(): MessagePacket { 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 eba676f388..e4350db605 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 @@ -24,7 +24,7 @@ data class MessagePacket( val version: Short = 0.toShort() ) { - fun asByteArray(): ByteArray { + fun asByteArray(forEncryption: Boolean = false): ByteArray { val bb = ByteBuffer.allocate(16 + payload.size) bb.put(MAGIC_PATTERN.toByteArray()) @@ -52,9 +52,10 @@ data class MessagePacket( bb.put(f2.value.toByte()) bb.put(this.sequenceNumber) bb.put(this.ackNumber) - - bb.put((this.payload.size ushr 3).toByte()) - bb.put((this.payload.size shl 5).toByte()) + val size = payload.size - + if (type == MessageType.ENCRYPTED && !forEncryption) 8 else 0 + bb.put((size ushr 3).toByte()) + bb.put((size shl 5).toByte()) bb.put(this.source.address) bb.put(this.destination.address) @@ -103,7 +104,7 @@ data class MessagePacket( throw CouldNotParseMessageException(payload) } val payloadEnd = 16 + size + - if (type == MessageType.ENCRYPTED) 8 + if (type == MessageType.ENCRYPTED) 8 // TAG else 0 return MessagePacket( 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 2cc75907c1..983a60d374 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 @@ -29,7 +29,7 @@ class PayloadJoiner(private val firstPacket: ByteArray) { firstPacket.size < FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS -> throw IncorrectPacketException(0, firstPacket) - fullFragments == 0 -> { + fullFragments == 0 -> { crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUnsignedLong() val rest = firstPacket[6] val end = min(rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, BlePacket.MAX_SIZE) @@ -41,10 +41,10 @@ class PayloadJoiner(private val firstPacket: ByteArray) { } // With middle packets - firstPacket.size < BlePacket.MAX_SIZE -> + firstPacket.size < BlePacket.MAX_SIZE -> throw IncorrectPacketException(0, firstPacket) - else -> { + else -> { fragments.add( firstPacket.copyOfRange( FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS, @@ -65,7 +65,7 @@ class PayloadJoiner(private val firstPacket: ByteArray) { } expectedIndex++ when { - idx < fullFragments -> { // this is a middle fragment + idx < fullFragments -> { // this is a middle fragment if (packet.size < BlePacket.MAX_SIZE) { throw IncorrectPacketException(idx.toByte(), packet) } @@ -86,7 +86,7 @@ class PayloadJoiner(private val firstPacket: ByteArray) { fragments.add(packet.copyOfRange(LastBlePacket.HEADER_SIZE, packet.size)) } - idx > fullFragments -> { // this is the extra fragment + idx > fullFragments -> { // this is the extra fragment val size = packet[1].toInt() if (packet.size < LastOptionalPlusOneBlePacket.HEADER_SIZE + size) { throw IncorrectPacketException(idx.toByte(), packet) 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 786a210bfb..efc4d63723 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 @@ -18,15 +18,15 @@ class StringLengthPrefixEncoding { var remaining = payload for ((index, key) in keys.withIndex()) { when { - remaining.size < key.length -> + 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 -> + index == keys.size - 1 && remaining.size == key.length -> return ret - remaining.size < key.length + LENGTH_BYTES -> + remaining.size < key.length + LENGTH_BYTES -> throw MessageIOException("Length not found: for $key in ${payload.toHex()}") } remaining = remaining.copyOfRange(key.length, remaining.size) @@ -43,14 +43,17 @@ class StringLengthPrefixEncoding { fun formatKeys(keys: Array, payloads: Array): ByteArray { val payloadTotalSize = payloads.fold(0) { acc, i -> acc + i.size } val keyTotalSize = keys.fold(0) { acc, i -> acc + i.length } + val zeros = payloads.fold(0) { acc, i -> acc + if (i.size == 0) 1 else 0 } - val bb = ByteBuffer.allocate(2 * keys.size + keyTotalSize + payloadTotalSize) + val bb = ByteBuffer.allocate(2 * (keys.size - zeros) + keyTotalSize + payloadTotalSize) for (idx in keys.indices) { val k = keys[idx] val payload = payloads[idx] bb.put(k.toByteArray()) - bb.putShort(payload.size.toShort()) - bb.put(payload) + if (payload.size > 0) { + bb.putShort(payload.size.toShort()) + bb.put(payload) + } } val ret = ByteArray(bb.position()) 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 4e586ba581..3cab3565a7 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 @@ -40,11 +40,11 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI fun negotiateLTK(): PairResult { // send SP1, SP2 val sp1sp2 = sp1sp2(nodeId.address, sp2()) - msgIO.sendMesssage(sp1sp2.messagePacket) + msgIO.sendMessage(sp1sp2.messagePacket) seq++ val sps1 = sps1() - msgIO.sendMesssage(sps1.messagePacket) + msgIO.sendMessage(sps1.messagePacket) // send SPS1 // read SPS1 @@ -55,7 +55,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI seq++ // send SPS2 val sps2 = sps2() - msgIO.sendMesssage(sps2.messagePacket) + msgIO.sendMessage(sps2.messagePacket) // read SPS2 val podSps2 = msgIO.receiveMessage() @@ -63,7 +63,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI seq++ // send SP0GP0 - msgIO.sendMesssage(sp0gp0().messagePacket) + msgIO.sendMessage(sp0gp0().messagePacket) // read P0 // TODO: failing to read or validate p0 will lead to undefined state @@ -77,7 +77,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI return PairResult( ltk = ltk, podId = nodeId, - seq = seq + msgSeq = seq, ) } 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 80aed263ab..c6d087f38d 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 @@ -3,7 +3,7 @@ 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.utils.extensions.toHex -data class PairResult(val ltk: ByteArray, val podId: Id, val seq: Byte) { +data class PairResult(val ltk: ByteArray, val podId: Id, val msgSeq: Byte) { init { require(ltk.size == 16) { "LTK length must be 16 bytes. Received LTK: ${ltk.toHex()}" } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaAttribute.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaAttribute.kt index ad193b87b8..726d567151 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaAttribute.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaAttribute.kt @@ -44,7 +44,7 @@ sealed class EapAkaAttribute { ret.add(EapAkaAttributeRes.parse(tail.copyOfRange(2, size))) EapAkaAttributeType.AT_CUSTOM_IV -> ret.add(EapAkaAttributeCustomIV.parse(tail.copyOfRange(2, size))) - else -> + else -> throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}. Expecting only AT_RES or CUSTOM_IV attribute types from the POD") } tail = tail.copyOfRange(size, tail.size) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt index 1861167a50..ef1b4bdc11 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt @@ -1,4 +1,18 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session -class EapSqn { +import java.nio.ByteBuffer + +/*** + * Eap-Aka start session sequence. + * Incremented for each new session + */ +class EapSqn(var sqn: Long) { + + fun increment(): ByteArray { + sqn++ + return ByteBuffer.allocate(8) + .putLong(sqn) + .array() + .copyOfRange(2, 8) + } } \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt new file mode 100644 index 0000000000..af4f6d0c27 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt @@ -0,0 +1,98 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session + +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.endecrypt.EnDecrypt +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.MessageType +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.plugins.pump.omnipod.dash.driver.pod.command.base.Command +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response +import info.nightscout.androidaps.utils.extensions.toHex + +class Session( + private val aapsLogger: AAPSLogger, + private val msgIO: MessageIO, + private val myId: Id, + private val podId: Id, + val sessionKeys: SessionKeys, + val enDecrypt: EnDecrypt +) { + + /** + * Used for commands: + * -> command with retries + * <- response, ACK TODO: retries? + * -> ACK + */ + fun sendCommand(cmd: Command): Response { + sessionKeys.msgSequenceNumber++ + aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${cmd.encoded.toHex()}") + + val msg = getCmdMessage(cmd) + aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}") + msgIO.sendMessage(msg) + + val responseMsg = msgIO.receiveMessage() + val decrypted = enDecrypt.decrypt(responseMsg) + aapsLogger.debug(LTag.PUMPBTCOMM, "Received response: $decrypted") + val response = parseResponse(decrypted) + + sessionKeys.msgSequenceNumber++ + val ack = getAck(responseMsg) + aapsLogger.debug(LTag.PUMPBTCOMM, "Sending ACK: ${ack.payload.toHex()}") + msgIO.sendMessage(ack) + return response + } + + private fun parseResponse(decrypted: MessagePacket): Response { + val payload = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0] + return NakResponse(payload) + } + + private fun getAck(response: MessagePacket): MessagePacket { + val msg = MessagePacket( + type = MessageType.ENCRYPTED, + sequenceNumber = sessionKeys.msgSequenceNumber, + source = myId, + destination = podId, + payload = ByteArray(0), + eqos = 1, + ack = true, + ackNumber = response.sequenceNumber.inc(), + ) + return enDecrypt.encrypt((msg)) + } + + private fun getCmdMessage(cmd: Command): MessagePacket { + val wrapped = StringLengthPrefixEncoding.formatKeys( + arrayOf(COMMAND_PREFIX, COMMAND_SUFFIX), + arrayOf(cmd.encoded, ByteArray(0)) + ) + + aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${wrapped.toHex()}") + + val msg = MessagePacket( + type = MessageType.ENCRYPTED, + sequenceNumber = sessionKeys.msgSequenceNumber, + source = myId, + destination = podId, + payload = wrapped, + eqos = 1 + ) + + return enDecrypt.encrypt(msg) + } + + companion object { + + private const val COMMAND_PREFIX = "S0.0=" + private const val COMMAND_SUFFIX = ",G0.0" + private const val RESPONSE_PREFIX = "0.0=" + + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt index e4cda0594b..6677742ba8 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt @@ -4,25 +4,29 @@ 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.endecrypt.Nonce import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.SessionEstablishmentException 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.MessageType import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult import info.nightscout.androidaps.utils.extensions.toHex -import org.spongycastle.util.encoders.Hex import java.security.SecureRandom -class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, private val ltk: PairResult) { +class SessionEstablisher( + private val aapsLogger: AAPSLogger, + private val msgIO: MessageIO, + private val ltk: PairResult, + private val eapSqn: EapSqn +) { - var seq = ltk.seq + var sequenceNumber = ltk.msgSeq private val controllerIV = ByteArray(IV_SIZE) private var nodeIV = ByteArray(IV_SIZE) private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) - private val sqn = byteArrayOf(0, 0, 0, 0, 0, 2) - private val milenage = Milenage(aapsLogger, ltk.ltk, sqn) + private val milenage = Milenage(aapsLogger, ltk.ltk, eapSqn.increment()) init { aapsLogger.debug(LTag.PUMPBTCOMM, "Starting EAP-AKA") @@ -32,21 +36,24 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO: fun negotiateSessionKeys(): SessionKeys { // send EAP-AKA challenge - seq++ //TODO: get from pod state. This only works for activating a new pod + sequenceNumber++ //TODO: get from pod state. This only works for activating a new pod var challenge = eapAkaChallenge() - msgIO.sendMesssage(challenge) + msgIO.sendMessage(challenge) val challengeResponse = msgIO.receiveMessage() processChallengeResponse(challengeResponse) //TODO: what do we have to answer if challenge response does not validate? - seq++ + sequenceNumber++ var success = eapSuccess() - msgIO.sendMesssage(success) + msgIO.sendMessage(success) return SessionKeys( - ck=milenage.ck, - noncePrefix = controllerIV + nodeIV, - sqn=sqn + ck = milenage.ck, + nonce = Nonce( + prefix = controllerIV + nodeIV, + sqn = 0 + ), + msgSequenceNumber = sequenceNumber ) } @@ -64,7 +71,7 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO: ) return MessagePacket( type = MessageType.SESSION_ESTABLISHMENT, - sequenceNumber = seq, + sequenceNumber = sequenceNumber, source = controllerId, destination = ltk.podId, payload = eapMsg.toByteArray() @@ -86,7 +93,7 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO: } is EapAkaAttributeCustomIV -> nodeIV = attr.payload.copyOfRange(0, IV_SIZE) - else -> + else -> throw SessionEstablishmentException("Unknown attribute received: $attr") } } @@ -101,7 +108,7 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO: return MessagePacket( type = MessageType.SESSION_ESTABLISHMENT, - sequenceNumber = seq, + sequenceNumber = sequenceNumber, source = controllerId, destination = ltk.podId, payload = eapMsg.toByteArray() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt index e321be9c91..e4fb6a04d5 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt @@ -1,9 +1,9 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session -data class SessionKeys(val ck: ByteArray, val noncePrefix: ByteArray, val sqn: ByteArray) { +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.Nonce + +data class SessionKeys(val ck: ByteArray, val nonce: Nonce, var msgSequenceNumber: Byte) { init { require(ck.size == 16) { "CK has to be 16 bytes long" } - require(noncePrefix.size == 8) { "noncePrefix has to be 8 bytes long" } - require(sqn.size == 6) { "SQN has to be 6 bytes long" } } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt index b0429dd1d5..75775945c7 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt @@ -18,5 +18,5 @@ sealed class PodEvent { /* Message exchange events */ class CommandSending(val command: Command) : PodEvent() class CommandSent(val command: Command) : PodEvent() - class ResponseReceived(val response: Response) : PodEvent() + class ResponseReceived(val response:Response) : PodEvent() } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt index 66fbfbf27a..275e995c91 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt @@ -324,13 +324,8 @@ class OmnipodDashOverviewFragment : DaggerFragment() { ) } - podInfoBinding.podActiveAlerts.text = if (podStateManager.activeAlerts!!.size > 0) { - // TODO - // TextUtils.join(System.lineSeparator(), omnipodUtil.getTranslatedActiveAlerts(podStateManager)) - "TODO" - } else { - PLACEHOLDER - } + podInfoBinding.podActiveAlerts.text = PLACEHOLDER + } if (errors.size == 0) { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/deactivation/viewmodel/action/DashDeactivatePodViewModel.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/deactivation/viewmodel/action/DashDeactivatePodViewModel.kt index 9d2ad3a869..8a6ec92571 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/deactivation/viewmodel/action/DashDeactivatePodViewModel.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/deactivation/viewmodel/action/DashDeactivatePodViewModel.kt @@ -6,16 +6,18 @@ import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.plugins.pump.omnipod.common.R import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.deactivation.viewmodel.action.DeactivatePodViewModel +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager import io.reactivex.Single import javax.inject.Inject class DashDeactivatePodViewModel @Inject constructor( + private val omnipodManager: OmnipodDashManager, injector: HasAndroidInjector, logger: AAPSLogger ) : DeactivatePodViewModel(injector, logger) { override fun doExecuteAction(): Single = Single.just( - PumpEnactResult(injector).success(false).comment("TODO") + PumpEnactResult(injector).success(true).comment("TODO") ) // TODO override fun discardPod() {