From 64fbea6afe2981f64fc26a05afb504182f157b51 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 4 Apr 2021 13:16:05 +0200 Subject: [PATCH] dash ble: detekt --- .../pump/omnipod/dash/driver/comm/Id.kt | 3 +- .../comm/exceptions/NakResponseException.kt | 3 +- .../dash/driver/comm/message/MessagePacket.kt | 17 ++++--- .../message/StringLengthPrefixEncoding.kt | 20 ++++---- .../dash/driver/comm/packet/BlePacket.kt | 51 ++++++++++--------- .../dash/driver/comm/pair/LTKExchanger.kt | 43 +++++++++------- .../dash/driver/comm/session/MilenageTest.kt | 2 +- 7 files changed, 78 insertions(+), 61 deletions(-) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/Id.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/Id.kt index d71f0ef688..e330434faf 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/Id.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/Id.kt @@ -46,8 +46,7 @@ data class Id(val address: ByteArray) { companion object { - private const val PERIPHERAL_NODE_INDEX = - 1 // TODO: understand the meaning of this value. It comes from preferences + private const val PERIPHERAL_NODE_INDEX = 1 fun fromInt(v: Int): Id { return Id(ByteBuffer.allocate(4).putInt(v).array()) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NakResponseException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NakResponseException.kt index e28900c7b7..93e4c408b4 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NakResponseException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NakResponseException.kt @@ -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 -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}") diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt index e4350db605..7b14d74094 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessagePacket.kt @@ -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.exceptions.CouldNotParseMessageException +import retrofit2.http.HEAD import java.nio.ByteBuffer /*** @@ -75,9 +76,8 @@ data class MessagePacket( private const val HEADER_SIZE = 16 fun parse(payload: ByteArray): MessagePacket { - if (payload.size < HEADER_SIZE) { - throw CouldNotParseMessageException(payload) - } + payload.assertSizeAtLeast(HEADER_SIZE) + if (payload.copyOfRange(0, 2).decodeToString() != MAGIC_PATTERN) { throw CouldNotParseMessageException(payload) } @@ -100,9 +100,8 @@ data class MessagePacket( val sequenceNumber = payload[4] val ackNumber = payload[5] val size = (payload[6].toInt() shl 3) or (payload[7].toUnsignedInt() ushr 5) - if (size + HEADER_SIZE > payload.size) { - throw CouldNotParseMessageException(payload) - } + payload.assertSizeAtLeast(size + HEADER_SIZE) + val payloadEnd = 16 + size + if (type == MessageType.ENCRYPTED) 8 // TAG else 0 @@ -146,3 +145,9 @@ private class Flag(var value: Int = 0) { } internal fun Byte.toUnsignedInt() = this.toInt() and 0xff + +private fun ByteArray.assertSizeAtLeast(size: Int) { + if (this.size < size) { + throw CouldNotParseMessageException(this) + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt index d121d12d41..90ff5b9887 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/StringLengthPrefixEncoding.kt @@ -17,23 +17,19 @@ class StringLengthPrefixEncoding private constructor() { val ret = Array(keys.size) { ByteArray(0) } var remaining = payload for ((index, key) in keys.withIndex()) { + remaining.assertSizeAtLeast(key.length) when { - remaining.size < key.length -> - throw MessageIOException("Payload too short: ${payload.toHex()} for key: $key") - !(remaining.copyOfRange(0, key.length).decodeToString() == key) -> + remaining.copyOfRange(0, key.length).decodeToString() != key -> throw MessageIOException("Key not found: $key in ${payload.toHex()}") // last key can be empty, no length index == keys.size - 1 && remaining.size == key.length -> return ret - - remaining.size < key.length + LENGTH_BYTES -> - throw MessageIOException("Length not found: for $key in ${payload.toHex()}") } + remaining.assertSizeAtLeast(key.length + LENGTH_BYTES) + remaining = remaining.copyOfRange(key.length, remaining.size) val length = (remaining[0].toUnsignedInt() shl 1) or remaining[1].toUnsignedInt() - if (length > remaining.size) { - throw MessageIOException("Payload too short, looking for length $length for $key in ${payload.toHex()}") - } + remaining.assertSizeAtLeast(length) ret[index] = remaining.copyOfRange(LENGTH_BYTES, LENGTH_BYTES + length) 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()}") + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt index 9fa26e6684..b971daed99 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/packet/BlePacket.kt @@ -46,27 +46,24 @@ data class FirstBlePacket( companion object { fun parse(payload: ByteArray): FirstBlePacket { - if (payload.size < FirstBlePacket.HEADER_SIZE_WITH_MIDDLE_PACKETS) { - throw IncorrectPacketException(payload, 0) - } + payload.assertSizeAtLeast(HEADER_SIZE_WITH_MIDDLE_PACKETS, 0) + if (payload[0].toInt() != 0) { // most likely we lost the first packet. throw IncorrectPacketException(payload, 0) } val fullFragments = payload[1].toInt() require(fullFragments < MAX_FRAGMENTS) { "Received more than $MAX_FRAGMENTS fragments" } - when { - // Without middle packets - payload.size < HEADER_SIZE_WITHOUT_MIDDLE_PACKETS -> - throw IncorrectPacketException(payload, 0) + + payload.assertSizeAtLeast(HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, 0) + + return when { fullFragments == 0 -> { val rest = payload[6] val end = Integer.min(rest + HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, payload.size) - if (end > payload.size) { - throw IncorrectPacketException(payload, 0) - } - return FirstBlePacket( + payload.assertSizeAtLeast(end, 0) + FirstBlePacket( fullFragments = fullFragments, payload = payload.copyOfRange(HEADER_SIZE_WITHOUT_MIDDLE_PACKETS, end), crc32 = ByteBuffer.wrap(payload.copyOfRange(2, 6)).int.toUnsignedLong(), @@ -76,11 +73,11 @@ data class FirstBlePacket( } // With middle packets - payload.size < BlePacket.MAX_SIZE -> + payload.size < MAX_SIZE -> throw IncorrectPacketException(payload, 0) else -> { - return FirstBlePacket( + FirstBlePacket( fullFragments = fullFragments, 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 { fun parse(payload: ByteArray): MiddleBlePacket { - if (payload.size < MAX_SIZE) { - throw IncorrectPacketException(payload, 0) - } + payload.assertSizeAtLeast(MAX_SIZE) return MiddleBlePacket( index = payload[0], payload.copyOfRange(1, MAX_SIZE) @@ -148,14 +143,13 @@ data class LastBlePacket( companion object { fun parse(payload: ByteArray): LastBlePacket { - if (payload.size < HEADER_SIZE) { - throw IncorrectPacketException(payload, 0) - } + payload.assertSizeAtLeast(HEADER_SIZE) + val rest = payload[1] val end = Integer.min(rest + HEADER_SIZE, payload.size) - if (payload.size < end) { - throw IncorrectPacketException(payload, 0) - } + + payload.assertSizeAtLeast(end) + return LastBlePacket( index = payload[0], crc32 = ByteBuffer.wrap(payload.copyOfRange(2, 6)).int.toUnsignedLong(), @@ -183,10 +177,10 @@ data class LastOptionalPlusOneBlePacket( companion object { fun parse(payload: ByteArray): LastOptionalPlusOneBlePacket { + payload.assertSizeAtLeast(2) val size = payload[1].toInt() - if (payload.size < HEADER_SIZE + size) { - throw IncorrectPacketException(payload, 0) - } + payload.assertSizeAtLeast(HEADER_SIZE + size) + return LastOptionalPlusOneBlePacket( index = payload[0], payload = payload.copyOfRange( @@ -200,3 +194,10 @@ data class LastOptionalPlusOneBlePacket( private const val HEADER_SIZE = 2 } } + + +private fun ByteArray.assertSizeAtLeast(size: Int, index: Byte?=null) { + if (this.size < size) { + throw IncorrectPacketException(this, index) + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt index fbe480dfdb..6777b2b162 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt @@ -27,19 +27,15 @@ internal class LTKExchanger( private val keyExchange = KeyExchange(aapsLogger, X25519KeyGenerator(), RandomByteGenerator()) private var seq: Byte = 1 + @Throws(PairingException::class) fun negotiateLTK(): PairResult { val sp1sp2 = sp1sp2(podId.address, sp2()) - val sendSp1Sp2Result = msgIO.sendMessage(sp1sp2.messagePacket) - if (sendSp1Sp2Result !is MessageSendSuccess) { - throw PairingException("Could not send SP1SP2: $sendSp1Sp2Result") - } + throwOnSendError(sp1sp2.messagePacket, "SP1SP2") seq++ val sps1 = sps1() - val sp1Result = msgIO.sendMessage(sps1.messagePacket) - if (sp1Result !is MessageSendSuccess) { - throw PairingException("Could not send SP1: $sp1Result") - } + throwOnSendError(sps1.messagePacket, "SP1") + val podSps1 = msgIO.receiveMessage() ?: throw PairingException("Could not read SPS1") processSps1FromPod(podSps1) @@ -47,20 +43,16 @@ internal class LTKExchanger( seq++ val sps2 = sps2() - val sp2Result = msgIO.sendMessage(sps2.messagePacket) - if (sp2Result !is MessageSendSuccess) { - throw PairingException("Could not send sps2: $sp2Result") - } + throwOnSendError(sps2.messagePacket, "SPS2") + val podSps2 = msgIO.receiveMessage() ?: throw PairingException("Could not read SPS2") validatePodSps2(podSps2) seq++ // send SP0GP0 - val sp0gp0Result = msgIO.sendMessage(sp0gp0().messagePacket) - if (sp0gp0Result is MessageSendErrorSending) { - throw PairingException("Could not send SP0GP0: $sp0gp0Result") - } + throwOnSendErrorSending(sp0gp0().messagePacket, "SP0GP0") + // No exception throwing after this point. It is possible that the pod saved the LTK 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 { val payload = StringLengthPrefixEncoding.formatKeys( arrayOf(SP1, SP2), @@ -160,7 +168,8 @@ internal class LTKExchanger( companion object { 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 SP2 = ",SP2=" diff --git a/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt index d16c20028a..8317873c53 100644 --- a/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt +++ b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt @@ -39,7 +39,7 @@ class MilenageTest { val m = Milenage( aapsLogger = aapsLogger, k = Hex.decode("c0772899720972a314f557de66d571dd"), - // byteArrayOf(0,0,0,0,0x01,0x5d), this is in logs. SQN has to be incremented. + // byteArrayOf(0,0,0,0,0x01,0x5d), this is in logs. SQN has to be incremented. sqn = byteArrayOf(0, 0, 0, 0, 0x01, 0x5e), randParam = Hex.decode("d71cc44820e5419f42c62ae97c035988") )