From 6195874982d72936a88f7e73e053cbf4d9f55aef Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 20:38:43 -0700 Subject: [PATCH 1/5] If user changes back to default channel, use the standard key --- app/src/main/java/com/geeksville/mesh/model/Channel.kt | 9 +++++++++ .../java/com/geeksville/mesh/ui/ChannelFragment.kt | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/Channel.kt b/app/src/main/java/com/geeksville/mesh/model/Channel.kt index 00d226d7a..872fa19a6 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -9,6 +9,9 @@ import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder import java.net.MalformedURLException +/** Utility function to make it easy to declare byte arrays - FIXME move someplace better */ +fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() } + data class Channel( val settings: MeshProtos.ChannelSettings = MeshProtos.ChannelSettings.getDefaultInstance() @@ -17,6 +20,12 @@ data class Channel( // Note: this string _SHOULD NOT BE LOCALIZED_ because it directly hashes to values used on the device for the default channel name. val defaultChannelName = "Default" + // These bytes must math the well known and not secret bytes used the default channel AES128 key device code + val channelDefaultKey = byteArrayOfInts( + 0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, + 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf + ) + // Placeholder when emulating val emulated = Channel( MeshProtos.ChannelSettings.newBuilder().setName(defaultChannelName) diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index b4c3ba701..df8523a7c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -147,12 +147,20 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { newSettings.name = channelNameEdit.text.toString().trim() // Generate a new AES256 key (for any channel not named Default) - if (newSettings.name != Channel.defaultChannelName) { + if (!newSettings.name.equals( + Channel.defaultChannelName, + ignoreCase = true + ) + ) { debug("ASSIGNING NEW AES256 KEY") val random = SecureRandom() val bytes = ByteArray(32) random.nextBytes(bytes) newSettings.psk = ByteString.copyFrom(bytes) + } else { + debug("ASSIGNING NEW default AES128 KEY") + newSettings.name = Channel.defaultChannelName // Fix any case errors + newSettings.psk = ByteString.copyFrom(Channel.channelDefaultKey) } // Try to change the radio, if it fails, tell the user why and throw away their redits From 75576efabe38d0ca786612d3ed4310ba673dff4b Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 08:43:22 -0700 Subject: [PATCH 2/5] ESP32 code does not generate "service changed" indications --- .../java/com/geeksville/mesh/service/BluetoothInterface.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 2f34ae733..4861fdc83 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -378,15 +378,16 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String } } + private var needForceRefresh = true + private fun onConnect(connRes: Result) { // This callback is invoked after we are connected connRes.getOrThrow() info("Connected to radio!") - if (!hasForcedRefresh) { - // FIXME - for some reason we need to refresh _everytime_. It is almost as if we've cached wrong descriptor fieldnums forever - // hasForcedRefresh = true + if (needForceRefresh) { // Our ESP32 code doesn't properly generate "service changed" indications. Therefore we need to force a refresh on initial start + needForceRefresh = false forceServiceRefresh() } From 68ed9d5333039a82ad1e3f806aa64e9fb47b9ef4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 16:02:25 -0700 Subject: [PATCH 3/5] ESP32 BLE handles are not stable across sleep - force refresh --- .../main/java/com/geeksville/mesh/service/BluetoothInterface.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 4861fdc83..23195a3f9 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -387,7 +387,7 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String info("Connected to radio!") if (needForceRefresh) { // Our ESP32 code doesn't properly generate "service changed" indications. Therefore we need to force a refresh on initial start - needForceRefresh = false + //needForceRefresh = false // In fact, because of tearing down BLE in sleep on the ESP32, our handle # assignments are not stable across sleep - so we much refetch every time forceServiceRefresh() } From 7ee4aff64c94266bcc434b9f22788ccc8ac49549 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 16:02:57 -0700 Subject: [PATCH 4/5] don't generate redundant set User packets --- .../geeksville/mesh/service/MeshService.kt | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index a855e167d..d746d8096 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -893,6 +893,7 @@ class MeshService : Service(), Logging { val packet = toMeshPacket(p) p.status = MessageStatus.ENROUTE p.time = System.currentTimeMillis() // update time to the actual time we started sending + debug("SENDING TO RADIO: $packet") sendToRadio(packet) } @@ -934,7 +935,7 @@ class MeshService : Service(), Logging { // decided to pass through to us (except for broadcast packets) //val toNum = packet.to - debug("Recieved: $packet") + // debug("Recieved: $packet") val p = packet.decoded // If the rxTime was not set by the device (because device software was old), guess at a time @@ -1370,24 +1371,34 @@ class MeshService : Service(), Logging { * Set our owner with either the new or old API */ fun setOwner(myId: String?, longName: String, shortName: String) { - debug("SetOwner $myId : ${longName.anonymize} : $shortName") + val myNode = myNodeInfo + if (myNode != null) { - val user = MeshProtos.User.newBuilder().also { - if (myId != null) // Only set the id if it was provided - it.id = myId - it.longName = longName - it.shortName = shortName - }.build() - // Also update our own map for our nodenum, by handling the packet just like packets from other users - if (myNodeInfo != null) { - handleReceivedUser(myNodeInfo!!.myNodeNum, user) - } + val myInfo = toNodeInfo(myNode.myNodeNum) + if (longName == myInfo.user?.longName && shortName == myInfo.user?.shortName) + debug("Ignoring nop owner change") + else { + debug("SetOwner $myId : ${longName.anonymize} : $shortName") - // set my owner info - sendToRadio(ToRadio.newBuilder().apply { - this.setOwner = user - }) + val user = MeshProtos.User.newBuilder().also { + if (myId != null) // Only set the id if it was provided + it.id = myId + it.longName = longName + it.shortName = shortName + }.build() + + // Also update our own map for our nodenum, by handling the packet just like packets from other users + + handleReceivedUser(myNode.myNodeNum, user) + + // set my owner info + sendToRadio(ToRadio.newBuilder().apply { + this.setOwner = user + }) + } + } else + throw Exception("Can't set user without a node info") // this shouldn't happen } /// Do not use directly, instead call generatePacketId() From f9c1ac8cd2d139b30fcabea2843ab53fb0b867f5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 16:21:26 -0700 Subject: [PATCH 5/5] Fix back to back writes to not overwrite BLE characterstic. Fixes the "sending while device was asleep bug" --- .../mesh/service/BluetoothInterface.kt | 3 +- .../geeksville/mesh/service/SafeBluetooth.kt | 12 +++- .../mesh/service/SoftwareUpdateService.kt | 67 ++++++++++++++----- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 23195a3f9..c52b0133d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -217,9 +217,8 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String // Note: we generate a new characteristic each time, because we are about to // change the data and we want the data stored in the closure val toRadio = getCharacteristic(uuid) - toRadio.value = a - s.asyncWriteCharacteristic(toRadio) { r -> + s.asyncWriteCharacteristic(toRadio, a) { r -> try { r.getOrThrow() debug("write of ${a.size} bytes completed") diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index 1ff591646..647ec4a54 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -622,19 +622,25 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWriteCharacteristic( c: BluetoothGattCharacteristic, + v: ByteArray, cont: Continuation ) = queueWork("writeC ${c.uuid}", cont) { currentReliableWrite = null + c.value = v gatt!!.writeCharacteristic(c) } fun asyncWriteCharacteristic( c: BluetoothGattCharacteristic, + v: ByteArray, cb: (Result) -> Unit - ) = queueWriteCharacteristic(c, CallbackContinuation(cb)) + ) = queueWriteCharacteristic(c, v, CallbackContinuation(cb)) - fun writeCharacteristic(c: BluetoothGattCharacteristic): BluetoothGattCharacteristic = - makeSync { queueWriteCharacteristic(c, it) } + fun writeCharacteristic( + c: BluetoothGattCharacteristic, + v: ByteArray + ): BluetoothGattCharacteristic = + makeSync { queueWriteCharacteristic(c, v, it) } /** Like write, but we use the extra reliable flow documented here: * https://stackoverflow.com/questions/24485536/what-is-reliable-write-in-ble diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index c10c3914e..6799630a0 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -13,6 +13,48 @@ import com.geeksville.util.exceptionReporter import java.util.* import java.util.zip.CRC32 +/** + * Move this somewhere as a generic network byte order function + */ +fun toNetworkByteArray(value: Int, formatType: Int): ByteArray { + + val len: Int = 4 // getTypeLen(formatType) + val mValue = ByteArray(len) + + when (formatType) { + /* BluetoothGattCharacteristic.FORMAT_SINT8 -> { + value = intToSignedBits(value, 8) + mValue.get(offset) = (value and 0xFF).toByte() + } + BluetoothGattCharacteristic.FORMAT_UINT8 -> mValue.get(offset) = + (value and 0xFF).toByte() + BluetoothGattCharacteristic.FORMAT_SINT16 -> { + value = intToSignedBits(value, 16) + mValue.get(offset++) = (value and 0xFF).toByte() + mValue.get(offset) = (value shr 8 and 0xFF).toByte() + } + BluetoothGattCharacteristic.FORMAT_UINT16 -> { + mValue.get(offset++) = (value and 0xFF).toByte() + mValue.get(offset) = (value shr 8 and 0xFF).toByte() + } + BluetoothGattCharacteristic.FORMAT_SINT32 -> { + value = intToSignedBits(value, 32) + mValue.get(offset++) = (value and 0xFF).toByte() + mValue.get(offset++) = (value shr 8 and 0xFF).toByte() + mValue.get(offset++) = (value shr 16 and 0xFF).toByte() + mValue.get(offset) = (value shr 24 and 0xFF).toByte() + } */ + BluetoothGattCharacteristic.FORMAT_UINT32 -> { + mValue[0] = (value and 0xFF).toByte() + mValue[1] = (value shr 8 and 0xFF).toByte() + mValue[2] = (value shr 16 and 0xFF).toByte() + mValue[3] = (value shr 24 and 0xFF).toByte() + } + else -> TODO() + } + return mValue +} + /** * typical flow * @@ -211,14 +253,10 @@ class SoftwareUpdateService : JobIntentService(), Logging { val firmwareSize = firmwareStream.available() // Start the update by writing the # of bytes in the image - logAssert( - totalSizeDesc.setValue( - firmwareSize, - BluetoothGattCharacteristic.FORMAT_UINT32, - 0 - ) + sync.writeCharacteristic( + totalSizeDesc, + toNetworkByteArray(firmwareSize, BluetoothGattCharacteristic.FORMAT_UINT32) ) - sync.writeCharacteristic(totalSizeDesc) // Our write completed, queue up a readback val totalSizeReadback = sync.readCharacteristic(totalSizeDesc) @@ -239,23 +277,18 @@ class SoftwareUpdateService : JobIntentService(), Logging { // slightly expensive to keep reallocing this buffer, but whatever logAssert(firmwareStream.read(buffer) == blockSize) firmwareCrc.update(buffer) - - dataDesc.value = buffer - sync.writeCharacteristic(dataDesc) + + sync.writeCharacteristic(dataDesc, buffer) firmwareNumSent += blockSize } // We have finished sending all our blocks, so post the CRC so our state machine can advance val c = firmwareCrc.value info("Sent all blocks, crc is $c") - logAssert( - crc32Desc.setValue( - c.toInt(), - BluetoothGattCharacteristic.FORMAT_UINT32, - 0 - ) + sync.writeCharacteristic( + crc32Desc, + toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32) ) - sync.writeCharacteristic(crc32Desc) // we just read the update result if !0 we have an error val updateResult =