dash ble: detekt
This commit is contained in:
parent
210a446123
commit
64fbea6afe
7 changed files with 78 additions and 61 deletions
|
@ -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())
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()}")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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="
|
||||||
|
|
Loading…
Reference in a new issue