From 7fb0cabee8611d091744e7f23af5135ef2c2992d Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 25 Jan 2020 06:16:10 -0800 Subject: [PATCH] clean up protobufs with a bit of futureproofing for new packet types --- TODO.md | 6 +- .../com/geeksville/mesh/IMeshService.aidl | 4 +- .../java/com/geeksville/mesh/MeshService.kt | 67 +++++++++++++------ app/src/main/proto/mesh.proto | 34 +++++++--- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/TODO.md b/TODO.md index 6d463fca6..684e72d80 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,11 @@ -* handle failures in onCharWrite, instead of logAssert - because they can happen if device goes away +* use android service from Signal +* DONE handle failures in onCharWrite, instead of logAssert - because they can happen if device goes away * make test implementation of android service (doesn't use bluetooth) * clean up sw update code in device side * DONE add broadcasters for use by signal (node changes and packet received) * make compose based access show mesh state -* use android service from Signal * add real messaging code/protobufs * use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state * connect to bluetooth device automatically using minimum power @@ -35,7 +35,7 @@ Don't leave device discoverable. Don't let unpaired users do thing with device * make analytics optional * possibly use finotes for analytics https://finotes.com/ - * also add a receiver that fires after a new update was installed from the play stoe +* also add a receiver that fires after a new update was installed from the play stoe # Done diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl index 68687fb3d..beae2f870 100644 --- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl @@ -11,8 +11,10 @@ interface IMeshService { /* Send an opaque packet to a specified node name + + typ is defined in mesh.proto Data.Type. For now juse use 0 to mean opaque bytes. */ - void sendOpaque(String destId, in byte[] payload); + void sendOpaque(String destId, in byte[] payload, int typ); /** Get the IDs of everyone on the mesh. You should also subscribe for NODE_CHANGE broadcasts. diff --git a/app/src/main/java/com/geeksville/mesh/MeshService.kt b/app/src/main/java/com/geeksville/mesh/MeshService.kt index 43ecb62a1..417459150 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshService.kt @@ -10,6 +10,7 @@ import com.geeksville.android.Logging import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos.ToRadio import com.google.protobuf.ByteString +import java.nio.charset.Charset /** * Handles all the communication with android apps. Also keeps an internal model @@ -115,9 +116,8 @@ class MeshService : Service(), Logging { private fun toNodeID(n: Int) = toNodeInfo(n).user?.id /// given a nodenum, return a db entry - creating if necessary - private - - fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum.getOrPut(n) { -> NodeInfo(n) } + private fun getOrCreateNodeInfo(n: Int) = + nodeDBbyNodeNum.getOrPut(n) { -> NodeInfo(n) } /// Map a userid to a node/ node num, or throw an exception if not found private fun toNodeInfo(id: String) = nodeDBbyID.getValue(id) @@ -139,6 +139,38 @@ class MeshService : Service(), Logging { /// Generate a new mesh packet builder with our node as the sender, and the specified recipient private fun newMeshPacketTo(id: String) = newMeshPacketTo(toNodeNum(id)) + /// Update our model and resend as needed for a MeshPacket we just received from the radio + private fun handleReceivedData(fromNum: Int, data: MeshProtos.Data) { + val bytes = data.payload.toByteArray() + val fromId = toNodeID(fromNum) + + /// the sending node ID if possible, else just its number + val fromString = fromId ?: fromId.toString() + + when (data.typValue) { + MeshProtos.Data.Type.SIMPLE_TEXT_VALUE -> + warn( + "TODO ignoring SIMPLE_TEXT from $fromString: ${bytes.toString( + Charset.forName("UTF-8") + )}" + ) + + MeshProtos.Data.Type.CLEAR_READACK_VALUE -> + warn( + "TODO ignoring CLEAR_READACK from $fromString" + ) + + MeshProtos.Data.Type.SIGNAL_OPAQUE_VALUE -> + if (fromId == null) + error("Ignoring opaque from $fromNum because we don't yet know its ID") + else { + debug("Received opaque from $fromId ${bytes.size}") + broadcastReceivedOpaque(fromId, bytes) + } + else -> TODO() + } + } + /// Update our model and resend as needed for a MeshPacket we just received from the radio private fun handleReceivedMeshPacket(packet: MeshPacket) { val fromNum = packet.from @@ -162,19 +194,9 @@ class MeshService : Service(), Logging { updateNodeInfo(fromNum) { it.lastSeen = p.time.msecs } - MeshProtos.SubPacket.TEXT_FIELD_NUMBER -> { - debug("TODO Ignoring TEXT from $fromNum ${p.text.text}") - } - MeshProtos.SubPacket.OPAQUE_FIELD_NUMBER -> { - val opaque = p.opaque.payload.toByteArray() - val fromId = toNodeID(fromNum) - if (fromId == null) - error("Ignoring opaque from $fromNum because we don't yet know its ID") - else { - debug("Received opaque from $fromId ${opaque.size}") - broadcastReceivedOpaque(fromId, opaque) - } - } + MeshProtos.SubPacket.DATA_FIELD_NUMBER -> + handleReceivedData(fromNum, p.data) + MeshProtos.SubPacket.USER_FIELD_NUMBER -> updateNodeInfo(fromNum) { it.user = MeshUser(p.user.id, p.user.longName, p.user.shortName) @@ -195,6 +217,10 @@ class MeshService : Service(), Logging { } } + private fun handleReceivedNodeInfo(info: MeshProtos.NodeInfo) { + TODO() + } + /** * Receives messages from our BT radio service and processes them to update our model * and send to clients as needed. @@ -206,7 +232,7 @@ class MeshService : Service(), Logging { info("Received from radio service: $proto") when (proto.variantCase.number) { MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(proto.packet) - MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> TODO() + MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleReceivedNodeInfo(proto.nodeInfo) else -> TODO("Unexpected FromRadio variant") } } @@ -217,15 +243,16 @@ class MeshService : Service(), Logging { error("TODO setOwner $myId : $longName : $shortName") } - override fun sendOpaque(destId: String, payloadIn: ByteArray) { + override fun sendOpaque(destId: String, payloadIn: ByteArray, typ: Int) { info("sendOpaque $destId <- ${payloadIn.size}") // encapsulate our payload in the proper protobufs and fire it off val packet = newMeshPacketTo(destId).apply { payload = MeshProtos.MeshPayload.newBuilder().apply { addSubPackets(MeshProtos.SubPacket.newBuilder().apply { - opaque = MeshProtos.Opaque.newBuilder().apply { - payload = ByteString.copyFrom(payloadIn) + data = MeshProtos.Data.newBuilder().also { + it.typ = MeshProtos.Data.Type.SIGNAL_OPAQUE + it.payload = ByteString.copyFrom(payloadIn) }.build() }.build()) }.build() diff --git a/app/src/main/proto/mesh.proto b/app/src/main/proto/mesh.proto index b0b3c380c..82d52e2d1 100644 --- a/app/src/main/proto/mesh.proto +++ b/app/src/main/proto/mesh.proto @@ -53,13 +53,28 @@ message Time { uint64 msecs = 1; // msecs since 1970 } -// A message sent from a device outside of the mesh, in a form the mesh does not understand -// i.e. a Signal app level message. -message Opaque { - bytes payload = 1; +// a data message to forward to an external app +message Data { + enum Type { + /// A message sent from a device outside of the mesh, in a form the mesh does not understand + SIGNAL_OPAQUE = 0; // NOTE: This must be 0, because it is documented in IMeshService.aidl to be so + + /// a simple UTF-8 text message, which even the little micros in the mesh can understand and show on their screen + /// eventually in some circumstances even signal might send messages in this form (see below) + SIMPLE_TEXT = 1; + + /// a message receive acknowledgement, sent in cleartext - allows radio to show user that a message has been read by the recpient, optional + CLEAR_READACK = 2; + + /// Not yet used but eventually: + /// SIGNAL_CLEAR_OPAQUE = 3; // Unencrypted at the signal level, relying on the radio crypt only (to keep size small), but contains Signal control data radios don't care about (or non ascii text) + } + + Type typ = 1; // required + bytes payload = 2; // required } -// a simple text message, which even the little micros in the mesh can understand and show on their screen + message Text { string text = 1; } @@ -87,11 +102,10 @@ message SubPacket { oneof variant { Position position = 1; Time time = 2; - Text text = 3; - Opaque opaque = 4; - User user = 5; - WantNodeNum want_node = 6; - DenyNodeNum deny_node = 7; + Data data = 3; + User user = 4; + WantNodeNum want_node = 5; + DenyNodeNum deny_node = 6; } }