dash/ble: start implement EAP-AKA
This commit is contained in:
parent
0523f7c17c
commit
e82826bf5a
9 changed files with 261 additions and 4 deletions
|
@ -25,6 +25,10 @@ data class Id(val address: ByteArray) {
|
||||||
return "$asInt/${address.toHex()}"
|
return "$asInt/${address.toHex()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toLong(): Long {
|
||||||
|
return ByteBuffer.wrap(address).long
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val PERIPHERAL_NODE_INDEX = 1 // TODO: understand the meaning of this value. It comes from preferences
|
private val PERIPHERAL_NODE_INDEX = 1 // TODO: understand the meaning of this value. It comes from preferences
|
||||||
|
|
|
@ -16,6 +16,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.Chara
|
||||||
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.EapAkaExchanger
|
||||||
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
|
||||||
|
@ -125,13 +126,20 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
|
|
||||||
val msgIO = MessageIO(aapsLogger, bleIO)
|
val msgIO = MessageIO(aapsLogger, bleIO)
|
||||||
val ltkExchanger = LTKExchanger(aapsLogger, msgIO)
|
val ltkExchanger = LTKExchanger(aapsLogger, msgIO)
|
||||||
|
|
||||||
emitter.onNext(PodEvent.Pairing)
|
emitter.onNext(PodEvent.Pairing)
|
||||||
|
|
||||||
val ltk = ltkExchanger.negotiateLTK()
|
val ltk = ltkExchanger.negotiateLTK()
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}")
|
aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}")
|
||||||
|
|
||||||
emitter.onNext(PodEvent.Connected(PodScanner.POD_ID_NOT_ACTIVATED)) // TODO supply actual pod id
|
emitter.onNext(PodEvent.EstablishingSession)
|
||||||
|
|
||||||
|
val EapAkaExchanger = EapAkaExchanger(msgIO, ltk)
|
||||||
|
val sessionKeys = EapAkaExchanger.negotiateSessionKeys()
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "Got session Key: $sessionKeys")
|
||||||
|
|
||||||
|
emitter.onNext(PodEvent.Connected(ltk.podId.toLong())) // TODO supply actual pod id
|
||||||
|
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
|
|
|
@ -75,7 +75,9 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
||||||
validateP0(p0)
|
validateP0(p0)
|
||||||
|
|
||||||
return PairResult(
|
return PairResult(
|
||||||
ltk = ltk
|
ltk = ltk,
|
||||||
|
podId = nodeId,
|
||||||
|
seq = seq
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,5 +16,5 @@ data class PairMessage(
|
||||||
payload = payload,
|
payload = payload,
|
||||||
sequenceNumber = sequenceNumber,
|
sequenceNumber = sequenceNumber,
|
||||||
sas = true // TODO: understand why this is true for PairMessages
|
sas = true // TODO: understand why this is true for PairMessages
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
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) {
|
data class PairResult(val ltk: ByteArray, val podId: Id, val seq: 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()}" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
|
||||||
|
enum class EapAkaAttributeType(val type: Byte) {
|
||||||
|
AT_RAND(1),
|
||||||
|
AT_AUTN(2),
|
||||||
|
AT_RES(3),
|
||||||
|
AT_CUSTOM_IV(126);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun byValue(value: Byte): EapAkaAttributeType =
|
||||||
|
EapAkaAttributeType.values().firstOrNull { it.type == value }
|
||||||
|
?: throw IllegalArgumentException("Unknown EAP-AKA attribute type: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EapAkaAttribute(val type: EapAkaAttributeType) {
|
||||||
|
|
||||||
|
abstract fun toByteArray(): ByteArray
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val SIZE_MULTIPLIER = 4 // The length for EAP-AKA attributes is a multiple of 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EapAkaAttributeRand(val payload: ByteArray) : EapAkaAttribute(
|
||||||
|
type = EapAkaAttributeType.AT_RAND
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(payload.size == 16) { "AT_RAND payload size has to be 16 bytes. Payload: ${payload.toHex()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toByteArray(): ByteArray {
|
||||||
|
return byteArrayOf(type.type, SIZE, 0, 0) + payload
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val SIZE = (20 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EapAkaAttributeAutn(val payload: ByteArray) : EapAkaAttribute(
|
||||||
|
type = EapAkaAttributeType.AT_AUTN
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(payload.size == 16) { "AT_AUTN payload size has to be 16 bytes. Payload: ${payload.toHex()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toByteArray(): ByteArray {
|
||||||
|
return byteArrayOf(type.type, SIZE, 0, 0) + payload
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val SIZE = (20 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute(
|
||||||
|
type = EapAkaAttributeType.AT_AUTN
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(payload.size == 8) { "AT_RES payload size has to be 8 bytes. Payload: ${payload.toHex()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toByteArray(): ByteArray {
|
||||||
|
return byteArrayOf(type.type, SIZE, 0, PAYLOAD_SIZE_BITS) + payload
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val SIZE = (12 / SIZE_MULTIPLIER).toByte() // type, size, len in bits=2, payload=8
|
||||||
|
private const val PAYLOAD_SIZE_BITS = 64.toByte() // type, size, 2 reserved bytes, payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EapAkaAttributeCustomIV(val payload: ByteArray) : EapAkaAttribute(
|
||||||
|
type = EapAkaAttributeType.AT_CUSTOM_IV
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(payload.size == 4) { "AT_RES payload size has to be 8 bytes. Payload: ${payload.toHex()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toByteArray(): ByteArray {
|
||||||
|
return byteArrayOf(type.type, SIZE, 0, 0) + payload
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val SIZE = (8 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=4
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult
|
||||||
|
import okio.ByteString.Companion.decodeHex
|
||||||
|
|
||||||
|
class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult) {
|
||||||
|
|
||||||
|
var seq = ltk.seq
|
||||||
|
private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID)
|
||||||
|
|
||||||
|
fun negotiateSessionKeys(): SessionKeys {
|
||||||
|
// send EAP-AKA challenge
|
||||||
|
seq++
|
||||||
|
var challenge = eapAkaChallenge()
|
||||||
|
msgIO.sendMesssage(challenge.messagePacket)
|
||||||
|
|
||||||
|
// read SPS1
|
||||||
|
val challengeResponse = msgIO.receiveMessage()
|
||||||
|
processChallengeResponse(challengeResponse)
|
||||||
|
// now we have all the data to generate: confPod, confPdm, ltk and noncePrefix
|
||||||
|
// TODO: what do we have to answer if challenge response does not validate?
|
||||||
|
|
||||||
|
seq++
|
||||||
|
var success = eapSuccess()
|
||||||
|
msgIO.sendMesssage(success.messagePacket)
|
||||||
|
|
||||||
|
return SessionKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun eapAkaChallenge(): EapAkaMessage {
|
||||||
|
val payload = ByteArray(0)
|
||||||
|
return EapAkaMessage(
|
||||||
|
code = EapCode.REQUEST,
|
||||||
|
identifier = 42, // TODO: find what value we need here
|
||||||
|
attributes = null,
|
||||||
|
sequenceNumber = seq,
|
||||||
|
source = controllerId,
|
||||||
|
destination = ltk.podId,
|
||||||
|
payload = payload
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun eapSuccess(): EapAkaMessage {
|
||||||
|
val payload = ByteArray(0)
|
||||||
|
return EapAkaMessage(
|
||||||
|
code = EapCode.SUCCESS,
|
||||||
|
attributes = null,
|
||||||
|
identifier = 42, // TODO: find what value we need here
|
||||||
|
sequenceNumber = seq,
|
||||||
|
source = controllerId,
|
||||||
|
destination = ltk.podId,
|
||||||
|
payload = payload
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val MILENAGE_OP = "cdc202d5123e20f62b6d676ac72cb318".decodeHex()
|
||||||
|
private val MILENAGE_AMF = "b9b9".decodeHex()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
|
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 retrofit2.http.HEAD
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
enum class EapCode(val code: Byte) {
|
||||||
|
REQUEST(1),
|
||||||
|
RESPONSE(2),
|
||||||
|
SUCCESS(3),
|
||||||
|
FAILURE(4);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun byValue(value: Byte): EapCode =
|
||||||
|
EapCode.values().firstOrNull { it.code == value }
|
||||||
|
?: throw IllegalArgumentException("Unknown EAP-AKA attribute type: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EapAkaMessage(
|
||||||
|
val code: EapCode,
|
||||||
|
val identifier: Byte,
|
||||||
|
val sequenceNumber: Byte,
|
||||||
|
val source: Id,
|
||||||
|
val destination: Id,
|
||||||
|
val payload: ByteArray,
|
||||||
|
val attributes: Array<EapAkaAttribute>?,
|
||||||
|
val messagePacket: MessagePacket = MessagePacket(
|
||||||
|
type = MessageType.SESSION_ESTABLISHMENT,
|
||||||
|
source = source,
|
||||||
|
destination = destination,
|
||||||
|
payload = payload,
|
||||||
|
sequenceNumber = sequenceNumber,
|
||||||
|
sas = true // TODO: understand why this is true for PairMessages
|
||||||
|
)) {
|
||||||
|
|
||||||
|
fun toByteArray(): ByteArray {
|
||||||
|
|
||||||
|
val serializedAttributes = attributes?.flatMap{ it.toByteArray().asIterable() }
|
||||||
|
val joinedAttributes = serializedAttributes?.toTypedArray()?.toByteArray()
|
||||||
|
|
||||||
|
val attrSize = joinedAttributes?.size ?: 0
|
||||||
|
val totalSize = HEADER_SIZE + attrSize
|
||||||
|
|
||||||
|
var bb = ByteBuffer
|
||||||
|
.allocate(totalSize)
|
||||||
|
.put(code.code)
|
||||||
|
.put(identifier)
|
||||||
|
.put(((totalSize ushr 1) and 0XFF).toByte())
|
||||||
|
.put((totalSize and 0XFF).toByte())
|
||||||
|
.put(AKA_PACKET_TYPE)
|
||||||
|
.put(SUBTYPE_AKA_CHALLENGE)
|
||||||
|
.put(byteArrayOf(0,0))
|
||||||
|
.put(joinedAttributes ?: ByteArray(0))
|
||||||
|
|
||||||
|
val ret = bb.array()
|
||||||
|
return ret.copyOfRange(0, ret.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val HEADER_SIZE = 8
|
||||||
|
private const val SUBTYPE_AKA_CHALLENGE = 1.toByte()
|
||||||
|
private const val AKA_PACKET_TYPE = 0x17.toByte()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
class SessionKeys {
|
||||||
|
}
|
Loading…
Reference in a new issue