gossip works

This commit is contained in:
callebtc
2025-09-05 16:29:46 +02:00
parent f4b33b5a09
commit 5769e70ea4
5 changed files with 62 additions and 26 deletions
@@ -707,6 +707,11 @@ class BluetoothMeshService(private val context: Context) {
val gossip = com.bitchat.android.services.meshgraph.GossipTLV.encodeNeighbors(directPeers) val gossip = com.bitchat.android.services.meshgraph.GossipTLV.encodeNeighbors(directPeers)
tlvPayload = tlvPayload + gossip tlvPayload = tlvPayload + gossip
} }
// Always update our own node in the mesh graph with the neighbor list we used
try {
com.bitchat.android.services.meshgraph.MeshGraphService.getInstance()
.updateFromAnnouncement(myPeerID, nickname, directPeers, System.currentTimeMillis().toULong())
} catch (_: Exception) { }
} catch (_: Exception) { } } catch (_: Exception) { }
val announcePacket = BitchatPacket( val announcePacket = BitchatPacket(
@@ -763,6 +768,11 @@ class BluetoothMeshService(private val context: Context) {
val gossip = com.bitchat.android.services.meshgraph.GossipTLV.encodeNeighbors(directPeers) val gossip = com.bitchat.android.services.meshgraph.GossipTLV.encodeNeighbors(directPeers)
tlvPayload = tlvPayload + gossip tlvPayload = tlvPayload + gossip
} }
// Always update our own node in the mesh graph with the neighbor list we used
try {
com.bitchat.android.services.meshgraph.MeshGraphService.getInstance()
.updateFromAnnouncement(myPeerID, nickname, directPeers, System.currentTimeMillis().toULong())
} catch (_: Exception) { }
} catch (_: Exception) { } } catch (_: Exception) { }
val packet = BitchatPacket( val packet = BitchatPacket(
@@ -240,11 +240,11 @@ class MessageHandler(private val myPeerID: String) {
previousPeerID = null previousPeerID = null
) )
// Update mesh graph from gossip neighbors (if present) // Update mesh graph from gossip neighbors (only if TLV present)
try { try {
val neighbors = com.bitchat.android.services.meshgraph.GossipTLV.decodeNeighborsFromAnnouncementPayload(packet.payload) val neighborsOrNull = com.bitchat.android.services.meshgraph.GossipTLV.decodeNeighborsFromAnnouncementPayload(packet.payload)
com.bitchat.android.services.meshgraph.MeshGraphService.getInstance() com.bitchat.android.services.meshgraph.MeshGraphService.getInstance()
.updateFromAnnouncement(peerID, nickname, neighbors, packet.timestamp) .updateFromAnnouncement(peerID, nickname, neighborsOrNull, packet.timestamp)
} catch (_: Exception) { } } catch (_: Exception) { }
Log.d(TAG, "✅ Processed verified TLV announce: stored identity for $peerID") Log.d(TAG, "✅ Processed verified TLV announce: stored identity for $peerID")
@@ -24,9 +24,10 @@ object GossipTLV {
} }
/** /**
* Scan a TLV-encoded announce payload and extract neighbor peerIDs if present. * Scan a TLV-encoded announce payload and extract neighbor peerIDs.
* Returns null if the TLV is not present at all; returns an empty list if present with length 0.
*/ */
fun decodeNeighborsFromAnnouncementPayload(payload: ByteArray): List<String> { fun decodeNeighborsFromAnnouncementPayload(payload: ByteArray): List<String>? {
val result = mutableListOf<String>() val result = mutableListOf<String>()
var offset = 0 var offset = 0
while (offset + 2 <= payload.size) { while (offset + 2 <= payload.size) {
@@ -45,10 +46,11 @@ object GossipTLV {
result.add(bytesToPeerIdHex(idBytes)) result.add(bytesToPeerIdHex(idBytes))
pos += 8 pos += 8
} }
break // only one neighbors TLV expected return result // present (possibly empty)
} }
} }
return result // Not present
return null
} }
private fun hexStringPeerIdTo8Bytes(hexString: String): ByteArray { private fun hexStringPeerIdTo8Bytes(hexString: String): ByteArray {
@@ -73,4 +75,3 @@ object GossipTLV {
return sb.toString() return sb.toString()
} }
} }
@@ -28,33 +28,40 @@ class MeshGraphService private constructor() {
* Update graph from a verified announcement. * Update graph from a verified announcement.
* Replaces previous neighbors for origin if this is newer (by timestamp). * Replaces previous neighbors for origin if this is newer (by timestamp).
*/ */
fun updateFromAnnouncement(originPeerID: String, originNickname: String?, neighbors: List<String>, timestamp: ULong) { fun updateFromAnnouncement(originPeerID: String, originNickname: String?, neighborsOrNull: List<String>?, timestamp: ULong) {
// Newer-only replacement per origin
val prevTs = lastUpdate[originPeerID]
if (prevTs != null && prevTs >= timestamp) return
lastUpdate[originPeerID] = timestamp
synchronized(this) { synchronized(this) {
// Update nickname // Always update nickname if provided
if (originNickname != null) nicknames[originPeerID] = originNickname if (originNickname != null) nicknames[originPeerID] = originNickname
// If no neighbors TLV present, do not modify edges or timestamps
if (neighborsOrNull == null) {
publishSnapshot()
return
}
// Newer-only replacement per origin (based on TLV-bearing announcements only)
val prevTs = lastUpdate[originPeerID]
if (prevTs != null && prevTs >= timestamp) {
// Older or equal TLV-bearing update: ignore
return
}
lastUpdate[originPeerID] = timestamp
// Remove old symmetric edges contributed by this origin // Remove old symmetric edges contributed by this origin
val prevNeighbors = adjacency[originPeerID]?.toSet().orEmpty() val prevNeighbors = adjacency[originPeerID]?.toSet().orEmpty()
prevNeighbors.forEach { n -> prevNeighbors.forEach { n ->
adjacency[n]?.remove(originPeerID) adjacency[n]?.remove(originPeerID)
} }
// Replace origin's adjacency with new set // Replace origin's adjacency with new set (may be empty)
val newSet = neighbors.distinct().take(10).filter { it != originPeerID }.toMutableSet() val newSet = neighborsOrNull.distinct().take(10).filter { it != originPeerID }.toMutableSet()
adjacency[originPeerID] = newSet adjacency[originPeerID] = newSet
// Ensure nodes exist for neighbors // Ensure undirected edges
newSet.forEach { n -> newSet.forEach { n ->
adjacency.putIfAbsent(n, mutableSetOf()) adjacency.putIfAbsent(n, mutableSetOf())
// Add symmetric edge for undirected graph visualization
adjacency[n]?.add(originPeerID) adjacency[n]?.add(originPeerID)
} }
// Compute snapshot
publishSnapshot() publishSnapshot()
} }
} }
@@ -93,4 +100,3 @@ class MeshGraphService private constructor() {
} }
} }
} }
@@ -26,6 +26,9 @@ import androidx.compose.ui.draw.rotate
import com.bitchat.android.mesh.BluetoothMeshService import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.services.meshgraph.MeshGraphService import com.bitchat.android.services.meshgraph.MeshGraphService
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
@Composable @Composable
fun MeshTopologySection() { fun MeshTopologySection() {
@@ -78,6 +81,22 @@ fun MeshTopologySection() {
val pos = positions[node.peerID] ?: androidx.compose.ui.geometry.Offset(cx, cy) val pos = positions[node.peerID] ?: androidx.compose.ui.geometry.Offset(cx, cy)
drawCircle(color = Color(0xFF00C851), radius = 10f, center = pos) drawCircle(color = Color(0xFF00C851), radius = 10f, center = pos)
} }
// Draw labels near nodes (nickname or short ID)
val labelColor = colorScheme.onSurface.toArgb()
val textSizePx = 10.sp.toPx()
drawIntoCanvas { canvas ->
val paint = android.graphics.Paint().apply {
isAntiAlias = true
color = labelColor
textSize = textSizePx
}
nodes.forEach { node ->
val pos = positions[node.peerID] ?: androidx.compose.ui.geometry.Offset(cx, cy)
val label = (node.nickname?.takeIf { it.isNotBlank() } ?: node.peerID.take(8))
canvas.nativeCanvas.drawText(label, pos.x + 12f, pos.y - 12f, paint)
}
}
} }
} }
// Label list for clarity under the canvas // Label list for clarity under the canvas
@@ -157,11 +176,6 @@ fun DebugSettingsSheet(
.padding(bottom = 24.dp), .padding(bottom = 24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
item {
// Mesh topology visualization
MeshTopologySection()
}
item { item {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Filled.BugReport, contentDescription = null, tint = Color(0xFFFF9500)) Icon(Icons.Filled.BugReport, contentDescription = null, tint = Color(0xFFFF9500))
@@ -195,6 +209,11 @@ fun DebugSettingsSheet(
} }
} }
// Mesh topology visualization (moved below verbose logging)
item {
MeshTopologySection()
}
// GATT controls // GATT controls
item { item {
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) { Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {