dash ble: detekt

This commit is contained in:
Andrei Vereha 2021-04-04 13:16:05 +02:00
parent 210a446123
commit 64fbea6afe
7 changed files with 78 additions and 61 deletions

View file

@ -46,8 +46,7 @@ data class Id(val address: ByteArray) {
companion object { companion object {
private const val PERIPHERAL_NODE_INDEX = private const val PERIPHERAL_NODE_INDEX = 1
1 // TODO: understand the meaning of this value. It comes from preferences
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())

View file

@ -2,4 +2,5 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.excepti
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse
class NakResponseException(val response: NakResponse) : Exception("Received NAK response: ${response.nakErrorType.value} ${response.nakErrorType.name}") class NakResponseException(val response: NakResponse) :
Exception("Received NAK response: ${response.nakErrorType.value} ${response.nakErrorType.name}")

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseMessageException import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseMessageException
import retrofit2.http.HEAD
import java.nio.ByteBuffer import java.nio.ByteBuffer
/*** /***
@ -75,9 +76,8 @@ data class MessagePacket(
private const val HEADER_SIZE = 16 private const val HEADER_SIZE = 16
fun parse(payload: ByteArray): MessagePacket { fun parse(payload: ByteArray): MessagePacket {
if (payload.size < HEADER_SIZE) { payload.assertSizeAtLeast(HEADER_SIZE)
throw CouldNotParseMessageException(payload)
}
if (payload.copyOfRange(0, 2).decodeToString() != MAGIC_PATTERN) { if (payload.copyOfRange(0, 2).decodeToString() != MAGIC_PATTERN) {
throw CouldNotParseMessageException(payload) throw CouldNotParseMessageException(payload)
} }
@ -100,9 +100,8 @@ data class MessagePacket(
val sequenceNumber = payload[4] val sequenceNumber = payload[4]
val ackNumber = payload[5] val ackNumber = payload[5]
val size = (payload[6].toInt() shl 3) or (payload[7].toUnsignedInt() ushr 5) val size = (payload[6].toInt() shl 3) or (payload[7].toUnsignedInt() ushr 5)
if (size + HEADER_SIZE > payload.size) { payload.assertSizeAtLeast(size + HEADER_SIZE)
throw CouldNotParseMessageException(payload)
}
val payloadEnd = 16 + size + val payloadEnd = 16 + size +
if (type == MessageType.ENCRYPTED) 8 // TAG if (type == MessageType.ENCRYPTED) 8 // TAG
else 0 else 0
@ -146,3 +145,9 @@ private class Flag(var value: Int = 0) {
} }
internal fun Byte.toUnsignedInt() = this.toInt() and 0xff internal fun Byte.toUnsignedInt() = this.toInt() and 0xff
private fun ByteArray.assertSizeAtLeast(size: Int) {
if (this.size < size) {
throw CouldNotParseMessageException(this)
}
}

View file

@ -17,23 +17,19 @@ class StringLengthPrefixEncoding private constructor() {
val ret = Array(keys.size) { ByteArray(0) } val ret = Array(keys.size) { ByteArray(0) }
var remaining = payload var remaining = payload
for ((index, key) in keys.withIndex()) { for ((index, key) in keys.withIndex()) {
remaining.assertSizeAtLeast(key.length)
when { when {
remaining.size < key.length -> remaining.copyOfRange(0, key.length).decodeToString() != key ->
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()}") throw MessageIOException("Key not found: $key in ${payload.toHex()}")
// last key can be empty, no length // 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 return ret
remaining.size < key.length + LENGTH_BYTES ->
throw MessageIOException("Length not found: for $key in ${payload.toHex()}")
} }
remaining.assertSizeAtLeast(key.length + LENGTH_BYTES)
remaining = remaining.copyOfRange(key.length, remaining.size) remaining = remaining.copyOfRange(key.length, remaining.size)
val length = (remaining[0].toUnsignedInt() shl 1) or remaining[1].toUnsignedInt() val length = (remaining[0].toUnsignedInt() shl 1) or remaining[1].toUnsignedInt()
if (length > remaining.size) { remaining.assertSizeAtLeast(length)
throw MessageIOException("Payload too short, looking for length $length for $key in ${payload.toHex()}")
}
ret[index] = remaining.copyOfRange(LENGTH_BYTES, LENGTH_BYTES + length) ret[index] = remaining.copyOfRange(LENGTH_BYTES, LENGTH_BYTES + length)
remaining = remaining.copyOfRange(LENGTH_BYTES + length, remaining.size) remaining = remaining.copyOfRange(LENGTH_BYTES + length, remaining.size)
} }
@ -64,3 +60,9 @@ class StringLengthPrefixEncoding private constructor() {
} }
} }
} }
private fun ByteArray.assertSizeAtLeast(size: Int) {
if (this.size < size) {
throw MessageIOException("Payload too short: ${this.toHex()}")
}
}

View file

@ -46,27 +46,24 @@ data class FirstBlePacket(
companion object { companion object {
fun parse(payload: ByteArray): FirstBlePacket { fun parse(payload: ByteArray): FirstBlePacket {
if (payload.size < FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS) { payload.assertSizeAtLeast(HEADER_SIZE_WITH_MIDDLE_PACKETS, 0)
throw IncorrectPacketException(payload, 0)
}
if (payload[0].toInt() != 0) { if (payload[0].toInt() != 0) {
// most likely we lost the first packet. // most likely we lost the first packet.
throw IncorrectPacketException(payload, 0) throw IncorrectPacketException(payload, 0)
} }
val fullFragments = payload[1].toInt() val fullFragments = payload[1].toInt()
require(fullFragments < MAX_FRAGMENTS) { "Received more than $MAX_FRAGMENTS fragments" } require(fullFragments < MAX_FRAGMENTS) { "Received more than $MAX_FRAGMENTS fragments" }
when {
// Without middle packets payload.assertSizeAtLeast(HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, 0)
payload.size < HEADER_SIZE_WITHOUT_MIDDLE_PACKETS ->
throw IncorrectPacketException(payload, 0) return when {
fullFragments == 0 -> { fullFragments == 0 -> {
val rest = payload[6] val rest = payload[6]
val end = Integer.min(rest + HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, payload.size) val end = Integer.min(rest + HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, payload.size)
if (end > payload.size) { payload.assertSizeAtLeast(end, 0)
throw IncorrectPacketException(payload, 0) FirstBlePacket(
}
return FirstBlePacket(
fullFragments = fullFragments, fullFragments = fullFragments,
payload = payload.copyOfRange(HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, end), payload = payload.copyOfRange(HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, end),
crc32 = ByteBuffer.wrap(payload.copyOfRange(2, 6)).int.toUnsignedLong(), crc32 = ByteBuffer.wrap(payload.copyOfRange(2, 6)).int.toUnsignedLong(),
@ -76,11 +73,11 @@ data class FirstBlePacket(
} }
// With middle packets // With middle packets
payload.size < BlePacket.MAX_SIZE -> payload.size < MAX_SIZE ->
throw IncorrectPacketException(payload, 0) throw IncorrectPacketException(payload, 0)
else -> { else -> {
return FirstBlePacket( FirstBlePacket(
fullFragments = fullFragments, fullFragments = fullFragments,
payload = payload.copyOfRange(HEADER_SIZE_WITH_MIDDLE_PACKETS, MAX_SIZE) payload = payload.copyOfRange(HEADER_SIZE_WITH_MIDDLE_PACKETS, MAX_SIZE)
) )
@ -110,9 +107,7 @@ data class MiddleBlePacket(val index: Byte, override val payload: ByteArray) : B
companion object { companion object {
fun parse(payload: ByteArray): MiddleBlePacket { fun parse(payload: ByteArray): MiddleBlePacket {
if (payload.size < MAX_SIZE) { payload.assertSizeAtLeast(MAX_SIZE)
throw IncorrectPacketException(payload, 0)
}
return MiddleBlePacket( return MiddleBlePacket(
index = payload[0], index = payload[0],
payload.copyOfRange(1, MAX_SIZE) payload.copyOfRange(1, MAX_SIZE)
@ -148,14 +143,13 @@ data class LastBlePacket(
companion object { companion object {
fun parse(payload: ByteArray): LastBlePacket { fun parse(payload: ByteArray): LastBlePacket {
if (payload.size < HEADER_SIZE) { payload.assertSizeAtLeast(HEADER_SIZE)
throw IncorrectPacketException(payload, 0)
}
val rest = payload[1] val rest = payload[1]
val end = Integer.min(rest + HEADER_SIZE, payload.size) val end = Integer.min(rest + HEADER_SIZE, payload.size)
if (payload.size < end) {
throw IncorrectPacketException(payload, 0) payload.assertSizeAtLeast(end)
}
return LastBlePacket( return LastBlePacket(
index = payload[0], index = payload[0],
crc32 = ByteBuffer.wrap(payload.copyOfRange(2, 6)).int.toUnsignedLong(), crc32 = ByteBuffer.wrap(payload.copyOfRange(2, 6)).int.toUnsignedLong(),
@ -183,10 +177,10 @@ data class LastOptionalPlusOneBlePacket(
companion object { companion object {
fun parse(payload: ByteArray): LastOptionalPlusOneBlePacket { fun parse(payload: ByteArray): LastOptionalPlusOneBlePacket {
payload.assertSizeAtLeast(2)
val size = payload[1].toInt() val size = payload[1].toInt()
if (payload.size < HEADER_SIZE + size) { payload.assertSizeAtLeast(HEADER_SIZE + size)
throw IncorrectPacketException(payload, 0)
}
return LastOptionalPlusOneBlePacket( return LastOptionalPlusOneBlePacket(
index = payload[0], index = payload[0],
payload = payload.copyOfRange( payload = payload.copyOfRange(
@ -200,3 +194,10 @@ data class LastOptionalPlusOneBlePacket(
private const val HEADER_SIZE = 2 private const val HEADER_SIZE = 2
} }
} }
private fun ByteArray.assertSizeAtLeast(size: Int, index: Byte?=null) {
if (this.size < size) {
throw IncorrectPacketException(this, index)
}
}

View file

@ -27,19 +27,15 @@ internal class LTKExchanger(
private val keyExchange = KeyExchange(aapsLogger, X25519KeyGenerator(), RandomByteGenerator()) private val keyExchange = KeyExchange(aapsLogger, X25519KeyGenerator(), RandomByteGenerator())
private var seq: Byte = 1 private var seq: Byte = 1
@Throws(PairingException::class)
fun negotiateLTK(): PairResult { fun negotiateLTK(): PairResult {
val sp1sp2 = sp1sp2(podId.address, sp2()) val sp1sp2 = sp1sp2(podId.address, sp2())
val sendSp1Sp2Result = msgIO.sendMessage(sp1sp2.messagePacket) throwOnSendError(sp1sp2.messagePacket, "SP1SP2")
if (sendSp1Sp2Result !is MessageSendSuccess) {
throw PairingException("Could not send SP1SP2: $sendSp1Sp2Result")
}
seq++ seq++
val sps1 = sps1() val sps1 = sps1()
val sp1Result = msgIO.sendMessage(sps1.messagePacket) throwOnSendError(sps1.messagePacket, "SP1")
if (sp1Result !is MessageSendSuccess) {
throw PairingException("Could not send SP1: $sp1Result")
}
val podSps1 = msgIO.receiveMessage() ?: throw PairingException("Could not read SPS1") val podSps1 = msgIO.receiveMessage() ?: throw PairingException("Could not read SPS1")
processSps1FromPod(podSps1) processSps1FromPod(podSps1)
@ -47,20 +43,16 @@ internal class LTKExchanger(
seq++ seq++
val sps2 = sps2() val sps2 = sps2()
val sp2Result = msgIO.sendMessage(sps2.messagePacket) throwOnSendError(sps2.messagePacket, "SPS2")
if (sp2Result !is MessageSendSuccess) {
throw PairingException("Could not send sps2: $sp2Result")
}
val podSps2 = msgIO.receiveMessage() ?: throw PairingException("Could not read SPS2") val podSps2 = msgIO.receiveMessage() ?: throw PairingException("Could not read SPS2")
validatePodSps2(podSps2) validatePodSps2(podSps2)
seq++ seq++
// send SP0GP0 // send SP0GP0
val sp0gp0Result = msgIO.sendMessage(sp0gp0().messagePacket) throwOnSendErrorSending(sp0gp0().messagePacket, "SP0GP0")
if (sp0gp0Result is MessageSendErrorSending) {
throw PairingException("Could not send SP0GP0: $sp0gp0Result")
}
// No exception throwing after this point. It is possible that the pod saved the LTK // No exception throwing after this point. It is possible that the pod saved the LTK
msgIO.receiveMessage() msgIO.receiveMessage()
@ -73,6 +65,22 @@ internal class LTKExchanger(
) )
} }
@Throws(PairingException::class)
private fun throwOnSendError(msg: MessagePacket, msgType: String) {
val result = msgIO.sendMessage(msg)
if (result !is MessageSendSuccess) {
throw PairingException("Could not send or confirm $msgType: $result")
}
}
@Throws(PairingException::class)
private fun throwOnSendErrorSending(msg: MessagePacket, msgType: String) {
val result = msgIO.sendMessage(msg)
if (result is MessageSendErrorSending) {
throw PairingException("Could not send $msgType: $result")
}
}
private fun sp1sp2(sp1: ByteArray, sp2: ByteArray): PairMessage { private fun sp1sp2(sp1: ByteArray, sp2: ByteArray): PairMessage {
val payload = StringLengthPrefixEncoding.formatKeys( val payload = StringLengthPrefixEncoding.formatKeys(
arrayOf(SP1, SP2), arrayOf(SP1, SP2),
@ -160,7 +168,8 @@ internal class LTKExchanger(
companion object { companion object {
private const val GET_POD_STATUS_HEX_COMMAND = private const val GET_POD_STATUS_HEX_COMMAND =
"ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that. "ffc32dbd08030e0100008a"
// This is the binary representation of "GetPodStatus command"
private const val SP1 = "SP1=" private const val SP1 = "SP1="
private const val SP2 = ",SP2=" private const val SP2 = ",SP2="