feat: Add SFPP confirmed status to Messages and Reactions (#4139)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
Co-authored-by: Mac DeCourcy <github.znq26@slmail.me>
This commit is contained in:
James Rich 2026-01-08 07:21:21 -06:00 committed by GitHub
parent 78bd1ad6dd
commit 782c068ead
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 2699 additions and 61 deletions

View file

@ -39,6 +39,8 @@ enum class MessageStatus : Parcelable {
QUEUED, // Waiting to send to the mesh as soon as we connect to the device
ENROUTE, // Delivered to the radio, but no ACK or NAK received
DELIVERED, // We received an ack
SFPP_ROUTING, // Message is being routed/buffered in the SFPP system
SFPP_CONFIRMED, // Message is confirmed on the SFPP chain
ERROR, // We received back a nak, message not delivered
}
@ -65,6 +67,7 @@ data class DataPacket(
var viaMqtt: Boolean = false, // True if this packet passed via MQTT somewhere along its path
var retryCount: Int = 0, // Number of automatic retry attempts
var emoji: Int = 0,
var sfppHash: ByteArray? = null,
) : Parcelable {
/** If there was an error with this message, this string describes what was wrong. */
@ -142,6 +145,7 @@ data class DataPacket(
parcel.readInt() == 1, // viaMqtt
parcel.readInt(), // retryCount
parcel.readInt(), // emoji
parcel.createByteArray(), // sfppHash
)
@Suppress("CyclomaticComplexMethod")
@ -168,6 +172,7 @@ data class DataPacket(
if (relayNode != other.relayNode) return false
if (retryCount != other.retryCount) return false
if (emoji != other.emoji) return false
if (!sfppHash.contentEquals(other.sfppHash)) return false
return true
}
@ -190,6 +195,7 @@ data class DataPacket(
result = 31 * result + relayNode.hashCode()
result = 31 * result + retryCount
result = 31 * result + emoji
result = 31 * result + (sfppHash?.contentHashCode() ?: 0)
return result
}
@ -213,6 +219,7 @@ data class DataPacket(
parcel.writeInt(if (viaMqtt) 1 else 0)
parcel.writeInt(retryCount)
parcel.writeInt(emoji)
parcel.writeByteArray(sfppHash)
}
override fun describeContents(): Int = 0
@ -238,6 +245,7 @@ data class DataPacket(
viaMqtt = parcel.readInt() == 1
retryCount = parcel.readInt()
emoji = parcel.readInt()
sfppHash = parcel.createByteArray()
}
companion object CREATOR : Parcelable.Creator<DataPacket> {

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model.util
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.MessageDigest
object SfppHasher {
private const val HASH_SIZE = 16
private const val INT_BYTES = 4
fun computeMessageHash(encryptedPayload: ByteArray, to: Int, from: Int, id: Int): ByteArray {
val digest = MessageDigest.getInstance("SHA-256")
digest.update(encryptedPayload)
digest.update(ByteBuffer.allocate(INT_BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(to).array())
digest.update(ByteBuffer.allocate(INT_BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(from).array())
digest.update(ByteBuffer.allocate(INT_BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(id).array())
return digest.digest().copyOf(HASH_SIZE)
}
}