Merge pull request #19 from 0pen-dash/andrei/encryption
andrei/encryption
This commit is contained in:
commit
034cbef215
31 changed files with 628 additions and 165 deletions
|
@ -49,7 +49,8 @@ class OmnipodDashManagerImpl @Inject constructor(
|
||||||
|
|
||||||
private val observeConnectToPod: Observable<PodEvent>
|
private val observeConnectToPod: Observable<PodEvent>
|
||||||
get() = Observable.defer {
|
get() = Observable.defer {
|
||||||
bleManager.connect().retryWithBackoff(retries = 2, delay = 3, timeUnit = TimeUnit.SECONDS)
|
bleManager.connect()
|
||||||
|
|
||||||
} // TODO are these reasonable values?
|
} // TODO are these reasonable values?
|
||||||
|
|
||||||
private fun observeSendProgramBolusCommand(
|
private fun observeSendProgramBolusCommand(
|
||||||
|
|
|
@ -36,5 +36,8 @@ data class Id(val address: ByteArray) {
|
||||||
fun fromInt(v: Int): Id {
|
fun fromInt(v: Int): Id {
|
||||||
return Id(ByteBuffer.allocate(4).putInt(v).array())
|
return Id(ByteBuffer.allocate(4).putInt(v).array())
|
||||||
}
|
}
|
||||||
|
fun fromLong(v: Long): Id {
|
||||||
|
return Id(ByteBuffer.allocate(8).putLong(v).array().copyOfRange(4, 8))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -10,19 +11,22 @@ import info.nightscout.androidaps.logging.LTag
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig
|
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.callbacks.BleCommCallbacks
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello
|
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.exceptions.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO
|
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.io.CharacteristicType
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
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.pair.LTKExchanger
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner
|
||||||
|
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.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.comm.status.ConnectionStatus
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
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.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import org.apache.commons.lang3.NotImplementedException
|
|
||||||
import java.util.concurrent.BlockingQueue
|
import java.util.concurrent.BlockingQueue
|
||||||
import java.util.concurrent.LinkedBlockingDeque
|
import java.util.concurrent.LinkedBlockingDeque
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
|
@ -32,12 +36,17 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class OmnipodDashBleManagerImpl @Inject constructor(
|
class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val aapsLogger: AAPSLogger
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val podState: OmnipodDashPodStateManager
|
||||||
) : OmnipodDashBleManager {
|
) : OmnipodDashBleManager {
|
||||||
|
|
||||||
private val bluetoothManager: BluetoothManager =
|
private val bluetoothManager: BluetoothManager =
|
||||||
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
||||||
|
private var sessionKeys: SessionKeys? = null
|
||||||
|
private var msgIO: MessageIO? = null
|
||||||
|
private var gatt: BluetoothGatt? = null
|
||||||
|
private var status: ConnectionStatus = ConnectionStatus.IDLE
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
FailedToConnectException::class,
|
FailedToConnectException::class,
|
||||||
|
@ -50,44 +59,73 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
DescriptorNotFoundException::class,
|
DescriptorNotFoundException::class,
|
||||||
CouldNotConfirmDescriptorWriteException::class
|
CouldNotConfirmDescriptorWriteException::class
|
||||||
)
|
)
|
||||||
private fun connect(podAddress: String): BleIO {
|
private fun connect(podDevice: BluetoothDevice): BleIO {
|
||||||
// TODO: locking?
|
|
||||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
|
||||||
val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>> =
|
val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>> =
|
||||||
mapOf(
|
mapOf(
|
||||||
CharacteristicType.CMD to LinkedBlockingDeque(),
|
CharacteristicType.CMD to LinkedBlockingDeque(),
|
||||||
CharacteristicType.DATA to LinkedBlockingDeque()
|
CharacteristicType.DATA to LinkedBlockingDeque()
|
||||||
)
|
)
|
||||||
val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets)
|
val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets)
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to $podAddress")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}")
|
||||||
var autoConnect = true
|
val autoConnect = false // TODO: check what to use here
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
autoConnect = false
|
val gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
|
||||||
// TODO: remove this in the future
|
|
||||||
// it's easier to start testing from scratch on each run.
|
|
||||||
}
|
|
||||||
val gatt = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
|
|
||||||
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS)
|
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS)
|
||||||
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
||||||
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
|
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
|
||||||
throw FailedToConnectException(podAddress)
|
throw FailedToConnectException(podDevice.address)
|
||||||
}
|
}
|
||||||
val discoverer = ServiceDiscoverer(aapsLogger, gatt, bleCommCallbacks)
|
val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks)
|
||||||
val chars = discoverer.discoverServices()
|
val chars = discoverer.discoverServices()
|
||||||
val bleIO = BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks)
|
val bleIO = BleIO(aapsLogger, chars, incomingPackets, gattConnection, bleCommCallbacks)
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).data)
|
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).data)
|
||||||
bleIO.readyToRead()
|
bleIO.readyToRead()
|
||||||
|
gatt = gattConnection
|
||||||
return bleIO
|
return bleIO
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendCommand(cmd: Command): Observable<PodEvent> {
|
override fun sendCommand(cmd: Command): Observable<PodEvent> = Observable.create { emitter ->
|
||||||
// TODO
|
try {
|
||||||
return Observable.error(NotImplementedException("sendCommand is not yet implemented"))
|
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 {
|
override fun getStatus(): ConnectionStatus {
|
||||||
TODO("not implemented")
|
var s: ConnectionStatus
|
||||||
|
synchronized(status) {
|
||||||
|
s = status
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
|
@ -102,12 +140,12 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
DescriptorNotFoundException::class,
|
DescriptorNotFoundException::class,
|
||||||
CouldNotConfirmDescriptorWriteException::class
|
CouldNotConfirmDescriptorWriteException::class
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun connect(): Observable<PodEvent> = Observable.create { emitter ->
|
override fun connect(): Observable<PodEvent> = Observable.create { emitter ->
|
||||||
// TODO: when we are already connected,
|
// TODO: when we are already connected,
|
||||||
// emit PodEvent.AlreadyConnected, complete the observable and return from this method
|
// emit PodEvent.AlreadyConnected, complete the observable and return from this method
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: this is wrong and I know it
|
if (podState.bluetoothAddress == null) {
|
||||||
aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation")
|
aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation")
|
||||||
|
|
||||||
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
|
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
|
||||||
|
@ -118,38 +156,75 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
PodScanner.POD_ID_NOT_ACTIVATED
|
PodScanner.POD_ID_NOT_ACTIVATED
|
||||||
).scanResult.device.address
|
).scanResult.device.address
|
||||||
// For tests: this.podAddress = "B8:27:EB:1D:7E:BB";
|
// For tests: this.podAddress = "B8:27:EB:1D:7E:BB";
|
||||||
|
podState.bluetoothAddress = podAddress
|
||||||
|
}
|
||||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||||
|
val podAddress = podState.bluetoothAddress ?: throw FailedToConnectException("Lost connection")
|
||||||
|
// check if already connected
|
||||||
|
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||||
|
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
||||||
|
|
||||||
val bleIO = connect(podAddress)
|
|
||||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||||
|
if (connectionState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
podState.uniqueId ?: throw FailedToConnectException("Already connection and uniqueId is missing")
|
||||||
|
emitter.onNext(PodEvent.AlreadyConnected(podAddress, podState.uniqueId ?: 0))
|
||||||
|
emitter.onComplete()
|
||||||
|
return@create
|
||||||
|
}
|
||||||
|
if (msgIO != null) {
|
||||||
|
disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
val msgIO = MessageIO(aapsLogger, bleIO)
|
val bleIO = connect(podDevice)
|
||||||
val ltkExchanger = LTKExchanger(aapsLogger, msgIO)
|
val mIO = MessageIO(aapsLogger, bleIO)
|
||||||
|
val myId = Id.fromInt(CONTROLLER_ID)
|
||||||
|
val podId = myId.increment()
|
||||||
|
var msgSeq = 1.toByte()
|
||||||
|
val ltkExchanger = LTKExchanger(aapsLogger, mIO, myId, podId, Id.fromLong(PodScanner.POD_ID_NOT_ACTIVATED))
|
||||||
|
if (podState.ltk == null) {
|
||||||
emitter.onNext(PodEvent.Pairing)
|
emitter.onNext(PodEvent.Pairing)
|
||||||
|
val pairResult = ltkExchanger.negotiateLTK()
|
||||||
|
podState.ltk = pairResult.ltk
|
||||||
|
podState.uniqueId = podId.toLong()
|
||||||
|
msgSeq = pairResult.msgSeq
|
||||||
|
podState.eapAkaSequenceNumber = 1
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${pairResult.ltk.toHex()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val ltk = ltkExchanger.negotiateLTK()
|
val ltk: ByteArray = podState.ltk!!
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}")
|
|
||||||
|
|
||||||
emitter.onNext(PodEvent.EstablishingSession)
|
emitter.onNext(PodEvent.EstablishingSession)
|
||||||
|
val eapSqn = podState.increaseEapAkaSequenceNumber()
|
||||||
|
val eapAkaExchanger = SessionEstablisher(aapsLogger, mIO, ltk, eapSqn, myId, podId, msgSeq)
|
||||||
|
val keys = eapAkaExchanger.negotiateSessionKeys()
|
||||||
|
podState.commitEapAkaSequenceNumber()
|
||||||
|
|
||||||
val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk)
|
if (BuildConfig.DEBUG) {
|
||||||
val sessionKeys = eapAkaExchanger.negotiateSessionKeys()
|
aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}")
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "CK: ${sessionKeys.ck.toHex()}")
|
aapsLogger.info(LTag.PUMPCOMM, "msgSequenceNumber: ${keys.msgSequenceNumber}")
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "noncePrefix: ${sessionKeys.noncePrefix.toHex()}")
|
aapsLogger.info(LTag.PUMPCOMM, "Nonce: ${keys.nonce}")
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "SQN: ${sessionKeys.sqn.toHex()}")
|
}
|
||||||
|
sessionKeys = keys
|
||||||
|
msgIO = mIO
|
||||||
|
|
||||||
emitter.onNext(PodEvent.Connected(ltk.podId.toLong())) // TODO supply actual pod id
|
emitter.onNext(PodEvent.Connected(podId.toLong()))
|
||||||
|
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
|
disconnect()
|
||||||
emitter.tryOnError(ex)
|
emitter.tryOnError(ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect() {
|
override fun disconnect() {
|
||||||
TODO("not implemented")
|
val localGatt = gatt
|
||||||
|
localGatt?.close() // TODO: use disconnect?
|
||||||
|
gatt = null
|
||||||
|
msgIO = null
|
||||||
|
sessionKeys = null
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -30,7 +30,7 @@ class BleCommCallbacks(
|
||||||
|
|
||||||
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||||
super.onConnectionStateChange(gatt, status, newState)
|
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) {
|
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
connected.countDown()
|
connected.countDown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,16 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
|
||||||
|
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
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) {
|
open class BleCommand(val data: ByteArray) {
|
||||||
|
|
||||||
constructor(type: BleCommandType) : this(byteArrayOf(type.value))
|
constructor(type: BleCommandType) : this(byteArrayOf(type.value))
|
||||||
|
@ -26,14 +36,5 @@ open class BleCommand(val data: ByteArray) {
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return data.contentHashCode()
|
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)
|
|
||||||
|
|
|
@ -7,9 +7,7 @@ enum class BleCommandType(val value: Byte) {
|
||||||
ABORT(0x03.toByte()),
|
ABORT(0x03.toByte()),
|
||||||
SUCCESS(0x04.toByte()),
|
SUCCESS(0x04.toByte()),
|
||||||
FAIL(0x05.toByte()),
|
FAIL(0x05.toByte()),
|
||||||
HELLO(
|
HELLO(0x06.toByte());
|
||||||
0x06.toByte()
|
|
||||||
);
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
||||||
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import org.spongycastle.crypto.engines.AESEngine
|
||||||
|
import org.spongycastle.crypto.modes.CCMBlockCipher
|
||||||
|
import org.spongycastle.crypto.params.AEADParameters
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter
|
||||||
|
|
||||||
|
class EnDecrypt(private val aapsLogger: AAPSLogger, private val nonce: Nonce, private val ck: ByteArray) {
|
||||||
|
|
||||||
|
val engine = AESEngine()
|
||||||
|
val cipher = CCMBlockCipher(engine)
|
||||||
|
|
||||||
|
fun decrypt(msg: MessagePacket): MessagePacket {
|
||||||
|
val payload = msg.payload
|
||||||
|
val header = msg.asByteArray().copyOfRange(0, 16)
|
||||||
|
|
||||||
|
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(
|
||||||
|
false,
|
||||||
|
AEADParameters(
|
||||||
|
KeyParameter(ck),
|
||||||
|
MAC_SIZE * 8, // in bits
|
||||||
|
n,
|
||||||
|
header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val decryptedPayload = ByteArray(payload.size - MAC_SIZE)
|
||||||
|
cipher.processPacket(payload, 0, payload.size, decryptedPayload, 0)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Decrypted payload ${decryptedPayload.toHex()}")
|
||||||
|
return msg.copy(payload = decryptedPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encrypt(headerMessage: MessagePacket): MessagePacket {
|
||||||
|
val payload = headerMessage.payload
|
||||||
|
val header = headerMessage.asByteArray(true).copyOfRange(0, 16)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
cipher.init(
|
||||||
|
true,
|
||||||
|
AEADParameters(
|
||||||
|
KeyParameter(ck),
|
||||||
|
MAC_SIZE * 8, // in bits
|
||||||
|
n,
|
||||||
|
header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cipher.processPacket(payload, 0, payload.size, encryptedPayload, 0)
|
||||||
|
|
||||||
|
return headerMessage.copy(payload = encryptedPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val MAC_SIZE = 8
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
open class FailedToConnectException : Exception {
|
open class FailedToConnectException : Exception {
|
||||||
constructor() : super()
|
constructor(message: String?) : super("Failed to connect: ${message ?: ""}")
|
||||||
constructor(message: String?) : super(message)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,6 @@ class BleIO(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val DEFAULT_IO_TIMEOUT_MS = 1000
|
private const val DEFAULT_IO_TIMEOUT_MS = 10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,34 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptio
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO
|
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.io.CharacteristicType
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.PayloadJoiner
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.PayloadJoiner
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.CommandType
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
|
||||||
class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) {
|
class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) {
|
||||||
|
|
||||||
fun sendMesssage(msg: MessagePacket) {
|
private fun expectCommandType(actual: BleCommand, expected: BleCommand) {
|
||||||
|
if (actual.data.isEmpty()) {
|
||||||
|
throw UnexpectedCommandException(actual)
|
||||||
|
}
|
||||||
|
// first byte is the command type
|
||||||
|
if (actual.data[0] == expected.data[0]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw UnexpectedCommandException(actual)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessage(msg: MessagePacket):MessagePacket? {
|
||||||
bleIO.flushIncomingQueues()
|
bleIO.flushIncomingQueues()
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandRTS().data)
|
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandRTS().data)
|
||||||
val expectCTS = bleIO.receivePacket(CharacteristicType.CMD)
|
val expectCTS = bleIO.receivePacket(CharacteristicType.CMD)
|
||||||
if (BleCommand(expectCTS) != BleCommandCTS()) {
|
if (expectCTS.isEmpty()) {
|
||||||
throw UnexpectedCommandException(BleCommand(expectCTS))
|
throw UnexpectedCommandException(BleCommand(expectCTS))
|
||||||
}
|
}
|
||||||
|
//if (expectCTS[0] == BleCommandType.RTS.value) {
|
||||||
|
//the pod is trying to send something, after we sent RTS, let's read it
|
||||||
|
//}
|
||||||
|
expectCommandType(BleCommand(expectCTS), BleCommandCTS())
|
||||||
val payload = msg.asByteArray()
|
val payload = msg.asByteArray()
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending message: ${payload.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending message: ${payload.toHex()}")
|
||||||
val splitter = PayloadSplitter(payload)
|
val splitter = PayloadSplitter(payload)
|
||||||
|
@ -29,18 +46,18 @@ class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) {
|
||||||
}
|
}
|
||||||
// TODO: peek for NACKs
|
// TODO: peek for NACKs
|
||||||
val expectSuccess = bleIO.receivePacket(CharacteristicType.CMD)
|
val expectSuccess = bleIO.receivePacket(CharacteristicType.CMD)
|
||||||
if (BleCommand(expectSuccess) != BleCommandSuccess()) {
|
expectCommandType(BleCommand(expectSuccess), BleCommandSuccess())
|
||||||
throw UnexpectedCommandException(BleCommand(expectSuccess))
|
|
||||||
}
|
|
||||||
// TODO: handle NACKS/FAILS/etc
|
// TODO: handle NACKS/FAILS/etc
|
||||||
bleIO.flushIncomingQueues()
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun receiveMessage(): MessagePacket {
|
// TODO: use higher timeout when receiving the first packet in a message
|
||||||
val expectRTS = bleIO.receivePacket(CharacteristicType.CMD)
|
fun receiveMessage( firstCmd: ByteArray? = null): MessagePacket {
|
||||||
if (BleCommand(expectRTS) != BleCommandRTS()) {
|
var expectRTS = firstCmd
|
||||||
throw UnexpectedCommandException(BleCommand(expectRTS))
|
if (expectRTS == null) {
|
||||||
|
expectRTS = bleIO.receivePacket(CharacteristicType.CMD)
|
||||||
}
|
}
|
||||||
|
expectCommandType(BleCommand(expectRTS), BleCommandRTS())
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandCTS().data)
|
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandCTS().data)
|
||||||
try {
|
try {
|
||||||
val joiner = PayloadJoiner(bleIO.receivePacket(CharacteristicType.DATA))
|
val joiner = PayloadJoiner(bleIO.receivePacket(CharacteristicType.DATA))
|
||||||
|
|
|
@ -24,7 +24,7 @@ data class MessagePacket(
|
||||||
val version: Short = 0.toShort()
|
val version: Short = 0.toShort()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun asByteArray(): ByteArray {
|
fun asByteArray(forEncryption: Boolean = false): ByteArray {
|
||||||
val bb = ByteBuffer.allocate(16 + payload.size)
|
val bb = ByteBuffer.allocate(16 + payload.size)
|
||||||
bb.put(MAGIC_PATTERN.toByteArray())
|
bb.put(MAGIC_PATTERN.toByteArray())
|
||||||
|
|
||||||
|
@ -52,9 +52,10 @@ data class MessagePacket(
|
||||||
bb.put(f2.value.toByte())
|
bb.put(f2.value.toByte())
|
||||||
bb.put(this.sequenceNumber)
|
bb.put(this.sequenceNumber)
|
||||||
bb.put(this.ackNumber)
|
bb.put(this.ackNumber)
|
||||||
|
val size = payload.size -
|
||||||
bb.put((this.payload.size ushr 3).toByte())
|
if (type == MessageType.ENCRYPTED && !forEncryption) 8 else 0
|
||||||
bb.put((this.payload.size shl 5).toByte())
|
bb.put((size ushr 3).toByte())
|
||||||
|
bb.put((size shl 5).toByte())
|
||||||
|
|
||||||
bb.put(this.source.address)
|
bb.put(this.source.address)
|
||||||
bb.put(this.destination.address)
|
bb.put(this.destination.address)
|
||||||
|
@ -103,7 +104,7 @@ data class MessagePacket(
|
||||||
throw CouldNotParseMessageException(payload)
|
throw CouldNotParseMessageException(payload)
|
||||||
}
|
}
|
||||||
val payloadEnd = 16 + size +
|
val payloadEnd = 16 + size +
|
||||||
if (type == MessageType.ENCRYPTED) 8
|
if (type == MessageType.ENCRYPTED) 8 // TAG
|
||||||
else 0
|
else 0
|
||||||
|
|
||||||
return MessagePacket(
|
return MessagePacket(
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PayloadJoiner(private val firstPacket: ByteArray) {
|
||||||
fullFragments == 0 -> {
|
fullFragments == 0 -> {
|
||||||
crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUnsignedLong()
|
crc = ByteBuffer.wrap(firstPacket.copyOfRange(2, 6)).int.toUnsignedLong()
|
||||||
val rest = firstPacket[6]
|
val rest = firstPacket[6]
|
||||||
val end = min(rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, BlePacket.MAX_SIZE)
|
val end = min(rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, firstPacket.size)
|
||||||
oneExtraPacket = rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS > end
|
oneExtraPacket = rest + FirstBlePacket.HEADER_SIZE_WITHOUT_MIDDLE_PACKETS > end
|
||||||
if (end > firstPacket.size) {
|
if (end > firstPacket.size) {
|
||||||
throw IncorrectPacketException(0, firstPacket)
|
throw IncorrectPacketException(0, firstPacket)
|
||||||
|
@ -78,12 +78,12 @@ class PayloadJoiner(private val firstPacket: ByteArray) {
|
||||||
}
|
}
|
||||||
crc = ByteBuffer.wrap(packet.copyOfRange(2, 6)).int.toUnsignedLong()
|
crc = ByteBuffer.wrap(packet.copyOfRange(2, 6)).int.toUnsignedLong()
|
||||||
val rest = packet[1].toInt()
|
val rest = packet[1].toInt()
|
||||||
val end = min(rest + LastBlePacket.HEADER_SIZE, BlePacket.MAX_SIZE)
|
val end = min(rest + LastBlePacket.HEADER_SIZE, packet.size)
|
||||||
oneExtraPacket = rest + LastBlePacket.HEADER_SIZE > end
|
oneExtraPacket = rest + LastBlePacket.HEADER_SIZE > end
|
||||||
if (packet.size < end) {
|
if (packet.size < end) {
|
||||||
throw IncorrectPacketException(idx.toByte(), packet)
|
throw IncorrectPacketException(idx.toByte(), packet)
|
||||||
}
|
}
|
||||||
fragments.add(packet.copyOfRange(LastBlePacket.HEADER_SIZE, packet.size))
|
fragments.add(packet.copyOfRange(LastBlePacket.HEADER_SIZE, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
idx > fullFragments -> { // this is the extra fragment
|
idx > fullFragments -> { // this is the extra fragment
|
||||||
|
|
|
@ -43,15 +43,18 @@ class StringLengthPrefixEncoding {
|
||||||
fun formatKeys(keys: Array<String>, payloads: Array<ByteArray>): ByteArray {
|
fun formatKeys(keys: Array<String>, payloads: Array<ByteArray>): ByteArray {
|
||||||
val payloadTotalSize = payloads.fold(0) { acc, i -> acc + i.size }
|
val payloadTotalSize = payloads.fold(0) { acc, i -> acc + i.size }
|
||||||
val keyTotalSize = keys.fold(0) { acc, i -> acc + i.length }
|
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) {
|
for (idx in keys.indices) {
|
||||||
val k = keys[idx]
|
val k = keys[idx]
|
||||||
val payload = payloads[idx]
|
val payload = payloads[idx]
|
||||||
bb.put(k.toByteArray())
|
bb.put(k.toByteArray())
|
||||||
|
if (payload.size > 0) {
|
||||||
bb.putShort(payload.size.toShort())
|
bb.putShort(payload.size.toShort())
|
||||||
bb.put(payload)
|
bb.put(payload)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val ret = ByteArray(bb.position())
|
val ret = ByteArray(bb.position())
|
||||||
bb.flip()
|
bb.flip()
|
||||||
|
|
|
@ -5,7 +5,6 @@ import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.logging.LTag
|
import info.nightscout.androidaps.logging.LTag
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig
|
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.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.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.MessageIO
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
||||||
|
@ -18,7 +17,7 @@ import org.spongycastle.crypto.macs.CMac
|
||||||
import org.spongycastle.crypto.params.KeyParameter
|
import org.spongycastle.crypto.params.KeyParameter
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO) {
|
internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, val myId: Id, val podId: Id, val podAddress: Id) {
|
||||||
|
|
||||||
private val pdmPrivate = X25519.generatePrivateKey()
|
private val pdmPrivate = X25519.generatePrivateKey()
|
||||||
private val pdmPublic = X25519.publicFromPrivate(pdmPrivate)
|
private val pdmPublic = X25519.publicFromPrivate(pdmPrivate)
|
||||||
|
@ -27,8 +26,6 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
private val pdmNonce = ByteArray(NONCE_SIZE)
|
private val pdmNonce = ByteArray(NONCE_SIZE)
|
||||||
private val pdmConf = ByteArray(CMAC_SIZE)
|
private val pdmConf = ByteArray(CMAC_SIZE)
|
||||||
private val podConf = 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 seq: Byte = 1
|
||||||
private var ltk = ByteArray(CMAC_SIZE)
|
private var ltk = ByteArray(CMAC_SIZE)
|
||||||
|
|
||||||
|
@ -39,12 +36,12 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
|
|
||||||
fun negotiateLTK(): PairResult {
|
fun negotiateLTK(): PairResult {
|
||||||
// send SP1, SP2
|
// send SP1, SP2
|
||||||
val sp1sp2 = sp1sp2(nodeId.address, sp2())
|
val sp1sp2 = sp1sp2(podId.address, sp2())
|
||||||
msgIO.sendMesssage(sp1sp2.messagePacket)
|
msgIO.sendMessage(sp1sp2.messagePacket)
|
||||||
|
|
||||||
seq++
|
seq++
|
||||||
val sps1 = sps1()
|
val sps1 = sps1()
|
||||||
msgIO.sendMesssage(sps1.messagePacket)
|
msgIO.sendMessage(sps1.messagePacket)
|
||||||
// send SPS1
|
// send SPS1
|
||||||
|
|
||||||
// read SPS1
|
// read SPS1
|
||||||
|
@ -55,7 +52,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
seq++
|
seq++
|
||||||
// send SPS2
|
// send SPS2
|
||||||
val sps2 = sps2()
|
val sps2 = sps2()
|
||||||
msgIO.sendMesssage(sps2.messagePacket)
|
msgIO.sendMessage(sps2.messagePacket)
|
||||||
// read SPS2
|
// read SPS2
|
||||||
|
|
||||||
val podSps2 = msgIO.receiveMessage()
|
val podSps2 = msgIO.receiveMessage()
|
||||||
|
@ -63,7 +60,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
|
|
||||||
seq++
|
seq++
|
||||||
// send SP0GP0
|
// send SP0GP0
|
||||||
msgIO.sendMesssage(sp0gp0().messagePacket)
|
msgIO.sendMessage(sp0gp0().messagePacket)
|
||||||
// read P0
|
// read P0
|
||||||
|
|
||||||
// TODO: failing to read or validate p0 will lead to undefined state
|
// TODO: failing to read or validate p0 will lead to undefined state
|
||||||
|
@ -76,8 +73,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
|
|
||||||
return PairResult(
|
return PairResult(
|
||||||
ltk = ltk,
|
ltk = ltk,
|
||||||
podId = nodeId,
|
msgSeq = seq
|
||||||
seq = seq
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,8 +84,8 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
)
|
)
|
||||||
return PairMessage(
|
return PairMessage(
|
||||||
sequenceNumber = seq,
|
sequenceNumber = seq,
|
||||||
source = controllerId,
|
source = myId,
|
||||||
destination = nodeId,
|
destination = podAddress,
|
||||||
payload = payload
|
payload = payload
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -101,8 +97,8 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
)
|
)
|
||||||
return PairMessage(
|
return PairMessage(
|
||||||
sequenceNumber = seq,
|
sequenceNumber = seq,
|
||||||
source = controllerId,
|
source = myId,
|
||||||
destination = nodeId,
|
destination = podAddress,
|
||||||
payload = payload
|
payload = payload
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -125,8 +121,8 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
)
|
)
|
||||||
return PairMessage(
|
return PairMessage(
|
||||||
sequenceNumber = seq,
|
sequenceNumber = seq,
|
||||||
source = controllerId,
|
source = myId,
|
||||||
destination = nodeId,
|
destination = podAddress,
|
||||||
payload = payload
|
payload = payload
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -159,8 +155,8 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
val payload = SP0GP0.toByteArray()
|
val payload = SP0GP0.toByteArray()
|
||||||
return PairMessage(
|
return PairMessage(
|
||||||
sequenceNumber = seq,
|
sequenceNumber = seq,
|
||||||
source = controllerId,
|
source = myId,
|
||||||
destination = nodeId,
|
destination = podAddress,
|
||||||
payload = payload
|
payload = payload
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
|
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
|
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 msgSeq: Byte) {
|
||||||
init {
|
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()}" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ enum class EapAkaAttributeType(val type: Byte) {
|
||||||
AT_RAND(1),
|
AT_RAND(1),
|
||||||
AT_AUTN(2),
|
AT_AUTN(2),
|
||||||
AT_RES(3),
|
AT_RES(3),
|
||||||
|
AT_CLIENT_ERROR_CODE(22),
|
||||||
AT_CUSTOM_IV(126);
|
AT_CUSTOM_IV(126);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -30,7 +31,7 @@ sealed class EapAkaAttribute {
|
||||||
fun parseAttributes(aapsLogger: AAPSLogger, payload: ByteArray): List<EapAkaAttribute> {
|
fun parseAttributes(aapsLogger: AAPSLogger, payload: ByteArray): List<EapAkaAttribute> {
|
||||||
var tail = payload
|
var tail = payload
|
||||||
val ret = LinkedList<EapAkaAttribute>()
|
val ret = LinkedList<EapAkaAttribute>()
|
||||||
while (tail.size > 0) {
|
while (tail.isNotEmpty()) {
|
||||||
if (tail.size < 2) {
|
if (tail.size < 2) {
|
||||||
throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}")
|
throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}")
|
||||||
}
|
}
|
||||||
|
@ -41,11 +42,15 @@ sealed class EapAkaAttribute {
|
||||||
val type = EapAkaAttributeType.byValue(tail[0])
|
val type = EapAkaAttributeType.byValue(tail[0])
|
||||||
when (type) {
|
when (type) {
|
||||||
EapAkaAttributeType.AT_RES ->
|
EapAkaAttributeType.AT_RES ->
|
||||||
ret.add(EapAkaAttributeRes.parse(tail.copyOfRange(2, size)))
|
ret.add(EapAkaAttributeRes.parse(tail.copyOfRange(2, EapAkaAttributeRes.SIZE)))
|
||||||
EapAkaAttributeType.AT_CUSTOM_IV ->
|
EapAkaAttributeType.AT_CUSTOM_IV ->
|
||||||
ret.add(EapAkaAttributeCustomIV.parse(tail.copyOfRange(2, size)))
|
ret.add(EapAkaAttributeCustomIV.parse(tail.copyOfRange(2, EapAkaAttributeCustomIV.SIZE)))
|
||||||
else ->
|
EapAkaAttributeType.AT_AUTN ->
|
||||||
throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}. Expecting only AT_RES or CUSTOM_IV attribute types from the POD")
|
ret.add(EapAkaAttributeAutn.parse(tail.copyOfRange(2, EapAkaAttributeAutn.SIZE)))
|
||||||
|
EapAkaAttributeType.AT_RAND ->
|
||||||
|
ret.add(EapAkaAttributeRand.parse(tail.copyOfRange(2, EapAkaAttributeRand.SIZE)))
|
||||||
|
EapAkaAttributeType.AT_CLIENT_ERROR_CODE ->
|
||||||
|
ret.add(EapAkaAttributeClientErrorCode.parse(tail.copyOfRange(2, EapAkaAttributeClientErrorCode.SIZE)))
|
||||||
}
|
}
|
||||||
tail = tail.copyOfRange(size, tail.size)
|
tail = tail.copyOfRange(size, tail.size)
|
||||||
}
|
}
|
||||||
|
@ -61,12 +66,17 @@ data class EapAkaAttributeRand(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(EapAkaAttributeType.AT_RAND.type, SIZE, 0, 0) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_RAND.type, (SIZE / SIZE_MULTIPLIER).toByte(), 0, 0) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun parse(payload: ByteArray): EapAkaAttribute {
|
||||||
private const val SIZE = (20 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=16
|
if (payload.size < 2 + 16) {
|
||||||
|
throw MessageIOException("Could not parse RAND attribute: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
return EapAkaAttributeRand(payload.copyOfRange(2, 2 + 16))
|
||||||
|
}
|
||||||
|
const val SIZE = 20 // type, size, 2 reserved bytes, payload=16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,12 +87,19 @@ data class EapAkaAttributeAutn(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(EapAkaAttributeType.AT_AUTN.type, SIZE, 0, 0) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_AUTN.type, (SIZE / SIZE_MULTIPLIER).toByte(), 0, 0) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val SIZE = (20 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=16
|
fun parse(payload: ByteArray): EapAkaAttribute {
|
||||||
|
if (payload.size < 2 + 16) {
|
||||||
|
throw MessageIOException("Could not parse AUTN attribute: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
return EapAkaAttributeAutn(payload.copyOfRange(2, 2 + 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
const val SIZE = 20 // type, size, 2 reserved bytes, payload=16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +110,7 @@ data class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(EapAkaAttributeType.AT_RES.type, SIZE, 0, PAYLOAD_SIZE_BITS) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_RES.type, (SIZE / SIZE_MULTIPLIER).toByte(), 0, PAYLOAD_SIZE_BITS) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -105,7 +122,7 @@ data class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
return EapAkaAttributeRes(payload.copyOfRange(2, 2 + 8))
|
return EapAkaAttributeRes(payload.copyOfRange(2, 2 + 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val SIZE = (12 / SIZE_MULTIPLIER).toByte() // type, size, len in bits=2, payload=8
|
const val SIZE = 12 // type, size, len in bits=2, payload=8
|
||||||
private const val PAYLOAD_SIZE_BITS = 64.toByte() // type, size, 2 reserved bytes, payload
|
private const val PAYLOAD_SIZE_BITS = 64.toByte() // type, size, 2 reserved bytes, payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +134,7 @@ data class EapAkaAttributeCustomIV(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(EapAkaAttributeType.AT_CUSTOM_IV.type, SIZE, 0, 0) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_CUSTOM_IV.type, (SIZE / SIZE_MULTIPLIER).toByte(), 0, 0) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -128,7 +145,29 @@ data class EapAkaAttributeCustomIV(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
}
|
}
|
||||||
return EapAkaAttributeCustomIV(payload.copyOfRange(2, 2 + 4))
|
return EapAkaAttributeCustomIV(payload.copyOfRange(2, 2 + 4))
|
||||||
}
|
}
|
||||||
|
const val SIZE = 8 // type, size, 2 reserved bytes, payload=4
|
||||||
private const val SIZE = (8 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=4
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class EapAkaAttributeClientErrorCode(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(payload.size == 2) { "Error code hast to be 2 bytes. Payload: ${payload.toHex()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toByteArray(): ByteArray {
|
||||||
|
return byteArrayOf(EapAkaAttributeType.AT_CLIENT_ERROR_CODE.type, (SIZE / SIZE_MULTIPLIER).toByte(), 0, 0) + payload
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun parse(payload: ByteArray): EapAkaAttributeClientErrorCode {
|
||||||
|
if (payload.size < 2 + 2) {
|
||||||
|
throw MessageIOException("Could not parse CLIENT_ERROR_CODE attribute: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
return EapAkaAttributeClientErrorCode(payload.copyOfRange(2, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
const val SIZE = 4 // type, size=1, payload:2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.logging.LTag
|
import info.nightscout.androidaps.logging.LTag
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import okio.ByteString.Companion.toByteString
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
enum class EapCode(val code: Byte) {
|
enum class EapCode(val code: Byte) {
|
||||||
|
@ -42,7 +41,7 @@ data class EapMessage(
|
||||||
.allocate(totalSize)
|
.allocate(totalSize)
|
||||||
.put(code.code)
|
.put(code.code)
|
||||||
.put(identifier)
|
.put(identifier)
|
||||||
.put(((totalSize ushr 1) and 0XFF).toByte())
|
.put(((totalSize ushr 8) and 0XFF).toByte())
|
||||||
.put((totalSize and 0XFF).toByte())
|
.put((totalSize and 0XFF).toByte())
|
||||||
.put(AKA_PACKET_TYPE)
|
.put(AKA_PACKET_TYPE)
|
||||||
.put(SUBTYPE_AKA_CHALLENGE)
|
.put(SUBTYPE_AKA_CHALLENGE)
|
||||||
|
@ -63,7 +62,7 @@ data class EapMessage(
|
||||||
if (payload.size < 4) {
|
if (payload.size < 4) {
|
||||||
throw MessageIOException("Invalid eap payload: ${payload.toHex()}")
|
throw MessageIOException("Invalid eap payload: ${payload.toHex()}")
|
||||||
}
|
}
|
||||||
val totalSize = (payload[2].toInt() shl 1) or payload[3].toInt()
|
val totalSize = (payload[2].toInt() shl 8) or payload[3].toInt()
|
||||||
if (totalSize > payload.size) {
|
if (totalSize > payload.size) {
|
||||||
throw MessageIOException("Invalid eap payload. Too short: ${payload.toHex()}")
|
throw MessageIOException("Invalid eap payload. Too short: ${payload.toHex()}")
|
||||||
}
|
}
|
||||||
|
@ -78,7 +77,7 @@ data class EapMessage(
|
||||||
throw MessageIOException("Invalid eap payload. Expected AKA packet type: ${payload.toHex()}")
|
throw MessageIOException("Invalid eap payload. Expected AKA packet type: ${payload.toHex()}")
|
||||||
}
|
}
|
||||||
val attributesPayload = payload.copyOfRange(8, totalSize)
|
val attributesPayload = payload.copyOfRange(8, totalSize)
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP attributes: ${attributesPayload.toByteString()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "parsing EAP payload: ${payload.toHex()}")
|
||||||
return EapMessage(
|
return EapMessage(
|
||||||
code = EapCode.byValue(payload[0]),
|
code = EapCode.byValue(payload[0]),
|
||||||
identifier = payload[1],
|
identifier = payload[1],
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
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()} in packet $cmd")
|
||||||
|
|
||||||
|
val msg = getCmdMessage(cmd)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}")
|
||||||
|
val reply = msgIO.sendMessage(msg)
|
||||||
|
if (reply != null) { // TODO : this means the last ACK was not received, send it again?
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Received a message with payload instead of CTS: ${reply.payload.toHex()} in packet $reply")
|
||||||
|
}
|
||||||
|
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()} in packet $ack")
|
||||||
|
msgIO.sendMessage(ack)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseResponse(decrypted: MessagePacket): Response {
|
||||||
|
|
||||||
|
val payload = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0]
|
||||||
|
aapsLogger.info(LTag.PUMPBTCOMM, "Received decrypted response: ${payload.toHex()} in packet: $decrypted")
|
||||||
|
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 = 0,
|
||||||
|
ack = true,
|
||||||
|
ackNumber = (response.sequenceNumber.toInt()+1).toByte()
|
||||||
|
)
|
||||||
|
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="
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,28 +3,33 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.logging.LTag
|
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.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.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.MessageIO
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
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.MessageType
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult
|
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import org.spongycastle.util.encoders.Hex
|
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, private val ltk: PairResult) {
|
class SessionEstablisher(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
var seq = ltk.seq
|
private val msgIO: MessageIO,
|
||||||
|
private val ltk: ByteArray,
|
||||||
|
private val eapSqn: ByteArray,
|
||||||
|
private val myId: Id,
|
||||||
|
private val podId: Id,
|
||||||
|
private var msgSeq: Byte
|
||||||
|
) {
|
||||||
|
|
||||||
private val controllerIV = ByteArray(IV_SIZE)
|
private val controllerIV = ByteArray(IV_SIZE)
|
||||||
private var nodeIV = ByteArray(IV_SIZE)
|
private var nodeIV = ByteArray(IV_SIZE)
|
||||||
|
|
||||||
private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID)
|
private val milenage = Milenage(aapsLogger, ltk, eapSqn)
|
||||||
private val sqn = byteArrayOf(0, 0, 0, 0, 0, 2)
|
|
||||||
private val milenage = Milenage(aapsLogger, ltk.ltk, sqn)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
require(eapSqn.size == 6) { "EAP-SQN has to be 6 bytes long" }
|
||||||
|
require(ltk.size == 16) { "LTK has to be 16 bytes long" }
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Starting EAP-AKA")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Starting EAP-AKA")
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
random.nextBytes(controllerIV)
|
random.nextBytes(controllerIV)
|
||||||
|
@ -32,21 +37,24 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO:
|
||||||
|
|
||||||
fun negotiateSessionKeys(): SessionKeys {
|
fun negotiateSessionKeys(): SessionKeys {
|
||||||
// send EAP-AKA challenge
|
// send EAP-AKA challenge
|
||||||
seq++ //TODO: get from pod state. This only works for activating a new pod
|
msgSeq++ // TODO: get from pod state. This only works for activating a new pod
|
||||||
var challenge = eapAkaChallenge()
|
var challenge = eapAkaChallenge()
|
||||||
msgIO.sendMesssage(challenge)
|
msgIO.sendMessage(challenge)
|
||||||
|
|
||||||
val challengeResponse = msgIO.receiveMessage()
|
val challengeResponse = msgIO.receiveMessage()
|
||||||
processChallengeResponse(challengeResponse) //TODO: what do we have to answer if challenge response does not validate?
|
processChallengeResponse(challengeResponse) // TODO: what do we have to answer if challenge response does not validate?
|
||||||
|
|
||||||
seq++
|
msgSeq++
|
||||||
var success = eapSuccess()
|
var success = eapSuccess()
|
||||||
msgIO.sendMesssage(success)
|
msgIO.sendMessage(success)
|
||||||
|
|
||||||
return SessionKeys(
|
return SessionKeys(
|
||||||
ck=milenage.ck,
|
ck = milenage.ck,
|
||||||
noncePrefix = controllerIV + nodeIV,
|
nonce = Nonce(
|
||||||
sqn=sqn
|
prefix = controllerIV + nodeIV,
|
||||||
|
sqn = 0
|
||||||
|
),
|
||||||
|
msgSequenceNumber = msgSeq
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,14 +67,14 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO:
|
||||||
|
|
||||||
val eapMsg = EapMessage(
|
val eapMsg = EapMessage(
|
||||||
code = EapCode.REQUEST,
|
code = EapCode.REQUEST,
|
||||||
identifier = 42, // TODO: find what value we need here, it's probably random
|
identifier = 189.toByte(), // TODO: find what value we need here, it's probably random
|
||||||
attributes = attributes
|
attributes = attributes
|
||||||
)
|
)
|
||||||
return MessagePacket(
|
return MessagePacket(
|
||||||
type = MessageType.SESSION_ESTABLISHMENT,
|
type = MessageType.SESSION_ESTABLISHMENT,
|
||||||
sequenceNumber = seq,
|
sequenceNumber = msgSeq,
|
||||||
source = controllerId,
|
source = myId,
|
||||||
destination = ltk.podId,
|
destination = podId,
|
||||||
payload = eapMsg.toByteArray()
|
payload = eapMsg.toByteArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +83,11 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO:
|
||||||
// TODO verify that identifier matches identifer from the Challenge
|
// TODO verify that identifier matches identifer from the Challenge
|
||||||
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
||||||
if (eapMsg.attributes.size != 2) {
|
if (eapMsg.attributes.size != 2) {
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got RES message: $eapMsg")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got message: $eapMsg")
|
||||||
|
if (eapMsg.attributes.size == 1 && eapMsg.attributes[0] is EapAkaAttributeClientErrorCode) {
|
||||||
|
// TODO: special exception for this
|
||||||
|
throw SessionEstablishmentException("Received CLIENT_ERROR_CODE for EAP-AKA challenge: ${eapMsg.attributes[0].toByteArray().toHex()}")
|
||||||
|
}
|
||||||
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
||||||
}
|
}
|
||||||
for (attr in eapMsg.attributes) {
|
for (attr in eapMsg.attributes) {
|
||||||
|
@ -96,14 +108,14 @@ class SessionEstablisher(private val aapsLogger: AAPSLogger, private val msgIO:
|
||||||
val eapMsg = EapMessage(
|
val eapMsg = EapMessage(
|
||||||
code = EapCode.SUCCESS,
|
code = EapCode.SUCCESS,
|
||||||
attributes = arrayOf(),
|
attributes = arrayOf(),
|
||||||
identifier = 44 // TODO: find what value we need here
|
identifier = 189.toByte() // TODO: find what value we need here
|
||||||
)
|
)
|
||||||
|
|
||||||
return MessagePacket(
|
return MessagePacket(
|
||||||
type = MessageType.SESSION_ESTABLISHMENT,
|
type = MessageType.SESSION_ESTABLISHMENT,
|
||||||
sequenceNumber = seq,
|
sequenceNumber = msgSeq,
|
||||||
source = controllerId,
|
source = myId,
|
||||||
destination = ltk.podId,
|
destination = podId,
|
||||||
payload = eapMsg.toByteArray()
|
payload = eapMsg.toByteArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
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 {
|
init {
|
||||||
require(ck.size == 16) { "CK has to be 16 bytes long" }
|
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" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.status
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.status
|
||||||
|
|
||||||
enum class ConnectionStatus {
|
enum class ConnectionStatus {
|
||||||
CONNECTED,
|
IDLE,
|
||||||
NOT_CONNECTED;
|
BUSY,
|
||||||
|
CONNECTING,
|
||||||
|
ESTABLISHING_SESSION,
|
||||||
|
PAIRING,
|
||||||
|
RUNNING_COMMAND;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ interface OmnipodDashPodStateManager {
|
||||||
val activationTime: Long?
|
val activationTime: Long?
|
||||||
var uniqueId: Long? // TODO make Int
|
var uniqueId: Long? // TODO make Int
|
||||||
var bluetoothAddress: String?
|
var bluetoothAddress: String?
|
||||||
|
var ltk: ByteArray?
|
||||||
|
var eapAkaSequenceNumber: Long
|
||||||
|
|
||||||
val bluetoothVersion: SoftwareVersion?
|
val bluetoothVersion: SoftwareVersion?
|
||||||
val firmwareVersion: SoftwareVersion?
|
val firmwareVersion: SoftwareVersion?
|
||||||
|
@ -46,6 +48,8 @@ interface OmnipodDashPodStateManager {
|
||||||
val basalProgram: BasalProgram?
|
val basalProgram: BasalProgram?
|
||||||
|
|
||||||
fun increaseMessageSequenceNumber()
|
fun increaseMessageSequenceNumber()
|
||||||
|
fun increaseEapAkaSequenceNumber(): ByteArray
|
||||||
|
fun commitEapAkaSequenceNumber()
|
||||||
fun updateFromDefaultStatusResponse(response: DefaultStatusResponse)
|
fun updateFromDefaultStatusResponse(response: DefaultStatusResponse)
|
||||||
fun updateFromVersionResponse(response: VersionResponse)
|
fun updateFromVersionResponse(response: VersionResponse)
|
||||||
fun updateFromSetUniqueIdResponse(response: SetUniqueIdResponse)
|
fun updateFromSetUniqueIdResponse(response: SetUniqueIdResponse)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.VersionResponse
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.VersionResponse
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -150,6 +151,32 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
||||||
store()
|
store()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var eapAkaSequenceNumber: Long
|
||||||
|
get() = podState.eapAkaSequenceNumber
|
||||||
|
set(value) {
|
||||||
|
podState.eapAkaSequenceNumber = value
|
||||||
|
store()
|
||||||
|
}
|
||||||
|
|
||||||
|
override var ltk: ByteArray?
|
||||||
|
get() = podState.ltk
|
||||||
|
set(value) {
|
||||||
|
podState.ltk = value
|
||||||
|
store()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun increaseEapAkaSequenceNumber(): ByteArray {
|
||||||
|
podState.eapAkaSequenceNumber++
|
||||||
|
return ByteBuffer.allocate(8)
|
||||||
|
.putLong(podState.eapAkaSequenceNumber)
|
||||||
|
.array()
|
||||||
|
.copyOfRange(2, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun commitEapAkaSequenceNumber() {
|
||||||
|
store()
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateFromDefaultStatusResponse(response: DefaultStatusResponse) {
|
override fun updateFromDefaultStatusResponse(response: DefaultStatusResponse) {
|
||||||
podState.deliveryStatus = response.deliveryStatus
|
podState.deliveryStatus = response.deliveryStatus
|
||||||
podState.podStatus = response.podStatus
|
podState.podStatus = response.podStatus
|
||||||
|
@ -262,6 +289,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
||||||
var activationTime: Long? = null
|
var activationTime: Long? = null
|
||||||
var uniqueId: Long? = null
|
var uniqueId: Long? = null
|
||||||
var bluetoothAddress: String? = null
|
var bluetoothAddress: String? = null
|
||||||
|
var ltk: ByteArray? = null
|
||||||
|
var eapAkaSequenceNumber: Long = 1
|
||||||
|
|
||||||
var bleVersion: SoftwareVersion? = null
|
var bleVersion: SoftwareVersion? = null
|
||||||
var firmwareVersion: SoftwareVersion? = null
|
var firmwareVersion: SoftwareVersion? = null
|
||||||
|
|
|
@ -324,13 +324,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
podInfoBinding.podActiveAlerts.text = if (podStateManager.activeAlerts!!.size > 0) {
|
podInfoBinding.podActiveAlerts.text = PLACEHOLDER
|
||||||
// TODO
|
|
||||||
// TextUtils.join(System.lineSeparator(), omnipodUtil.getTranslatedActiveAlerts(podStateManager))
|
|
||||||
"TODO"
|
|
||||||
} else {
|
|
||||||
PLACEHOLDER
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.size == 0) {
|
if (errors.size == 0) {
|
||||||
|
|
|
@ -6,16 +6,18 @@ import info.nightscout.androidaps.data.PumpEnactResult
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.common.R
|
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.common.ui.wizard.deactivation.viewmodel.action.DeactivatePodViewModel
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DashDeactivatePodViewModel @Inject constructor(
|
class DashDeactivatePodViewModel @Inject constructor(
|
||||||
|
private val omnipodManager: OmnipodDashManager,
|
||||||
injector: HasAndroidInjector,
|
injector: HasAndroidInjector,
|
||||||
logger: AAPSLogger
|
logger: AAPSLogger
|
||||||
) : DeactivatePodViewModel(injector, logger) {
|
) : DeactivatePodViewModel(injector, logger) {
|
||||||
|
|
||||||
override fun doExecuteAction(): Single<PumpEnactResult> = Single.just(
|
override fun doExecuteAction(): Single<PumpEnactResult> = Single.just(
|
||||||
PumpEnactResult(injector).success(false).comment("TODO")
|
PumpEnactResult(injector).success(true).comment("TODO")
|
||||||
) // TODO
|
) // TODO
|
||||||
|
|
||||||
override fun discardPod() {
|
override fun discardPod() {
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLoggerTest
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
||||||
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import org.spongycastle.util.encoders.Hex
|
||||||
|
|
||||||
|
class EnDecryptTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decrypt() {
|
||||||
|
// TODO: add data received from the pod
|
||||||
|
// this test is failing because the way we increment the nonce
|
||||||
|
val aapsLogger = AAPSLoggerTest()
|
||||||
|
val enDecrypt = EnDecrypt(
|
||||||
|
aapsLogger,
|
||||||
|
Nonce(
|
||||||
|
Hex.decode("dda23c090a0a0a0a"),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
Hex.decode("ba1283744b6de9fab6d9b77d95a71d6e"),
|
||||||
|
)
|
||||||
|
val encryptedMessage = Hex.decode(
|
||||||
|
"54571101070003400242000002420001" +
|
||||||
|
"e09158bcb0285a81bf30635f3a17ee73f0afbb3286bc524a8a66" +
|
||||||
|
"fb1bc5b001e56543"
|
||||||
|
)
|
||||||
|
val decrypted = Hex.decode("53302e303d000effffffff00060704ffffffff82b22c47302e30")
|
||||||
|
val msg = MessagePacket.parse(encryptedMessage)
|
||||||
|
val decryptedMsg = enDecrypt.decrypt(msg)
|
||||||
|
|
||||||
|
Assert.assertEquals(decrypted.toHex(), decryptedMsg.payload.toHex())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun encrypt() {
|
||||||
|
val aapsLogger = AAPSLoggerTest()
|
||||||
|
val enDecrypt = EnDecrypt(
|
||||||
|
aapsLogger,
|
||||||
|
Nonce(
|
||||||
|
Hex.decode("dda23c090a0a0a0a"),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
Hex.decode("ba1283744b6de9fab6d9b77d95a71d6e"),
|
||||||
|
)
|
||||||
|
val encryptedMessage = Hex.decode(
|
||||||
|
"54571101070003400242000002420001" +
|
||||||
|
"e09158bcb0285a81bf30635f3a17ee73f0afbb3286bc524a8a66" +
|
||||||
|
"fb1bc5b001e56543"
|
||||||
|
)
|
||||||
|
val command = Hex.decode("53302e303d000effffffff00060704ffffffff82b22c47302e30")
|
||||||
|
val msg = MessagePacket.parse(encryptedMessage).copy(payload = command) // copy for the headers
|
||||||
|
|
||||||
|
val encrypted = enDecrypt.encrypt(msg)
|
||||||
|
|
||||||
|
Assert.assertEquals(encryptedMessage.toHex(), encrypted.asByteArray().toHex())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLoggerTest
|
||||||
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
import org.spongycastle.util.encoders.Hex
|
||||||
|
|
||||||
|
class EapMessageTest {
|
||||||
|
|
||||||
|
@Test fun testParseAndBack() {
|
||||||
|
val aapsLogger = AAPSLoggerTest()
|
||||||
|
val payload = Hex.decode("01bd0038170100000205000000c55c78e8d3b9b9e935860a7259f6c001050000c2cd1248451103bd77a6c7ef88c441ba7e0200006cff5d18")
|
||||||
|
val eapMsg = EapMessage.parse(aapsLogger, payload)
|
||||||
|
val back = eapMsg.toByteArray()
|
||||||
|
Assert.assertEquals(back.toHex(), payload.toHex())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue