mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 00:45:22 +00:00
* feat(debug): add DebugSettingsManager + DebugSettingsSheet scaffold and AboutSheet entry; groundwork for verbose logging, GATT controls, relay stats, device/scan views * fix(debug): wire debug sheet launch via onShowDebug in AboutSheet from ChatScreen; add public start/stop server/client methods} * fix(about): remove misplaced debug item block inside PoW section; keep debug launcher managed by ChatScreen only * feat(debug): wire DebugSettingsSheet role switches to explicit startServer/stopServer and startClient/stopClient; expose role controls + disconnectAll on BluetoothConnectionManager; fix syntax error; build passes * feat(debug): add connect/disconnect helpers; wire DebugSettingsSheet connect/disconnect actions to BluetoothConnectionManager * feat(debug): add debug settings button at bottom of AboutSheet; wire onShowDebug callback to open DebugSettingsSheet from ChatScreen * feat(debug): wire verbose logging into chat view via DebugSettingsManager; add rolling relay stats; push connected devices and scan results; ensure GATT role stop closes connections; log incoming packets and relay events; add UI polling for devices; build passes * chore(debug-branch): remove unrelated files that were mistakenly added in first commit; keep only intended debug settings changes * fix(chat): prevent mesh→geohash leak Root cause: - DebugSettingsManager→chat bridge appended system logs to the global timeline regardless of active channel - MeshDelegateHandler added public mesh messages to UI unconditionally Fixes: - Only inject debug system messages when selected location channel is Mesh - Only add public mesh messages to UI when Mesh is selected (still send notifications) Build: ./gradlew assembleDebug } * fix(timeline): restore message persistence across channel switches Root cause: - NostrGeohashService.switchLocationChannel() called messageManager.clearMessages() which wiped ALL messages (mesh + debug + geohash) from main timeline - Geohash events were adding to main timeline, causing cross-contamination Fixes: - Remove messageManager.clearMessages() from channel switching - ChatScreen displayMessages now routes to separate storage: - Mesh: messages (main timeline, includes debug logs when Mesh selected) - Geohash: viewModel.getGeohashMessages(geohash) from separate history - Private: privateChats[peerID] - Channels: channelMessages[channel] - Geohash events no longer add to main timeline, only to geohash history - Mesh messages always stored to preserve history when switching away Result: Each chat type has persistent separate storage, no message loss * Debug/relay logs: use MessageType names; include device route in verbose packet logs. * Verbose device-peer assignment and connection/disconnection logs with peerId, deviceId, nickname; packet relay log uses MessageType names.} * Debug logs API: include nickname and deviceId for incoming/relay; update callers.} * Fix compile: import MessageType; correct debug manager API usage and remove stray insertion; build.} * Respect relay toggle; helper to log relay with deviceId.} * PacketRelayManager: add relay toggle and unified logging helper; clean file header comments; build fixes.} * changes * Geohash local echo: do not add to mesh timeline; rely on geohash history and ChatScreen display for location channels. * persisting * persist debug settings * gitignore * gatt server / client controls * debugger * max connection settings and graph * more graph * better logging * refactor logging
210 lines
7.3 KiB
Kotlin
210 lines
7.3 KiB
Kotlin
package com.bitchat.android.mesh
|
|
import com.bitchat.android.protocol.MessageType
|
|
|
|
import android.util.Log
|
|
import com.bitchat.android.model.RoutedPacket
|
|
import com.bitchat.android.protocol.BitchatPacket
|
|
import com.bitchat.android.util.toHexString
|
|
import kotlinx.coroutines.*
|
|
import kotlin.random.Random
|
|
|
|
/**
|
|
* Centralized packet relay management
|
|
*
|
|
* This class handles all relay decisions and logic for bitchat packets.
|
|
* All packets that aren't specifically addressed to us get processed here.
|
|
*/
|
|
class PacketRelayManager(private val myPeerID: String) {
|
|
private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } }
|
|
|
|
companion object {
|
|
private const val TAG = "PacketRelayManager"
|
|
}
|
|
|
|
private fun isRelayEnabled(): Boolean = try {
|
|
com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().packetRelayEnabled.value
|
|
} catch (_: Exception) { true }
|
|
|
|
// Logging moved to BluetoothPacketBroadcaster per actual transmission target
|
|
|
|
// Delegate for callbacks
|
|
var delegate: PacketRelayManagerDelegate? = null
|
|
|
|
// Coroutines
|
|
private val relayScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
|
|
/**
|
|
* Main entry point for relay decisions
|
|
* Only packets that aren't specifically addressed to us should be passed here
|
|
*/
|
|
suspend fun handlePacketRelay(routed: RoutedPacket) {
|
|
val packet = routed.packet
|
|
val peerID = routed.peerID ?: "unknown"
|
|
|
|
Log.d(TAG, "Evaluating relay for packet type ${'$'}{packet.type} from ${'$'}peerID (TTL: ${'$'}{packet.ttl})")
|
|
|
|
// Double-check this packet isn't addressed to us
|
|
if (isPacketAddressedToMe(packet)) {
|
|
Log.d(TAG, "Packet addressed to us, skipping relay")
|
|
return
|
|
}
|
|
|
|
// Skip our own packets
|
|
if (peerID == myPeerID) {
|
|
Log.d(TAG, "Packet from ourselves, skipping relay")
|
|
return
|
|
}
|
|
|
|
// Check TTL and decrement
|
|
if (packet.ttl == 0u.toUByte()) {
|
|
Log.d(TAG, "TTL expired, not relaying packet")
|
|
return
|
|
}
|
|
|
|
// Decrement TTL by 1
|
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
|
Log.d(TAG, "Decremented TTL from ${'$'}{packet.ttl} to ${'$'}{relayPacket.ttl}")
|
|
|
|
// Apply relay logic based on packet type and debug switch
|
|
val shouldRelay = isRelayEnabled() && shouldRelayPacket(relayPacket, peerID)
|
|
|
|
if (shouldRelay) {
|
|
relayPacket(RoutedPacket(relayPacket, peerID, routed.relayAddress))
|
|
} else {
|
|
Log.d(TAG, "Relay decision: NOT relaying packet type ${'$'}{packet.type}")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a packet is specifically addressed to us
|
|
*/
|
|
internal fun isPacketAddressedToMe(packet: BitchatPacket): Boolean {
|
|
val recipientID = packet.recipientID
|
|
|
|
// No recipient means broadcast (not addressed to us specifically)
|
|
if (recipientID == null) {
|
|
return false
|
|
}
|
|
|
|
// Check if it's a broadcast recipient
|
|
val broadcastRecipient = delegate?.getBroadcastRecipient()
|
|
if (broadcastRecipient != null && recipientID.contentEquals(broadcastRecipient)) {
|
|
return false
|
|
}
|
|
|
|
// Check if recipient matches our peer ID
|
|
val recipientIDString = recipientID.toHexString()
|
|
return recipientIDString == myPeerID
|
|
}
|
|
|
|
/**
|
|
* Determine if we should relay this packet based on type and network conditions
|
|
*/
|
|
private fun shouldRelayPacket(packet: BitchatPacket, fromPeerID: String): Boolean {
|
|
// Always relay if TTL is high enough (indicates important message)
|
|
if (packet.ttl >= 4u) {
|
|
Log.d(TAG, "High TTL (${ '$' }{packet.ttl}), relaying")
|
|
return true
|
|
}
|
|
|
|
// Get network size for adaptive relay probability
|
|
val networkSize = delegate?.getNetworkSize() ?: 1
|
|
|
|
// Small networks always relay to ensure connectivity
|
|
if (networkSize <= 3) {
|
|
Log.d(TAG, "Small network (${ '$' }networkSize peers), relaying")
|
|
return true
|
|
}
|
|
|
|
// Apply adaptive relay probability based on network size
|
|
val relayProb = when {
|
|
networkSize <= 10 -> 1.0 // Always relay in small networks
|
|
networkSize <= 30 -> 0.85 // High probability for medium networks
|
|
networkSize <= 50 -> 0.7 // Moderate probability
|
|
networkSize <= 100 -> 0.55 // Lower probability for large networks
|
|
else -> 0.4 // Lowest probability for very large networks
|
|
}
|
|
|
|
val shouldRelay = Random.nextDouble() < relayProb
|
|
Log.d(TAG, "Network size: ${'$'}networkSize, Relay probability: ${'$'}relayProb, Decision: ${'$'}shouldRelay")
|
|
|
|
return shouldRelay
|
|
}
|
|
|
|
/**
|
|
* Relay message with adaptive probability and timing (same as iOS)
|
|
* Moved from MessageHandler.kt
|
|
*/
|
|
suspend fun relayMessage(routed: RoutedPacket) {
|
|
val packet = routed.packet
|
|
|
|
if (packet.ttl == 0u.toUByte()) {
|
|
Log.d(TAG, "TTL expired, not relaying message")
|
|
return
|
|
}
|
|
|
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
|
|
|
// Check network size and apply adaptive relay probability
|
|
val networkSize = delegate?.getNetworkSize() ?: 1
|
|
val relayProb = when {
|
|
networkSize <= 10 -> 1.0
|
|
networkSize <= 30 -> 0.85
|
|
networkSize <= 50 -> 0.7
|
|
networkSize <= 100 -> 0.55
|
|
else -> 0.4
|
|
}
|
|
|
|
val shouldRelay = relayPacket.ttl >= 4u || networkSize <= 3 || Random.nextDouble() < relayProb
|
|
|
|
if (shouldRelay) {
|
|
val delay = Random.nextLong(50, 500) // Random delay like iOS
|
|
Log.d(TAG, "Relaying message after ${'$'}delay ms delay")
|
|
delay(delay)
|
|
relayPacket(routed.copy(packet = relayPacket))
|
|
} else {
|
|
Log.d(TAG, "Relay decision: NOT relaying message (network size: ${'$'}networkSize, prob: ${'$'}relayProb)")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Actually broadcast the packet for relay
|
|
*/
|
|
private fun relayPacket(routed: RoutedPacket) {
|
|
Log.d(TAG, "🔄 Relaying packet type ${'$'}{routed.packet.type} with TTL ${'$'}{routed.packet.ttl}")
|
|
delegate?.broadcastPacket(routed)
|
|
}
|
|
|
|
/**
|
|
* Get debug information
|
|
*/
|
|
fun getDebugInfo(): String {
|
|
return buildString {
|
|
appendLine("=== Packet Relay Manager Debug Info ===")
|
|
appendLine("Relay Scope Active: ${'$'}{relayScope.isActive}")
|
|
appendLine("My Peer ID: ${'$'}myPeerID")
|
|
appendLine("Network Size: ${'$'}{delegate?.getNetworkSize() ?: \"unknown\"}")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shutdown the relay manager
|
|
*/
|
|
fun shutdown() {
|
|
Log.d(TAG, "Shutting down PacketRelayManager")
|
|
relayScope.cancel()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delegate interface for packet relay manager callbacks
|
|
*/
|
|
interface PacketRelayManagerDelegate {
|
|
// Network information
|
|
fun getNetworkSize(): Int
|
|
fun getBroadcastRecipient(): ByteArray
|
|
|
|
// Packet operations
|
|
fun broadcastPacket(routed: RoutedPacket)
|
|
}
|