kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
fix: Handle node public key mismatch and show warning (#1720)
* Handle node public key mismatch and show warning - Add a mismatchKey flag to Node and MessageTopBar to indicate a public key mismatch. - Set the public key to a default error value (all zeros) when a node's public key changes. - Display a warning in the MessageTopBar when a key mismatch is detected in PKC. - Only clear all nodes when a different mynode number is present. * feat: Add key mismatch detection to NodeInfoDao This commit introduces a new feature to the `NodeInfoDao` that detects and handles public key mismatches for existing nodes. - A new function `upsertCheckKeyMatch` is added to `NodeInfoDao` that checks for public key changes when upserting a node. If a mismatch is detected, the public key is set to `ERROR_BYTE_STRING`, and a warning is logged. - The function `upsertCheckKeyMatch` is used instead of `upsert` in `NodeRepository` and in `putAll` inside of `NodeInfoDao`. - A new test `testPkcMismatch` is added to `NodeInfoDaoTest` to verify the key mismatch detection. - Changed `testNodes` to have unique public keys. - Added `mismatchKey` state to the node model. * detekt spacing * Refactor: Correctly handle different node installations in NodeRepository The logic for detecting different node installations in `NodeRepository.kt` was inverted, this commit fixes the logic to use `!=` instead of `==` to detect if the node number has changed.pull/1728/head
rodzic
53c240198c
commit
a28dc377ae
|
@ -26,6 +26,7 @@ import com.geeksville.mesh.database.entity.MyNodeEntity
|
|||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.NodeSortOption
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -92,7 +93,6 @@ class NodeInfoDaoTest {
|
|||
41.878113 to -87.629799, // Chicago
|
||||
39.952583 to -75.165222, // Philadelphia
|
||||
)
|
||||
|
||||
private val testNodes = listOf(ourNode, unknownNode) + testPositions.mapIndexed { index, pos ->
|
||||
NodeEntity(
|
||||
num = 9 + index,
|
||||
|
@ -102,6 +102,7 @@ class NodeInfoDaoTest {
|
|||
shortName = "KM$index"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { index.toByte() })
|
||||
},
|
||||
longName = "Kevin Mester$index", shortName = "KM$index",
|
||||
latitude = pos.first, longitude = pos.second,
|
||||
|
@ -203,4 +204,15 @@ class NodeInfoDaoTest {
|
|||
val containsUnsetNode = nodes.any { it.isUnknownUser }
|
||||
assertTrue(containsUnsetNode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPkcMismatch() = runBlocking {
|
||||
val newNode = testNodes[1].copy(user = testNodes[1].user.copy {
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { 99 })
|
||||
})
|
||||
nodeInfoDao.putAll(listOf(newNode))
|
||||
val nodes = getNodes()
|
||||
val containsMismatchNode = nodes.any { it.mismatchKey }
|
||||
assertTrue(containsMismatchNode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,13 +103,16 @@ class NodeRepository @Inject constructor(
|
|||
).mapLatest { list -> list.map { it.toModel() } }.flowOn(dispatchers.io).conflate()
|
||||
|
||||
suspend fun upsert(node: NodeEntity) = withContext(dispatchers.io) {
|
||||
nodeInfoDao.upsert(node)
|
||||
nodeInfoDao.upsertCheckKeyMatch(node)
|
||||
}
|
||||
|
||||
suspend fun installNodeDB(mi: MyNodeEntity, nodes: List<NodeEntity>) = withContext(dispatchers.io) {
|
||||
val isDifferentNode = myNodeInfo.value?.myNodeNum != mi.myNodeNum
|
||||
nodeInfoDao.clearMyNodeInfo()
|
||||
nodeInfoDao.setMyNodeInfo(mi) // set MyNodeEntity first
|
||||
nodeInfoDao.clearNodeInfo()
|
||||
if (isDifferentNode) {
|
||||
nodeInfoDao.clearNodeInfo()
|
||||
}
|
||||
nodeInfoDao.putAll(nodes)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package com.geeksville.mesh.database.dao
|
||||
|
||||
import android.util.Log
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.MapColumn
|
||||
|
@ -26,10 +27,11 @@ import androidx.room.Transaction
|
|||
import androidx.room.Upsert
|
||||
import com.geeksville.mesh.database.entity.MetadataEntity
|
||||
import com.geeksville.mesh.database.entity.MyNodeEntity
|
||||
import com.geeksville.mesh.database.entity.NodeWithRelations
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.NodeWithRelations
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
private const val TAG = "NodeInfoDao"
|
||||
@Suppress("TooManyFunctions")
|
||||
@Dao
|
||||
interface NodeInfoDao {
|
||||
|
@ -108,8 +110,20 @@ interface NodeInfoDao {
|
|||
@Upsert
|
||||
fun upsert(node: NodeEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun putAll(nodes: List<NodeEntity>)
|
||||
fun upsertCheckKeyMatch(node: NodeEntity) {
|
||||
val existingNode = getNodeByNum(node.num)
|
||||
if (existingNode != null && existingNode.user.publicKey != node.user.publicKey) {
|
||||
Log.w(TAG, "Node ${node.num} has changed its public key")
|
||||
val user =
|
||||
node.user.toBuilder().setPublicKey(NodeEntity.ERROR_BYTE_STRING).build()
|
||||
node.user = user
|
||||
}
|
||||
upsert(node)
|
||||
}
|
||||
@Transaction
|
||||
fun putAll(nodes: List<NodeEntity>) {
|
||||
nodes.forEach { upsertCheckKeyMatch(it) }
|
||||
}
|
||||
|
||||
@Query("DELETE FROM nodes")
|
||||
fun clearNodeInfo()
|
||||
|
@ -122,4 +136,7 @@ interface NodeInfoDao {
|
|||
|
||||
@Query("DELETE FROM metadata WHERE num=:num")
|
||||
fun deleteMetadata(num: Int)
|
||||
|
||||
@Query("SELECT * FROM nodes WHERE num = :num")
|
||||
fun getNodeByNum(num: Int): NodeEntity?
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ data class NodeEntity(
|
|||
|
||||
val isUnknownUser get() = user.hwModel == MeshProtos.HardwareModel.UNSET
|
||||
val hasPKC get() = !user.publicKey.isEmpty
|
||||
val errorByteString: ByteString get() = ByteString.copyFrom(ByteArray(32) { 0 })
|
||||
val errorByteString: ByteString get() = ERROR_BYTE_STRING
|
||||
|
||||
fun setPosition(p: MeshProtos.Position, defaultTime: Int = currentTime()) {
|
||||
position = p.copy { time = if (p.time != 0) p.time else defaultTime }
|
||||
|
@ -174,6 +174,7 @@ data class NodeEntity(
|
|||
fun degD(i: Int) = i * 1e-7
|
||||
fun degI(d: Double) = (d * 1e7).toInt()
|
||||
|
||||
val ERROR_BYTE_STRING: ByteString = ByteString.copyFrom(ByteArray(32) { 0 })
|
||||
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,10 @@ import com.geeksville.mesh.PaxcountProtos
|
|||
import com.geeksville.mesh.TelemetryProtos.DeviceMetrics
|
||||
import com.geeksville.mesh.TelemetryProtos.EnvironmentMetrics
|
||||
import com.geeksville.mesh.TelemetryProtos.PowerMetrics
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.util.GPSFormat
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
import com.google.protobuf.ByteString
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
data class Node(
|
||||
|
@ -59,8 +59,7 @@ data class Node(
|
|||
|
||||
val isUnknownUser get() = user.hwModel == MeshProtos.HardwareModel.UNSET
|
||||
val hasPKC get() = !user.publicKey.isEmpty
|
||||
val errorByteString: ByteString get() = ByteString.copyFrom(ByteArray(32) { 0 })
|
||||
val mismatchKey get() = user.publicKey == errorByteString
|
||||
val mismatchKey get() = user.publicKey == NodeEntity.ERROR_BYTE_STRING
|
||||
|
||||
val hasEnvironmentMetrics: Boolean
|
||||
get() = environmentMetrics != EnvironmentMetrics.getDefaultInstance()
|
||||
|
|
|
@ -184,6 +184,8 @@ internal fun MessageScreen(
|
|||
DataPacket.ID_BROADCAST -> channelName
|
||||
else -> viewModel.getUser(nodeId).longName
|
||||
}
|
||||
val mismatchKey =
|
||||
DataPacket.PKC_CHANNEL_INDEX == channelIndex && viewModel.getNode(nodeId).mismatchKey
|
||||
|
||||
// if (channelIndex != DataPacket.PKC_CHANNEL_INDEX && nodeId != DataPacket.ID_BROADCAST) {
|
||||
// subtitle = "(ch: $channelIndex - $channelName)"
|
||||
|
@ -242,7 +244,7 @@ internal fun MessageScreen(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
MessageTopBar(title, channelIndex, onNavigateBack)
|
||||
MessageTopBar(title, channelIndex, mismatchKey, onNavigateBack)
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
|
@ -368,6 +370,7 @@ private fun ActionModeTopBar(
|
|||
private fun MessageTopBar(
|
||||
title: String,
|
||||
channelIndex: Int?,
|
||||
mismatchKey: Boolean = false,
|
||||
onNavigateBack: () -> Unit
|
||||
) = TopAppBar(
|
||||
title = { Text(text = title) },
|
||||
|
@ -381,7 +384,7 @@ private fun MessageTopBar(
|
|||
},
|
||||
actions = {
|
||||
if (channelIndex == DataPacket.PKC_CHANNEL_INDEX) {
|
||||
NodeKeyStatusIcon(hasPKC = true, mismatchKey = false)
|
||||
NodeKeyStatusIcon(hasPKC = true, mismatchKey = mismatchKey)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
Ładowanie…
Reference in New Issue