refactor mesh core

This commit is contained in:
callebtc
2026-01-04 14:05:14 +07:00
parent 23e7e12e9e
commit 7d6a581544
10 changed files with 1222 additions and 1513 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,716 @@
package com.bitchat.android.mesh
import android.content.Context
import android.util.Log
import com.bitchat.android.crypto.EncryptionService
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.BitchatFilePacket
import com.bitchat.android.model.IdentityAnnouncement
import com.bitchat.android.model.NoisePayload
import com.bitchat.android.model.NoisePayloadType
import com.bitchat.android.model.PrivateMessagePacket
import com.bitchat.android.model.RequestSyncPacket
import com.bitchat.android.model.RoutedPacket
import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.MessageType
import com.bitchat.android.protocol.SpecialRecipients
import com.bitchat.android.service.TransportBridgeService
import com.bitchat.android.sync.GossipSyncManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
/**
* Shared mesh coordinator that wires all mesh-layer components and provides common APIs
* for send/receive operations across transports.
*/
class MeshCore(
private val context: Context,
private val scope: CoroutineScope,
private val transport: MeshTransport,
private val encryptionService: EncryptionService,
val myPeerID: String,
private val maxTtl: UByte,
sharedGossipManager: GossipSyncManager?,
gossipConfigProvider: GossipSyncManager.ConfigProvider,
private val hooks: Hooks = Hooks()
) {
data class Hooks(
val onMessageReceived: ((BitchatMessage) -> Unit)? = null,
val onPeerIdBindingUpdated: ((String, String, ByteArray, String?) -> Unit)? = null,
val onAnnounceProcessed: ((RoutedPacket, Boolean) -> Unit)? = null,
val readReceiptInterceptor: ((String, String) -> Boolean)? = null,
val onReadReceiptSent: ((String) -> Unit)? = null,
val announcementNicknameProvider: (() -> String?)? = null,
val leavePayloadProvider: (() -> ByteArray)? = null
)
private val peerManager = PeerManager()
val fragmentManager = FragmentManager()
private val securityManager = SecurityManager(encryptionService, myPeerID)
private val storeForwardManager = StoreForwardManager()
private val messageHandler = MessageHandler(myPeerID, context.applicationContext)
private val packetProcessor = PacketProcessor(myPeerID)
val gossipSyncManager: GossipSyncManager =
sharedGossipManager ?: GossipSyncManager(myPeerID = myPeerID, scope = scope, configProvider = gossipConfigProvider)
var delegate: MeshDelegate? = null
private var announceJob: Job? = null
private var isActive = false
init {
messageHandler.packetProcessor = packetProcessor
setupDelegates()
if (sharedGossipManager == null) {
gossipSyncManager.delegate = object : GossipSyncManager.Delegate {
override fun sendPacket(packet: BitchatPacket) {
dispatchGlobal(RoutedPacket(packet))
}
override fun sendPacketToPeer(peerID: String, packet: BitchatPacket) {
transport.sendPacketToPeer(peerID, packet)
}
override fun signPacketForBroadcast(packet: BitchatPacket): BitchatPacket {
return signPacketBeforeBroadcast(packet)
}
}
}
}
fun startCore() {
if (isActive) return
isActive = true
startPeriodicBroadcastAnnounce()
gossipSyncManager.start()
}
fun stopCore() {
if (!isActive) return
isActive = false
announceJob?.cancel()
announceJob = null
gossipSyncManager.stop()
}
fun shutdown() {
peerManager.shutdown()
fragmentManager.shutdown()
securityManager.shutdown()
storeForwardManager.shutdown()
messageHandler.shutdown()
packetProcessor.shutdown()
}
fun processIncoming(packet: BitchatPacket, peerID: String?, relayAddress: String?) {
packetProcessor.processPacket(RoutedPacket(packet, peerID, relayAddress))
}
fun sendFromBridge(packet: RoutedPacket) {
transport.broadcastPacket(packet)
}
private fun dispatchGlobal(routed: RoutedPacket) {
transport.broadcastPacket(routed)
TransportBridgeService.broadcast(transport.id, routed)
}
private fun startPeriodicBroadcastAnnounce() {
announceJob?.cancel()
announceJob = scope.launch {
while (isActive) {
try {
delay(30_000)
sendBroadcastAnnounce()
} catch (_: Exception) { }
}
}
}
private fun setupDelegates() {
peerManager.delegate = object : PeerManagerDelegate {
override fun onPeerListUpdated(peerIDs: List<String>) {
delegate?.didUpdatePeerList(peerIDs)
}
override fun onPeerRemoved(peerID: String) {
try { gossipSyncManager.removeAnnouncementForPeer(peerID) } catch (_: Exception) { }
try { encryptionService.removePeer(peerID) } catch (_: Exception) { }
}
}
securityManager.delegate = object : SecurityManagerDelegate {
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) {
scope.launch {
delay(100)
sendAnnouncementToPeer(peerID)
delay(1000)
storeForwardManager.sendCachedMessages(peerID)
}
}
override fun sendHandshakeResponse(peerID: String, response: ByteArray) {
val responsePacket = BitchatPacket(
version = 1u,
type = MessageType.NOISE_HANDSHAKE.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = MeshPacketUtils.hexStringToByteArray(peerID),
timestamp = System.currentTimeMillis().toULong(),
payload = response,
ttl = maxTtl
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(responsePacket)))
}
override fun getPeerInfo(peerID: String): PeerInfo? = peerManager.getPeerInfo(peerID)
}
storeForwardManager.delegate = object : StoreForwardManagerDelegate {
override fun isFavorite(peerID: String): Boolean {
return delegate?.isFavorite(peerID) ?: false
}
override fun isPeerOnline(peerID: String): Boolean {
return peerManager.isPeerActive(peerID)
}
override fun sendPacket(packet: BitchatPacket) {
dispatchGlobal(RoutedPacket(packet))
}
}
messageHandler.delegate = object : MessageHandlerDelegate {
override fun addOrUpdatePeer(peerID: String, nickname: String): Boolean {
return peerManager.addOrUpdatePeer(peerID, nickname)
}
override fun removePeer(peerID: String) {
peerManager.removePeer(peerID)
}
override fun updatePeerNickname(peerID: String, nickname: String) {
peerManager.addOrUpdatePeer(peerID, nickname)
}
override fun getPeerNickname(peerID: String): String? {
return peerManager.getPeerNickname(peerID)
}
override fun getNetworkSize(): Int {
return peerManager.getActivePeerCount()
}
override fun getMyNickname(): String? {
return delegate?.getNickname()
}
override fun getPeerInfo(peerID: String): PeerInfo? {
return peerManager.getPeerInfo(peerID)
}
override fun updatePeerInfo(
peerID: String,
nickname: String,
noisePublicKey: ByteArray,
signingPublicKey: ByteArray,
isVerified: Boolean
): Boolean {
return peerManager.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified)
}
override fun sendPacket(packet: BitchatPacket) {
val signedPacket = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signedPacket))
}
override fun relayPacket(routed: RoutedPacket) {
dispatchGlobal(routed)
}
override fun getBroadcastRecipient(): ByteArray {
return SpecialRecipients.BROADCAST
}
override fun verifySignature(packet: BitchatPacket, peerID: String): Boolean {
return securityManager.verifySignature(packet, peerID)
}
override fun encryptForPeer(data: ByteArray, recipientPeerID: String): ByteArray? {
return securityManager.encryptForPeer(data, recipientPeerID)
}
override fun decryptFromPeer(encryptedData: ByteArray, senderPeerID: String): ByteArray? {
return securityManager.decryptFromPeer(encryptedData, senderPeerID)
}
override fun verifyEd25519Signature(signature: ByteArray, data: ByteArray, publicKey: ByteArray): Boolean {
return encryptionService.verifyEd25519Signature(signature, data, publicKey)
}
override fun hasNoiseSession(peerID: String): Boolean {
return encryptionService.hasEstablishedSession(peerID)
}
override fun initiateNoiseHandshake(peerID: String) {
this@MeshCore.initiateNoiseHandshake(peerID)
}
override fun processNoiseHandshakeMessage(payload: ByteArray, peerID: String): ByteArray? {
return try {
encryptionService.processHandshakeMessage(payload, peerID)
} catch (_: Exception) {
null
}
}
override fun updatePeerIDBinding(
newPeerID: String,
nickname: String,
publicKey: ByteArray,
previousPeerID: String?
) {
peerManager.addOrUpdatePeer(newPeerID, nickname)
val fingerprint = peerManager.storeFingerprintForPeer(newPeerID, publicKey)
previousPeerID?.let { peerManager.removePeer(it) }
Log.d("MeshCore", "Updated peer ID binding: $newPeerID fp=${fingerprint.take(16)}")
hooks.onPeerIdBindingUpdated?.invoke(newPeerID, nickname, publicKey, previousPeerID)
}
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
return delegate?.decryptChannelMessage(encryptedContent, channel)
}
override fun onMessageReceived(message: BitchatMessage) {
hooks.onMessageReceived?.invoke(message)
delegate?.didReceiveMessage(message)
}
override fun onChannelLeave(channel: String, fromPeer: String) {
delegate?.didReceiveChannelLeave(channel, fromPeer)
}
override fun onDeliveryAckReceived(messageID: String, peerID: String) {
delegate?.didReceiveDeliveryAck(messageID, peerID)
}
override fun onReadReceiptReceived(messageID: String, peerID: String) {
delegate?.didReceiveReadReceipt(messageID, peerID)
}
}
packetProcessor.delegate = object : PacketProcessorDelegate {
override fun validatePacketSecurity(packet: BitchatPacket, peerID: String): Boolean {
return securityManager.validatePacket(packet, peerID)
}
override fun updatePeerLastSeen(peerID: String) {
peerManager.updatePeerLastSeen(peerID)
}
override fun getPeerNickname(peerID: String): String? {
return peerManager.getPeerNickname(peerID)
}
override fun getNetworkSize(): Int {
return peerManager.getActivePeerCount()
}
override fun getBroadcastRecipient(): ByteArray {
return SpecialRecipients.BROADCAST
}
override fun handleNoiseHandshake(routed: RoutedPacket): Boolean {
return runBlocking { securityManager.handleNoiseHandshake(routed) }
}
override fun handleNoiseEncrypted(routed: RoutedPacket) {
scope.launch { messageHandler.handleNoiseEncrypted(routed) }
}
override fun handleAnnounce(routed: RoutedPacket) {
scope.launch {
val isFirst = messageHandler.handleAnnounce(routed)
hooks.onAnnounceProcessed?.invoke(routed, isFirst)
try { gossipSyncManager.onPublicPacketSeen(routed.packet) } catch (_: Exception) { }
}
}
override fun handleMessage(routed: RoutedPacket) {
scope.launch { messageHandler.handleMessage(routed) }
try {
val pkt = routed.packet
val isBroadcast = (pkt.recipientID == null || pkt.recipientID.contentEquals(SpecialRecipients.BROADCAST))
if (isBroadcast && pkt.type == MessageType.MESSAGE.value) {
gossipSyncManager.onPublicPacketSeen(pkt)
}
} catch (_: Exception) { }
}
override fun handleLeave(routed: RoutedPacket) {
scope.launch { messageHandler.handleLeave(routed) }
}
override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
try {
val isBroadcast = (packet.recipientID == null || packet.recipientID.contentEquals(SpecialRecipients.BROADCAST))
if (isBroadcast && packet.type == MessageType.FRAGMENT.value) {
gossipSyncManager.onPublicPacketSeen(packet)
}
} catch (_: Exception) { }
return fragmentManager.handleFragment(packet)
}
override fun sendAnnouncementToPeer(peerID: String) {
this@MeshCore.sendAnnouncementToPeer(peerID)
}
override fun sendCachedMessages(peerID: String) {
storeForwardManager.sendCachedMessages(peerID)
}
override fun relayPacket(routed: RoutedPacket) {
dispatchGlobal(routed)
}
override fun handleRequestSync(routed: RoutedPacket) {
val fromPeer = routed.peerID ?: return
val req = RequestSyncPacket.decode(routed.packet.payload) ?: return
gossipSyncManager.handleRequestSync(fromPeer, req)
}
}
}
fun sendMessage(content: String, mentions: List<String> = emptyList(), channel: String? = null) {
if (content.isEmpty()) return
scope.launch {
val packet = BitchatPacket(
version = 1u,
type = MessageType.MESSAGE.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = SpecialRecipients.BROADCAST,
timestamp = System.currentTimeMillis().toULong(),
payload = content.toByteArray(Charsets.UTF_8),
signature = null,
ttl = maxTtl
)
val signedPacket = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signedPacket))
try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { }
}
}
fun sendFileBroadcast(file: BitchatFilePacket) {
try {
val payload = file.encode() ?: return
scope.launch {
val packet = BitchatPacket(
version = 2u,
type = MessageType.FILE_TRANSFER.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = SpecialRecipients.BROADCAST,
timestamp = System.currentTimeMillis().toULong(),
payload = payload,
signature = null,
ttl = maxTtl
)
val signed = signPacketBeforeBroadcast(packet)
val transferId = MeshPacketUtils.sha256Hex(payload)
dispatchGlobal(RoutedPacket(signed, transferId = transferId))
try { gossipSyncManager.onPublicPacketSeen(signed) } catch (_: Exception) { }
}
} catch (e: Exception) {
Log.e("MeshCore", "sendFileBroadcast failed: ${e.message}", e)
}
}
fun sendFilePrivate(recipientPeerID: String, file: BitchatFilePacket) {
try {
scope.launch {
if (!encryptionService.hasEstablishedSession(recipientPeerID)) {
initiateNoiseHandshake(recipientPeerID)
return@launch
}
val tlv = file.encode() ?: return@launch
val np = NoisePayload(type = NoisePayloadType.FILE_TRANSFER, data = tlv).encode()
val enc = encryptionService.encrypt(np, recipientPeerID)
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_ENCRYPTED.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = MeshPacketUtils.hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = enc,
signature = null,
ttl = maxTtl
)
val signed = signPacketBeforeBroadcast(packet)
val transferId = MeshPacketUtils.sha256Hex(tlv)
dispatchGlobal(RoutedPacket(signed, transferId = transferId))
}
} catch (e: Exception) {
Log.e("MeshCore", "sendFilePrivate failed: ${e.message}", e)
}
}
fun cancelFileTransfer(transferId: String): Boolean {
return transport.cancelTransfer(transferId)
}
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) {
if (content.isEmpty() || recipientPeerID.isEmpty()) return
scope.launch {
val finalMessageID = messageID ?: java.util.UUID.randomUUID().toString()
if (encryptionService.hasEstablishedSession(recipientPeerID)) {
try {
val privateMessage = PrivateMessagePacket(messageID = finalMessageID, content = content)
val tlvData = privateMessage.encode() ?: return@launch
val messagePayload = NoisePayload(
type = NoisePayloadType.PRIVATE_MESSAGE,
data = tlvData
)
val encrypted = encryptionService.encrypt(messagePayload.encode(), recipientPeerID)
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_ENCRYPTED.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = MeshPacketUtils.hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = encrypted,
signature = null,
ttl = maxTtl
)
val signedPacket = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signedPacket))
} catch (e: Exception) {
Log.e("MeshCore", "Failed to encrypt private message: ${e.message}")
}
} else {
initiateNoiseHandshake(recipientPeerID)
}
}
}
fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) {
scope.launch {
if (hooks.readReceiptInterceptor?.invoke(messageID, recipientPeerID) == true) return@launch
try {
val payload = NoisePayload(
type = NoisePayloadType.READ_RECEIPT,
data = messageID.toByteArray(Charsets.UTF_8)
).encode()
val enc = encryptionService.encrypt(payload, recipientPeerID)
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_ENCRYPTED.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = MeshPacketUtils.hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = enc,
signature = null,
ttl = maxTtl
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(packet)))
hooks.onReadReceiptSent?.invoke(messageID)
} catch (e: Exception) {
Log.e("MeshCore", "Failed to send read receipt: ${e.message}")
}
}
}
fun sendBroadcastAnnounce() {
scope.launch {
val nickname = hooks.announcementNicknameProvider?.invoke()
?: delegate?.getNickname()
?: myPeerID
val staticKey = encryptionService.getStaticPublicKey() ?: run {
Log.e("MeshCore", "No static public key available for announcement")
return@launch
}
val signingKey = encryptionService.getSigningPublicKey() ?: run {
Log.e("MeshCore", "No signing public key available for announcement")
return@launch
}
val announcement = IdentityAnnouncement(nickname, staticKey, signingKey)
val tlvPayload = announcement.encode() ?: return@launch
val announcePacket = BitchatPacket(
type = MessageType.ANNOUNCE.value,
ttl = maxTtl,
senderID = myPeerID,
payload = tlvPayload
)
val signedPacket = signPacketBeforeBroadcast(announcePacket)
dispatchGlobal(RoutedPacket(signedPacket))
try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { }
}
}
fun sendAnnouncementToPeer(peerID: String) {
if (peerManager.hasAnnouncedToPeer(peerID)) return
val nickname = hooks.announcementNicknameProvider?.invoke()
?: delegate?.getNickname()
?: myPeerID
val staticKey = encryptionService.getStaticPublicKey() ?: return
val signingKey = encryptionService.getSigningPublicKey() ?: return
val announcement = IdentityAnnouncement(nickname, staticKey, signingKey)
val tlvPayload = announcement.encode() ?: return
val packet = BitchatPacket(
type = MessageType.ANNOUNCE.value,
ttl = maxTtl,
senderID = myPeerID,
payload = tlvPayload
)
val signedPacket = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signedPacket))
peerManager.markPeerAsAnnouncedTo(peerID)
try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { }
}
fun sendLeaveAnnouncement() {
val payload = hooks.leavePayloadProvider?.invoke() ?: byteArrayOf()
val packet = BitchatPacket(
type = MessageType.LEAVE.value,
ttl = maxTtl,
senderID = myPeerID,
payload = payload
)
val signedPacket = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signedPacket))
}
fun getPeerNicknames(): Map<String, String> = peerManager.getAllPeerNicknames()
fun getPeerRSSI(): Map<String, Int> = peerManager.getAllPeerRSSI()
fun getPeerNickname(peerID: String): String? = peerManager.getPeerNickname(peerID)
fun addOrUpdatePeer(peerID: String, nickname: String): Boolean {
return peerManager.addOrUpdatePeer(peerID, nickname)
}
fun removePeer(peerID: String) {
peerManager.removePeer(peerID)
}
fun setDirectConnection(peerID: String, isDirect: Boolean) {
peerManager.setDirectConnection(peerID, isDirect)
}
fun updatePeerRSSI(peerID: String, rssi: Int) {
peerManager.updatePeerRSSI(peerID, rssi)
}
fun getDebugInfoWithDeviceAddresses(deviceMap: Map<String, String>): String {
return peerManager.getDebugInfoWithDeviceAddresses(deviceMap)
}
fun getFingerprintDebugInfo(): String {
return peerManager.getFingerprintDebugInfo()
}
fun hasEstablishedSession(peerID: String): Boolean {
return encryptionService.hasEstablishedSession(peerID)
}
fun getSessionState(peerID: String): com.bitchat.android.noise.NoiseSession.NoiseSessionState {
return encryptionService.getSessionState(peerID)
}
fun initiateNoiseHandshake(peerID: String) {
scope.launch {
try {
val handshakeData = encryptionService.initiateHandshake(peerID) ?: return@launch
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_HANDSHAKE.value,
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
recipientID = MeshPacketUtils.hexStringToByteArray(peerID),
timestamp = System.currentTimeMillis().toULong(),
payload = handshakeData,
ttl = maxTtl
)
val signedPacket = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signedPacket))
} catch (e: Exception) {
Log.e("MeshCore", "Failed to initiate Noise handshake with $peerID: ${e.message}")
}
}
}
fun getPeerFingerprint(peerID: String): String? = peerManager.getFingerprintForPeer(peerID)
fun getPeerInfo(peerID: String): PeerInfo? = peerManager.getPeerInfo(peerID)
fun updatePeerInfo(
peerID: String,
nickname: String,
noisePublicKey: ByteArray,
signingPublicKey: ByteArray,
isVerified: Boolean
): Boolean = peerManager.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified)
fun getIdentityFingerprint(): String = encryptionService.getIdentityFingerprint()
fun shouldShowEncryptionIcon(peerID: String): Boolean = encryptionService.hasEstablishedSession(peerID)
fun getEncryptedPeers(): List<String> = emptyList()
fun getActivePeerCount(): Int = try { peerManager.getActivePeerCount() } catch (_: Exception) { 0 }
fun getDeviceAddressForPeer(peerID: String): String? = transport.getDeviceAddressForPeer(peerID)
fun getDeviceAddressToPeerMapping(): Map<String, String> = transport.getDeviceAddressToPeerMapping()
fun getDebugStatus(
transportInfo: String,
deviceMap: Map<String, String>,
extraLines: List<String> = emptyList(),
title: String? = null
): String {
return buildString {
appendLine("=== ${title ?: "${transport.id} Mesh Debug Status"} ===")
appendLine("My Peer ID: $myPeerID")
if (extraLines.isNotEmpty()) {
extraLines.forEach { appendLine(it) }
}
appendLine(transportInfo)
appendLine(peerManager.getDebugInfo(deviceMap))
appendLine(fragmentManager.getDebugInfo())
appendLine(securityManager.getDebugInfo())
appendLine(storeForwardManager.getDebugInfo())
appendLine(messageHandler.getDebugInfo())
appendLine(packetProcessor.getDebugInfo())
}
}
fun clearAllInternalData() {
fragmentManager.clearAllFragments()
storeForwardManager.clearAllCache()
securityManager.clearAllData()
peerManager.clearAllPeers()
peerManager.clearAllFingerprints()
}
fun clearAllEncryptionData() {
encryptionService.clearPersistentIdentity()
}
private fun signPacketBeforeBroadcast(packet: BitchatPacket): BitchatPacket {
return try {
val packetDataForSigning = packet.toBinaryDataForSigning() ?: return packet
val signature = encryptionService.signData(packetDataForSigning)
if (signature != null) {
packet.copy(signature = signature)
} else {
packet
}
} catch (_: Exception) {
packet
}
}
}
@@ -0,0 +1,19 @@
package com.bitchat.android.mesh
import com.bitchat.android.model.BitchatMessage
/**
* Shared mesh delegate interface for transport-agnostic callbacks.
*/
interface MeshDelegate {
fun didReceiveMessage(message: BitchatMessage)
fun didUpdatePeerList(peers: List<String>)
fun didReceiveChannelLeave(channel: String, fromPeer: String)
fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String)
fun didReceiveReadReceipt(messageID: String, recipientPeerID: String)
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
fun getNickname(): String?
fun isFavorite(peerID: String): Boolean
}
typealias BluetoothMeshDelegate = MeshDelegate
@@ -0,0 +1,37 @@
package com.bitchat.android.mesh
/**
* Shared helpers for mesh packet handling.
*/
object MeshPacketUtils {
/**
* Convert hex string peer ID to binary data (8 bytes), matching iOS behavior.
*/
fun hexStringToByteArray(hexString: String): ByteArray {
val result = ByteArray(8) { 0 }
var tempID = hexString
var index = 0
while (tempID.length >= 2 && index < 8) {
val hexByte = tempID.substring(0, 2)
val byte = hexByte.toIntOrNull(16)?.toByte()
if (byte != null) {
result[index] = byte
}
tempID = tempID.substring(2)
index++
}
return result
}
/**
* Hash payloads to a stable hex ID for transfer tracking.
*/
fun sha256Hex(bytes: ByteArray): String = try {
val md = java.security.MessageDigest.getInstance("SHA-256")
md.update(bytes)
md.digest().joinToString("") { "%02x".format(it) }
} catch (_: Exception) {
bytes.size.toString(16)
}
}
@@ -0,0 +1,51 @@
package com.bitchat.android.mesh
import com.bitchat.android.model.BitchatFilePacket
/**
* Transport-agnostic mesh service API for UI and routing layers.
*/
interface MeshService {
val myPeerID: String
var delegate: MeshDelegate?
fun startServices()
fun stopServices()
fun sendMessage(content: String, mentions: List<String> = emptyList(), channel: String? = null)
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null)
fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String)
fun sendFileBroadcast(file: BitchatFilePacket)
fun sendFilePrivate(recipientPeerID: String, file: BitchatFilePacket)
fun cancelFileTransfer(transferId: String): Boolean
fun sendBroadcastAnnounce()
fun sendAnnouncementToPeer(peerID: String)
fun getPeerNicknames(): Map<String, String>
fun getPeerRSSI(): Map<String, Int>
fun getActivePeerCount(): Int
fun hasEstablishedSession(peerID: String): Boolean
fun getSessionState(peerID: String): com.bitchat.android.noise.NoiseSession.NoiseSessionState
fun initiateNoiseHandshake(peerID: String)
fun getPeerFingerprint(peerID: String): String?
fun getPeerInfo(peerID: String): PeerInfo?
fun updatePeerInfo(
peerID: String,
nickname: String,
noisePublicKey: ByteArray,
signingPublicKey: ByteArray,
isVerified: Boolean
): Boolean
fun getIdentityFingerprint(): String
fun shouldShowEncryptionIcon(peerID: String): Boolean
fun getEncryptedPeers(): List<String>
fun getDeviceAddressForPeer(peerID: String): String?
fun getDeviceAddressToPeerMapping(): Map<String, String>
fun printDeviceAddressesForPeers(): String
fun getDebugStatus(): String
fun clearAllInternalData()
fun clearAllEncryptionData()
}
@@ -0,0 +1,23 @@
package com.bitchat.android.mesh
import com.bitchat.android.model.RoutedPacket
import com.bitchat.android.protocol.BitchatPacket
/**
* Transport abstraction used by MeshCore to send packets via a specific medium.
*/
interface MeshTransport {
val id: String
fun broadcastPacket(routed: RoutedPacket)
fun sendPacketToPeer(peerID: String, packet: BitchatPacket)
fun cancelTransfer(transferId: String): Boolean = false
fun getDeviceAddressForPeer(peerID: String): String? = null
fun getDeviceAddressToPeerMapping(): Map<String, String> = emptyMap()
fun getTransportDebugInfo(): String = ""
}
@@ -130,7 +130,7 @@ class MessageRouter private constructor(
val myNpub = try { com.bitchat.android.nostr.NostrIdentityBridge.getCurrentNostrIdentity(context)?.npub } catch (_: Exception) { null } val myNpub = try { com.bitchat.android.nostr.NostrIdentityBridge.getCurrentNostrIdentity(context)?.npub } catch (_: Exception) { null }
val content = if (isFavorite) "[FAVORITED]:${myNpub ?: ""}" else "[UNFAVORITED]:${myNpub ?: ""}" val content = if (isFavorite) "[FAVORITED]:${myNpub ?: ""}" else "[UNFAVORITED]:${myNpub ?: ""}"
val nickname = mesh.getPeerNicknames()[toPeerID] ?: toPeerID val nickname = mesh.getPeerNicknames()[toPeerID] ?: toPeerID
mesh.sendPrivateMessage(content, toPeerID, nickname) mesh.sendPrivateMessage(content, toPeerID, nickname, null)
} else { } else {
nostr.sendFavoriteNotification(toPeerID, isFavorite) nostr.sendFavoriteNotification(toPeerID, isFavorite)
} }
@@ -324,7 +324,7 @@ class ChatViewModel(
fun leaveChannel(channel: String) { fun leaveChannel(channel: String) {
channelManager.leaveChannel(channel) channelManager.leaveChannel(channel)
meshService.sendMessage("left $channel") meshService.sendMessage("left $channel", emptyList(), null)
} }
// MARK: - Private Chat Management (delegated) // MARK: - Private Chat Management (delegated)
@@ -0,0 +1,3 @@
package com.bitchat.android.wifiaware
typealias WifiAwareMeshDelegate = com.bitchat.android.mesh.MeshDelegate
@@ -12,32 +12,29 @@ import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.crypto.EncryptionService
import com.bitchat.android.model.* import com.bitchat.android.mesh.MeshCore
import com.bitchat.android.protocol.* import com.bitchat.android.mesh.MeshService
import com.bitchat.android.mesh.MeshTransport
import com.bitchat.android.mesh.PeerInfo
import com.bitchat.android.model.BitchatFilePacket
import com.bitchat.android.model.RoutedPacket
import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.service.TransportBridgeService import com.bitchat.android.service.TransportBridgeService
import com.bitchat.android.sync.GossipSyncManager import com.bitchat.android.sync.GossipSyncManager
import com.bitchat.android.util.toHexString import com.bitchat.android.util.toHexString
// Mesh-layer components are reused from the existing Bluetooth stack import kotlinx.coroutines.CoroutineScope
import com.bitchat.android.mesh.PeerManager import kotlinx.coroutines.Dispatchers
import com.bitchat.android.mesh.PeerManagerDelegate import kotlinx.coroutines.SupervisorJob
import com.bitchat.android.mesh.PeerInfo import kotlinx.coroutines.cancel
import com.bitchat.android.mesh.FragmentManager import kotlinx.coroutines.delay
import com.bitchat.android.mesh.SecurityManager import kotlinx.coroutines.isActive
import com.bitchat.android.mesh.SecurityManagerDelegate import kotlinx.coroutines.launch
import com.bitchat.android.mesh.StoreForwardManager
import com.bitchat.android.mesh.StoreForwardManagerDelegate
import com.bitchat.android.mesh.MessageHandler
import com.bitchat.android.mesh.MessageHandlerDelegate
import com.bitchat.android.mesh.PacketProcessor
import com.bitchat.android.mesh.PacketProcessorDelegate
import kotlinx.coroutines.*
import java.io.IOException import java.io.IOException
import java.net.Inet6Address import java.net.Inet6Address
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors import java.util.concurrent.Executors
@@ -52,7 +49,7 @@ import java.util.concurrent.Executors
* - MessageHandler: Message type processing and relay logic * - MessageHandler: Message type processing and relay logic
* - PacketProcessor: Incoming packet routing * - PacketProcessor: Incoming packet routing
*/ */
class WifiAwareMeshService(private val context: Context) : TransportBridgeService.TransportLayer { class WifiAwareMeshService(private val context: Context) : MeshService, TransportBridgeService.TransportLayer {
companion object { companion object {
private const val TAG = "WifiAwareMeshService" private const val TAG = "WifiAwareMeshService"
@@ -65,18 +62,10 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
private val encryptionService = EncryptionService(context) private val encryptionService = EncryptionService(context)
// Peer ID must match BluetoothMeshService: first 16 hex chars of identity fingerprint (8 bytes) // Peer ID must match BluetoothMeshService: first 16 hex chars of identity fingerprint (8 bytes)
val myPeerID: String = encryptionService.getIdentityFingerprint().take(16) override val myPeerID: String = encryptionService.getIdentityFingerprint().take(16)
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// Core components private val wifiTransport = WifiAwareTransport()
private val peerManager = PeerManager() private lateinit var meshCore: MeshCore
private val fragmentManager = FragmentManager()
private val securityManager = SecurityManager(encryptionService, myPeerID)
private val storeForwardManager = StoreForwardManager()
private val messageHandler = MessageHandler(myPeerID, context.applicationContext)
private val packetProcessor = PacketProcessor(myPeerID)
// Gossip sync
private val gossipSyncManager: GossipSyncManager
// Wi-Fi Aware transport // Wi-Fi Aware transport
private val awareManager = context.getSystemService(WifiAwareManager::class.java) private val awareManager = context.getSystemService(WifiAwareManager::class.java)
@@ -87,10 +76,13 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
private var isActive = false private var isActive = false
// Delegate // Delegate
var delegate: WifiAwareMeshDelegate? = null override var delegate: WifiAwareMeshDelegate? = null
set(value) {
// Coroutines - must be initialized before tracker field = value
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) if (::meshCore.isInitialized) {
meshCore.delegate = value
}
}
private val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager private val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// Transport state // Transport state
@@ -102,59 +94,31 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
private val lastTimestamps = ConcurrentHashMap<String, ULong>() private val lastTimestamps = ConcurrentHashMap<String, ULong>()
init { init {
setupDelegates()
messageHandler.packetProcessor = packetProcessor
// Use shared GossipSyncManager from MeshServiceHolder if available (minimal refactor)
val shared = com.bitchat.android.service.MeshServiceHolder.sharedGossipSyncManager val shared = com.bitchat.android.service.MeshServiceHolder.sharedGossipSyncManager
if (shared != null) { meshCore = MeshCore(
gossipSyncManager = shared context = context.applicationContext,
} else { scope = serviceScope,
gossipSyncManager = GossipSyncManager( transport = wifiTransport,
myPeerID = myPeerID, encryptionService = encryptionService,
scope = serviceScope, myPeerID = myPeerID,
configProvider = object : GossipSyncManager.ConfigProvider { maxTtl = MAX_TTL,
override fun seenCapacity(): Int = 500 sharedGossipManager = shared,
override fun gcsMaxBytes(): Int = 400 gossipConfigProvider = object : GossipSyncManager.ConfigProvider {
override fun gcsTargetFpr(): Double = 0.01 override fun seenCapacity(): Int = 500
override fun gcsMaxBytes(): Int = 400
override fun gcsTargetFpr(): Double = 0.01
},
hooks = MeshCore.Hooks(
onAnnounceProcessed = { routed, _ ->
routed.peerID?.let { pid ->
try { meshCore.gossipSyncManager.scheduleInitialSyncToPeer(pid, 1_000) } catch (_: Exception) { }
}
},
leavePayloadProvider = {
(delegate?.getNickname() ?: myPeerID).toByteArray(Charsets.UTF_8)
} }
) )
gossipSyncManager.delegate = object : GossipSyncManager.Delegate { )
override fun sendPacketToPeer(peerID: String, packet: BitchatPacket) {
this@WifiAwareMeshService.sendPacketToPeer(peerID, packet)
}
override fun sendPacket(packet: BitchatPacket) {
dispatchGlobal(RoutedPacket(packet))
}
override fun signPacketForBroadcast(packet: BitchatPacket): BitchatPacket {
return signPacketBeforeBroadcast(packet)
}
}
}
}
/**
* Helper method hexToBa.
*/
private fun hexStringToByteArray(hex: String): ByteArray {
val out = ByteArray(8)
var idx = 0
var s = hex
while (s.length >= 2 && idx < 8) {
val b = s.substring(0, 2).toIntOrNull(16)?.toByte() ?: 0
out[idx++] = b
s = s.drop(2)
}
return out
}
/**
* Sign packet before broadcasting.
*/
private fun signPacketBeforeBroadcast(packet: BitchatPacket): BitchatPacket {
val data = packet.toBinaryDataForSigning() ?: return packet
val sig = encryptionService.signData(data) ?: return packet
return packet.copy(signature = sig)
} }
/** /**
@@ -177,17 +141,7 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
override fun send(packet: RoutedPacket) { override fun send(packet: RoutedPacket) {
// Received from bridge (e.g. BLE) -> Send via Wi-Fi // Received from bridge (e.g. BLE) -> Send via Wi-Fi
// Direct injection prevents routing loops (bridge handles source check) // Direct injection prevents routing loops (bridge handles source check)
broadcastPacket(packet) meshCore.sendFromBridge(packet)
}
/**
* unified dispatch: Send to local Wi-Fi and bridge to other transports
*/
private fun dispatchGlobal(routed: RoutedPacket) {
// 1. Send to local Wi-Fi transport
broadcastPacket(routed)
// 2. Bridge to other transports (e.g. BLE)
TransportBridgeService.broadcast("WIFI", routed)
} }
/** /**
@@ -226,195 +180,7 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
} }
} }
/**
* Configures delegates for internal components so that events are routed back
* through this service and ultimately to the {@link WifiAwareMeshDelegate}.
*/
private fun setupDelegates() {
peerManager.delegate = object : PeerManagerDelegate {
override fun onPeerListUpdated(peerIDs: List<String>) {
delegate?.didUpdatePeerList(peerIDs)
}
override fun onPeerRemoved(peerID: String) {
try { gossipSyncManager.removeAnnouncementForPeer(peerID) } catch (_: Exception) { }
try { encryptionService.removePeer(peerID) } catch (_: Exception) { }
}
}
securityManager.delegate = object : SecurityManagerDelegate {
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) {
serviceScope.launch {
delay(100)
sendAnnouncementToPeer(peerID)
delay(1000)
storeForwardManager.sendCachedMessages(peerID)
}
}
override fun sendHandshakeResponse(peerID: String, response: ByteArray) {
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_HANDSHAKE.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(peerID),
timestamp = System.currentTimeMillis().toULong(),
payload = response,
ttl = MAX_TTL
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(packet)))
}
override fun getPeerInfo(peerID: String): PeerInfo? {
return peerManager.getPeerInfo(peerID)
}
}
storeForwardManager.delegate = object : StoreForwardManagerDelegate {
override fun isFavorite(peerID: String) = delegate?.isFavorite(peerID) ?: false
override fun isPeerOnline(peerID: String) = peerManager.isPeerActive(peerID)
override fun sendPacket(packet: BitchatPacket) {
dispatchGlobal(RoutedPacket(packet))
}
}
messageHandler.delegate = object : MessageHandlerDelegate {
override fun addOrUpdatePeer(peerID: String, nickname: String) =
peerManager.addOrUpdatePeer(peerID, nickname)
override fun removePeer(peerID: String) = peerManager.removePeer(peerID)
override fun updatePeerNickname(peerID: String, nickname: String) {
peerManager.addOrUpdatePeer(peerID, nickname)
}
override fun getPeerNickname(peerID: String) =
peerManager.getPeerNickname(peerID)
override fun getNetworkSize() = peerManager.getActivePeerCount()
override fun getMyNickname() = delegate?.getNickname()
override fun getPeerInfo(peerID: String): PeerInfo? = peerManager.getPeerInfo(peerID)
override fun updatePeerInfo(
peerID: String,
nickname: String,
noisePublicKey: ByteArray,
signingPublicKey: ByteArray,
isVerified: Boolean
): Boolean = peerManager.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified)
override fun sendPacket(packet: BitchatPacket) {
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(packet)))
}
override fun relayPacket(routed: RoutedPacket) { dispatchGlobal(routed) }
override fun getBroadcastRecipient() = SpecialRecipients.BROADCAST
override fun verifySignature(packet: BitchatPacket, peerID: String) =
securityManager.verifySignature(packet, peerID)
override fun encryptForPeer(data: ByteArray, recipientPeerID: String) =
securityManager.encryptForPeer(data, recipientPeerID)
override fun decryptFromPeer(encryptedData: ByteArray, senderPeerID: String) =
securityManager.decryptFromPeer(encryptedData, senderPeerID)
override fun verifyEd25519Signature(signature: ByteArray, data: ByteArray, publicKey: ByteArray): Boolean =
encryptionService.verifyEd25519Signature(signature, data, publicKey)
override fun hasNoiseSession(peerID: String) =
encryptionService.hasEstablishedSession(peerID)
override fun initiateNoiseHandshake(peerID: String) {
serviceScope.launch {
val hs = encryptionService.initiateHandshake(peerID) ?: return@launch
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_HANDSHAKE.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(peerID),
timestamp = System.currentTimeMillis().toULong(),
payload = hs,
ttl = MAX_TTL
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(packet)))
}
}
override fun processNoiseHandshakeMessage(payload: ByteArray, peerID: String): ByteArray? =
try { encryptionService.processHandshakeMessage(payload, peerID) } catch (_: Exception) { null }
override fun updatePeerIDBinding(newPeerID: String, nickname: String, publicKey: ByteArray, previousPeerID: String?) {
peerManager.addOrUpdatePeer(newPeerID, nickname)
val fingerprint = peerManager.storeFingerprintForPeer(newPeerID, publicKey)
previousPeerID?.let { peerManager.removePeer(it) }
Log.d(TAG, "Updated peer binding to $newPeerID, fp=${fingerprint.take(16)}")
}
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String) =
delegate?.decryptChannelMessage(encryptedContent, channel)
override fun onMessageReceived(message: BitchatMessage) {
delegate?.didReceiveMessage(message)
}
override fun onChannelLeave(channel: String, fromPeer: String) {
delegate?.didReceiveChannelLeave(channel, fromPeer)
}
override fun onDeliveryAckReceived(messageID: String, peerID: String) {
delegate?.didReceiveDeliveryAck(messageID, peerID)
}
override fun onReadReceiptReceived(messageID: String, peerID: String) {
delegate?.didReceiveReadReceipt(messageID, peerID)
}
}
packetProcessor.delegate = object : PacketProcessorDelegate {
override fun validatePacketSecurity(packet: BitchatPacket, peerID: String) =
securityManager.validatePacket(packet, peerID)
override fun updatePeerLastSeen(peerID: String) = peerManager.updatePeerLastSeen(peerID)
override fun getPeerNickname(peerID: String): String? = peerManager.getPeerNickname(peerID)
override fun getNetworkSize(): Int = peerManager.getActivePeerCount()
override fun getBroadcastRecipient(): ByteArray = SpecialRecipients.BROADCAST
override fun handleNoiseHandshake(routed: RoutedPacket): Boolean =
runBlocking { securityManager.handleNoiseHandshake(routed) }
override fun handleNoiseEncrypted(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleNoiseEncrypted(routed) }
}
override fun handleAnnounce(routed: RoutedPacket) {
serviceScope.launch {
val isFirst = messageHandler.handleAnnounce(routed)
routed.peerID?.let { pid ->
try { gossipSyncManager.scheduleInitialSyncToPeer(pid, 1_000) } catch (_: Exception) { }
}
try { gossipSyncManager.onPublicPacketSeen(routed.packet) } catch (_: Exception) { }
}
}
override fun handleMessage(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleMessage(routed) }
try {
val pkt = routed.packet
val isBroadcast = (pkt.recipientID == null || pkt.recipientID.contentEquals(SpecialRecipients.BROADCAST))
if (isBroadcast && pkt.type == MessageType.MESSAGE.value) {
gossipSyncManager.onPublicPacketSeen(pkt)
}
} catch (_: Exception) { }
}
override fun handleLeave(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleLeave(routed) }
}
override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
try {
val isBroadcast = (packet.recipientID == null || packet.recipientID.contentEquals(SpecialRecipients.BROADCAST))
if (isBroadcast && packet.type == MessageType.FRAGMENT.value) {
gossipSyncManager.onPublicPacketSeen(packet)
}
} catch (_: Exception) { }
return fragmentManager.handleFragment(packet)
}
override fun sendAnnouncementToPeer(peerID: String) = this@WifiAwareMeshService.sendAnnouncementToPeer(peerID)
override fun sendCachedMessages(peerID: String) = storeForwardManager.sendCachedMessages(peerID)
override fun relayPacket(routed: RoutedPacket) = dispatchGlobal(routed)
override fun handleRequestSync(routed: RoutedPacket) {
val fromPeer = routed.peerID ?: return
val req = RequestSyncPacket.decode(routed.packet.payload) ?: return
gossipSyncManager.handleRequestSync(fromPeer, req)
}
}
}
/** /**
* Starts Wi-Fi Aware services (publish + subscribe). * Starts Wi-Fi Aware services (publish + subscribe).
@@ -428,7 +194,7 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE Manifest.permission.CHANGE_WIFI_STATE
]) ])
fun startServices() { override fun startServices() {
if (isActive) return if (isActive) return
isActive = true isActive = true
Log.i(TAG, "Starting Wi-Fi Aware mesh with peer ID: $myPeerID") Log.i(TAG, "Starting Wi-Fi Aware mesh with peer ID: $myPeerID")
@@ -527,10 +293,9 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
} }
}, Handler(Looper.getMainLooper())) }, Handler(Looper.getMainLooper()))
sendPeriodicBroadcastAnnounce() meshCore.startCore()
startPeriodicConnectionMaintenance() startPeriodicConnectionMaintenance()
connectionTracker.start() connectionTracker.start()
gossipSyncManager.start()
// Register with cross-layer transport bridge // Register with cross-layer transport bridge
TransportBridgeService.register("WIFI", this) TransportBridgeService.register("WIFI", this)
@@ -539,7 +304,7 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
/** /**
* Stops the Wi-Fi Aware mesh services and cleans up sockets and sessions. * Stops the Wi-Fi Aware mesh services and cleans up sockets and sessions.
*/ */
fun stopServices() { override fun stopServices() {
if (!isActive) return if (!isActive) return
isActive = false isActive = false
Log.i(TAG, "Stopping Wi-Fi Aware mesh") Log.i(TAG, "Stopping Wi-Fi Aware mesh")
@@ -547,12 +312,12 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
// Unregister from bridge // Unregister from bridge
TransportBridgeService.unregister("WIFI") TransportBridgeService.unregister("WIFI")
sendLeaveAnnouncement() meshCore.sendLeaveAnnouncement()
serviceScope.launch { serviceScope.launch {
delay(200) delay(200)
gossipSyncManager.stop() meshCore.stopCore()
connectionTracker.stop() // Handles socket closing and callback unregistration connectionTracker.stop() // Handles socket closing and callback unregistration
publishSession?.close(); publishSession = null publishSession?.close(); publishSession = null
@@ -561,29 +326,12 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
handleToPeerId.clear() handleToPeerId.clear()
peerManager.shutdown() meshCore.shutdown()
fragmentManager.shutdown()
securityManager.shutdown()
storeForwardManager.shutdown()
messageHandler.shutdown()
packetProcessor.shutdown()
serviceScope.cancel() serviceScope.cancel()
} }
} }
/**
* Periodically broadcasts an ANNOUNCE packet (every ~30s) while the service is active,
* so new/idle peers can discover us without user action.
*/
private fun sendPeriodicBroadcastAnnounce() {
serviceScope.launch {
while (isActive) {
try { delay(30_000); sendBroadcastAnnounce() } catch (_: Exception) { }
}
}
}
/** /**
* Periodic active maintenance: retries connections to discovered but unconnected peers. * Periodic active maintenance: retries connections to discovered but unconnected peers.
*/ */
@@ -681,13 +429,13 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
client.keepAlive = true client.keepAlive = true
Log.d(TAG, "SERVER: accepted TCP from ${peerId.take(8)} addr=${client.inetAddress?.hostAddress}") Log.d(TAG, "SERVER: accepted TCP from ${peerId.take(8)} addr=${client.inetAddress?.hostAddress}")
connectionTracker.onClientConnected(peerId, client) connectionTracker.onClientConnected(peerId, client)
try { peerManager.setDirectConnection(peerId, true) } catch (_: Exception) {} try { meshCore.setDirectConnection(peerId, true) } catch (_: Exception) {}
try { peerManager.addOrUpdatePeer(peerId, peerId) } catch (_: Exception) {} try { meshCore.addOrUpdatePeer(peerId, peerId) } catch (_: Exception) {}
listenerExec.execute { listenToPeer(client, peerId) } listenerExec.execute { listenToPeer(client, peerId) }
handleSubscriberKeepAlive(client, peerId, pubSession, peerHandle) handleSubscriberKeepAlive(client, peerId, pubSession, peerHandle)
// Kick off Noise handshake for this logical peer // Kick off Noise handshake for this logical peer
if (myPeerID < peerId) { if (myPeerID < peerId) {
messageHandler.delegate?.initiateNoiseHandshake(peerId) meshCore.initiateNoiseHandshake(peerId)
Log.d(TAG, "SERVER: initiating Noise handshake to ${peerId.take(8)} (lower ID)") Log.d(TAG, "SERVER: initiating Noise handshake to ${peerId.take(8)} (lower ID)")
} }
// Ensure fast presence even before handshake settles // Ensure fast presence even before handshake settles
@@ -817,13 +565,13 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
Log.d(TAG, "CLIENT: TCP connected to ${peerId.take(8)} addr=$scopedAddr:$port (iface=$iface)") Log.d(TAG, "CLIENT: TCP connected to ${peerId.take(8)} addr=$scopedAddr:$port (iface=$iface)")
connectionTracker.onClientConnected(peerId, sock) connectionTracker.onClientConnected(peerId, sock)
try { peerManager.setDirectConnection(peerId, true) } catch (_: Exception) {} try { meshCore.setDirectConnection(peerId, true) } catch (_: Exception) {}
try { peerManager.addOrUpdatePeer(peerId, peerId) } catch (_: Exception) {} try { meshCore.addOrUpdatePeer(peerId, peerId) } catch (_: Exception) {}
listenerExec.execute { listenToPeer(sock, peerId) } listenerExec.execute { listenToPeer(sock, peerId) }
handleServerKeepAlive(sock, peerId, peerHandle) handleServerKeepAlive(sock, peerId, peerHandle)
// Kick off Noise handshake for this logical peer // Kick off Noise handshake for this logical peer
if (myPeerID < peerId) { if (myPeerID < peerId) {
messageHandler.delegate?.initiateNoiseHandshake(peerId) meshCore.initiateNoiseHandshake(peerId)
Log.d(TAG, "CLIENT: initiating Noise handshake to ${peerId.take(8)} (lower ID)") Log.d(TAG, "CLIENT: initiating Noise handshake to ${peerId.take(8)} (lower ID)")
} }
// Ensure fast presence even before handshake settles // Ensure fast presence even before handshake settles
@@ -908,7 +656,7 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
// Note: We do NOT update peerSockets mapping based on senderPeerHex. // Note: We do NOT update peerSockets mapping based on senderPeerHex.
// The socket belongs to initialLogicalPeerId effectively serving as the "MAC address" layer. // The socket belongs to initialLogicalPeerId effectively serving as the "MAC address" layer.
Log.d(TAG, "RX: packet type=${pkt.type} from ${senderPeerHex.take(8)} via ${initialLogicalPeerId.take(8)} (bytes=${raw.size})") Log.d(TAG, "RX: packet type=${pkt.type} from ${senderPeerHex.take(8)} via ${initialLogicalPeerId.take(8)} (bytes=${raw.size})")
packetProcessor.processPacket(RoutedPacket(pkt, senderPeerHex, initialLogicalPeerId)) meshCore.processIncoming(pkt, senderPeerHex, initialLogicalPeerId)
} }
// Breaking out of the loop means the socket is dead or service is stopping. // Breaking out of the loop means the socket is dead or service is stopping.
@@ -922,7 +670,7 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
Log.d(TAG, "Cleaning up peer: $initialId") Log.d(TAG, "Cleaning up peer: $initialId")
connectionTracker.disconnect(initialId) connectionTracker.disconnect(initialId)
peerManager.removePeer(initialId) meshCore.removePeer(initialId)
} }
} }
@@ -932,24 +680,8 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @param mentions Optional list of mentioned peer IDs * @param mentions Optional list of mentioned peer IDs
* @param channel Optional channel name * @param channel Optional channel name
*/ */
fun sendMessage(content: String, mentions: List<String> = emptyList(), channel: String? = null) { override fun sendMessage(content: String, mentions: List<String>, channel: String?) {
if (content.isEmpty()) return meshCore.sendMessage(content, mentions, channel)
serviceScope.launch {
val packet = BitchatPacket(
version = 1u,
type = MessageType.MESSAGE.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = SpecialRecipients.BROADCAST,
timestamp = System.currentTimeMillis().toULong(),
payload = content.toByteArray(Charsets.UTF_8),
signature = null,
ttl = MAX_TTL
)
val signed = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signed))
try { gossipSyncManager.onPublicPacketSeen(signed) } catch (_: Exception) { }
}
} }
/** /**
@@ -960,37 +692,8 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @param recipientNickname Recipient nickname * @param recipientNickname Recipient nickname
* @param messageID Optional message ID (UUID if null) * @param messageID Optional message ID (UUID if null)
*/ */
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) { override fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String?) {
if (content.isEmpty() || recipientPeerID.isEmpty()) return meshCore.sendPrivateMessage(content, recipientPeerID, recipientNickname, messageID)
serviceScope.launch {
val finalId = messageID ?: UUID.randomUUID().toString()
if (encryptionService.hasEstablishedSession(recipientPeerID)) {
try {
val pm = PrivateMessagePacket(messageID = finalId, content = content)
val tlv = pm.encode() ?: return@launch
val payload = NoisePayload(type = NoisePayloadType.PRIVATE_MESSAGE, data = tlv).encode()
val enc = encryptionService.encrypt(payload, recipientPeerID)
val pkt = BitchatPacket(
version = 1u,
type = MessageType.NOISE_ENCRYPTED.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = enc,
signature = null,
ttl = MAX_TTL
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(pkt)))
} catch (e: Exception) {
Log.e(TAG, "Failed to encrypt private message: ${e.message}")
}
} else {
messageHandler.delegate?.initiateNoiseHandshake(recipientPeerID)
}
}
} }
/** /**
@@ -1001,29 +704,8 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @param recipientPeerID The peer to notify. * @param recipientPeerID The peer to notify.
* @param readerNickname Nickname of the reader (may be shown by the receiver). * @param readerNickname Nickname of the reader (may be shown by the receiver).
*/ */
fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) { override fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) {
serviceScope.launch { meshCore.sendReadReceipt(messageID, recipientPeerID, readerNickname)
try {
val payload = NoisePayload(
type = NoisePayloadType.READ_RECEIPT,
data = messageID.toByteArray(Charsets.UTF_8)
).encode()
val enc = encryptionService.encrypt(payload, recipientPeerID)
val pkt = BitchatPacket(
version = 1u,
type = MessageType.NOISE_ENCRYPTED.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = enc,
signature = null,
ttl = MAX_TTL
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(pkt)))
} catch (e: Exception) {
Log.e(TAG, "Failed to send read receipt: ${e.message}")
}
}
} }
/** /**
@@ -1032,28 +714,8 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* *
* @param file Encoded metadata and chunks descriptor of the file to send. * @param file Encoded metadata and chunks descriptor of the file to send.
*/ */
fun sendFileBroadcast(file: BitchatFilePacket) { override fun sendFileBroadcast(file: BitchatFilePacket) {
try { meshCore.sendFileBroadcast(file)
val payload = file.encode() ?: run { Log.e(TAG, "file TLV encode failed"); return }
serviceScope.launch {
val pkt = BitchatPacket(
version = 2u, // FILE_TRANSFER big length
type = MessageType.FILE_TRANSFER.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = SpecialRecipients.BROADCAST,
timestamp = System.currentTimeMillis().toULong(),
payload = payload,
signature = null,
ttl = MAX_TTL
)
val signed = signPacketBeforeBroadcast(pkt)
val transferId = sha256Hex(payload)
dispatchGlobal(RoutedPacket(signed, transferId = transferId))
try { gossipSyncManager.onPublicPacketSeen(signed) } catch (_: Exception) { }
}
} catch (e: Exception) {
Log.e(TAG, "sendFileBroadcast failed: ${e.message}", e)
}
} }
/** /**
@@ -1063,33 +725,8 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @param recipientPeerID Target peer. * @param recipientPeerID Target peer.
* @param file Encoded metadata and chunks descriptor of the file to send. * @param file Encoded metadata and chunks descriptor of the file to send.
*/ */
fun sendFilePrivate(recipientPeerID: String, file: BitchatFilePacket) { override fun sendFilePrivate(recipientPeerID: String, file: BitchatFilePacket) {
try { meshCore.sendFilePrivate(recipientPeerID, file)
serviceScope.launch {
if (!encryptionService.hasEstablishedSession(recipientPeerID)) {
messageHandler.delegate?.initiateNoiseHandshake(recipientPeerID)
return@launch
}
val tlv = file.encode() ?: return@launch
val np = NoisePayload(type = NoisePayloadType.FILE_TRANSFER, data = tlv).encode()
val enc = encryptionService.encrypt(np, recipientPeerID)
val pkt = BitchatPacket(
version = 1u,
type = MessageType.NOISE_ENCRYPTED.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = enc,
signature = null,
ttl = MAX_TTL
)
val signed = signPacketBeforeBroadcast(pkt)
val transferId = sha256Hex(tlv)
dispatchGlobal(RoutedPacket(signed, transferId = transferId))
}
} catch (e: Exception) {
Log.e(TAG, "sendFilePrivate failed: ${e.message}", e)
}
} }
/** /**
@@ -1098,116 +735,57 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @param transferId Deterministic id (usually sha256 of the file TLV). * @param transferId Deterministic id (usually sha256 of the file TLV).
* @return true if a transfer with this id was found and cancellation was scheduled, false otherwise. * @return true if a transfer with this id was found and cancellation was scheduled, false otherwise.
*/ */
fun cancelFileTransfer(transferId: String): Boolean { override fun cancelFileTransfer(transferId: String): Boolean {
return false return meshCore.cancelFileTransfer(transferId)
} }
/**
* Computes the SHA-256 of the given bytes and returns a lowercase hex string.
* Falls back to the byte-length in hex if MessageDigest is unavailable.
*/
private fun sha256Hex(bytes: ByteArray): String = try {
val md = java.security.MessageDigest.getInstance("SHA-256")
md.update(bytes); md.digest().joinToString("") { "%02x".format(it) }
} catch (_: Exception) { bytes.size.toString(16) }
/** /**
* Broadcasts an ANNOUNCE packet to the entire mesh. * Broadcasts an ANNOUNCE packet to the entire mesh.
*/ */
fun sendBroadcastAnnounce() { override fun sendBroadcastAnnounce() {
serviceScope.launch { meshCore.sendBroadcastAnnounce()
val nickname = delegate?.getNickname() ?: myPeerID
val staticKey = encryptionService.getStaticPublicKey() ?: run {
Log.e(TAG, "No static public key available for announcement"); return@launch
}
val signingKey = encryptionService.getSigningPublicKey() ?: run {
Log.e(TAG, "No signing public key available for announcement"); return@launch
}
val tlvPayload = IdentityAnnouncement(nickname, staticKey, signingKey).encode() ?: return@launch
val announcePacket = BitchatPacket(
type = MessageType.ANNOUNCE.value,
ttl = MAX_TTL,
senderID = myPeerID,
payload = tlvPayload
)
val signed = signPacketBeforeBroadcast(announcePacket)
dispatchGlobal(RoutedPacket(signed))
try { gossipSyncManager.onPublicPacketSeen(signed) } catch (_: Exception) { }
}
} }
/** /**
* Sends an ANNOUNCE packet to a specific peer. * Sends an ANNOUNCE packet to a specific peer.
*/ */
fun sendAnnouncementToPeer(peerID: String) { override fun sendAnnouncementToPeer(peerID: String) {
if (peerManager.hasAnnouncedToPeer(peerID)) return meshCore.sendAnnouncementToPeer(peerID)
val nickname = delegate?.getNickname() ?: myPeerID
val staticKey = encryptionService.getStaticPublicKey() ?: return
val signingKey = encryptionService.getSigningPublicKey() ?: return
val tlvPayload = IdentityAnnouncement(nickname, staticKey, signingKey).encode() ?: return
val packet = BitchatPacket(
type = MessageType.ANNOUNCE.value,
ttl = MAX_TTL,
senderID = myPeerID,
payload = tlvPayload
)
val signed = signPacketBeforeBroadcast(packet)
dispatchGlobal(RoutedPacket(signed))
peerManager.markPeerAsAnnouncedTo(peerID)
try { gossipSyncManager.onPublicPacketSeen(signed) } catch (_: Exception) { }
}
/**
* Sends a LEAVE announcement to all peers before disconnecting.
*/
private fun sendLeaveAnnouncement() {
val nickname = delegate?.getNickname() ?: myPeerID
val packet = BitchatPacket(
type = MessageType.LEAVE.value,
ttl = MAX_TTL,
senderID = myPeerID,
payload = nickname.toByteArray()
)
dispatchGlobal(RoutedPacket(signPacketBeforeBroadcast(packet)))
} }
/** @return Mapping of peer IDs to nicknames. */ /** @return Mapping of peer IDs to nicknames. */
fun getPeerNicknames(): Map<String, String> = peerManager.getAllPeerNicknames() override fun getPeerNicknames(): Map<String, String> = meshCore.getPeerNicknames()
/** @return Mapping of peer IDs to RSSI values. */ /** @return Mapping of peer IDs to RSSI values. */
fun getPeerRSSI(): Map<String, Int> = peerManager.getAllPeerRSSI() override fun getPeerRSSI(): Map<String, Int> = meshCore.getPeerRSSI()
/** @return current active peer count for status surfaces. */
override fun getActivePeerCount(): Int = meshCore.getActivePeerCount()
/** /**
* @return true if a Noise session with the peer is fully established. * @return true if a Noise session with the peer is fully established.
*/ */
fun hasEstablishedSession(peerID: String) = encryptionService.hasEstablishedSession(peerID) override fun hasEstablishedSession(peerID: String) = meshCore.hasEstablishedSession(peerID)
/** /**
* @return a human-readable Noise session state for the given peer (implementation-defined). * @return a human-readable Noise session state for the given peer (implementation-defined).
*/ */
fun getSessionState(peerID: String) = encryptionService.getSessionState(peerID) override fun getSessionState(peerID: String) = meshCore.getSessionState(peerID)
/** /**
* Triggers a Noise handshake with the given peer. Safe to call repeatedly; no-op if already handshaking/established. * Triggers a Noise handshake with the given peer. Safe to call repeatedly; no-op if already handshaking/established.
*/ */
fun initiateNoiseHandshake(peerID: String) = messageHandler.delegate?.initiateNoiseHandshake(peerID) override fun initiateNoiseHandshake(peerID: String) = meshCore.initiateNoiseHandshake(peerID)
/** /**
* @return the stored public-key fingerprint (hex) for a peer, if known. * @return the stored public-key fingerprint (hex) for a peer, if known.
*/ */
fun getPeerFingerprint(peerID: String): String? = peerManager.getFingerprintForPeer(peerID) override fun getPeerFingerprint(peerID: String): String? = meshCore.getPeerFingerprint(peerID)
/** /**
* Retrieves the full profile for a peer, including keys and verification state, if available. * Retrieves the full profile for a peer, including keys and verification state, if available.
*/ */
fun getPeerInfo(peerID: String): PeerInfo? = peerManager.getPeerInfo(peerID) override fun getPeerInfo(peerID: String): PeerInfo? = meshCore.getPeerInfo(peerID)
/** /**
* Updates local metadata for a peer and returns whether the change was applied. * Updates local metadata for a peer and returns whether the change was applied.
@@ -1219,35 +797,35 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @param isVerified Whether this identity is verified by the user. * @param isVerified Whether this identity is verified by the user.
* @return true if the record was updated or created, false otherwise. * @return true if the record was updated or created, false otherwise.
*/ */
fun updatePeerInfo( override fun updatePeerInfo(
peerID: String, peerID: String,
nickname: String, nickname: String,
noisePublicKey: ByteArray, noisePublicKey: ByteArray,
signingPublicKey: ByteArray, signingPublicKey: ByteArray,
isVerified: Boolean isVerified: Boolean
): Boolean = peerManager.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified) ): Boolean = meshCore.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified)
/** /**
* @return the local devices long-term identity fingerprint (hex). * @return the local devices long-term identity fingerprint (hex).
*/ */
fun getIdentityFingerprint(): String = encryptionService.getIdentityFingerprint() override fun getIdentityFingerprint(): String = meshCore.getIdentityFingerprint()
/** /**
* @return true if the UI should show an “encrypted” indicator for this peer. * @return true if the UI should show an “encrypted” indicator for this peer.
*/ */
fun shouldShowEncryptionIcon(peerID: String) = encryptionService.hasEstablishedSession(peerID) override fun shouldShowEncryptionIcon(peerID: String) = meshCore.shouldShowEncryptionIcon(peerID)
/** /**
* @return a snapshot list of peers with established Noise sessions. * @return a snapshot list of peers with established Noise sessions.
*/ */
fun getEncryptedPeers(): List<String> = emptyList() override fun getEncryptedPeers(): List<String> = meshCore.getEncryptedPeers()
/** /**
* @return the current IPv4/IPv6 address of a connected peer, if any. * @return the current IPv4/IPv6 address of a connected peer, if any.
* Prefers the scoped IPv6 address format. * Prefers the scoped IPv6 address format.
*/ */
fun getDeviceAddressForPeer(peerID: String): String? = override fun getDeviceAddressForPeer(peerID: String): String? =
connectionTracker.peerSockets[peerID]?.let { resolveScopedAddress(it) } meshCore.getDeviceAddressForPeer(peerID)
/** /**
* Helper to resolve a scoped IPv6 address from a socket for UI display. * Helper to resolve a scoped IPv6 address from a socket for UI display.
@@ -1269,18 +847,13 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
* @return a mapping of peerID → connected device IP address for all active sockets. * @return a mapping of peerID → connected device IP address for all active sockets.
* Results are formatted as scoped addresses if applicable. * Results are formatted as scoped addresses if applicable.
*/ */
fun getDeviceAddressToPeerMapping(): Map<String, String> { override fun getDeviceAddressToPeerMapping(): Map<String, String> =
val map = mutableMapOf<String, String>() meshCore.getDeviceAddressToPeerMapping()
connectionTracker.peerSockets.forEach { (pid, sock) ->
map[pid] = resolveScopedAddress(sock) ?: "unknown"
}
return map
}
/** /**
* @return map of peer ID to nickname, bridged for UI warning fix. * @return map of peer ID to nickname, bridged for UI warning fix.
*/ */
fun getPeerNicknamesMap(): Map<String, String?> = peerManager.getAllPeerNicknames() fun getPeerNicknamesMap(): Map<String, String?> = meshCore.getPeerNicknames()
/** Returns recently discovered peer IDs via Aware discovery (may not be connected). */ /** Returns recently discovered peer IDs via Aware discovery (may not be connected). */
fun getDiscoveredPeerIds(): Set<String> = fun getDiscoveredPeerIds(): Set<String> =
@@ -1289,23 +862,27 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
/** /**
* Utility for logs/UI: pretty-prints one peer-to-address mapping per line. * Utility for logs/UI: pretty-prints one peer-to-address mapping per line.
*/ */
fun printDeviceAddressesForPeers(): String = override fun printDeviceAddressesForPeers(): String =
getDeviceAddressToPeerMapping().entries.joinToString("\n") { "${it.key} -> ${it.value}" } getDeviceAddressToPeerMapping().entries.joinToString("\n") { "${it.key} -> ${it.value}" }
/** /**
* @return A detailed string containing the debug status of all mesh components. * @return A detailed string containing the debug status of all mesh components.
*/ */
fun getDebugStatus(): String = buildString { override fun getDebugStatus(): String {
appendLine("=== Wi-Fi Aware Mesh Debug Status ===") return meshCore.getDebugStatus(
appendLine("My Peer ID: $myPeerID") transportInfo = connectionTracker.getDebugInfo(),
appendLine("Peers: ${connectionTracker.peerSockets.keys}") deviceMap = getDeviceAddressToPeerMapping(),
appendLine(connectionTracker.getDebugInfo()) extraLines = listOf("Peers: ${connectionTracker.peerSockets.keys}"),
appendLine(peerManager.getDebugInfo(getDeviceAddressToPeerMapping())) title = "Wi-Fi Aware Mesh Debug Status"
appendLine(fragmentManager.getDebugInfo()) )
appendLine(securityManager.getDebugInfo()) }
appendLine(storeForwardManager.getDebugInfo())
appendLine(messageHandler.getDebugInfo()) override fun clearAllInternalData() {
appendLine(packetProcessor.getDebugInfo()) meshCore.clearAllInternalData()
}
override fun clearAllEncryptionData() {
meshCore.clearAllEncryptionData()
} }
/** Utility extension to safely close sockets. */ /** Utility extension to safely close sockets. */
@@ -1313,20 +890,32 @@ class WifiAwareMeshService(private val context: Context) : TransportBridgeServic
/** Utility extension to safely close server sockets. */ /** Utility extension to safely close server sockets. */
private fun ServerSocket.closeQuietly() = try { close() } catch (_: Exception) {} private fun ServerSocket.closeQuietly() = try { close() } catch (_: Exception) {}
}
private inner class WifiAwareTransport : MeshTransport {
override val id: String = "WIFI"
/** override fun broadcastPacket(routed: RoutedPacket) {
* Delegate interface for mesh service callbacks (maintains exact same interface) this@WifiAwareMeshService.broadcastPacket(routed)
*/ }
interface WifiAwareMeshDelegate {
fun didReceiveMessage(message: BitchatMessage) override fun sendPacketToPeer(peerID: String, packet: BitchatPacket) {
fun didUpdatePeerList(peers: List<String>) this@WifiAwareMeshService.sendPacketToPeer(peerID, packet)
fun didReceiveChannelLeave(channel: String, fromPeer: String) }
fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String)
fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) override fun getDeviceAddressForPeer(peerID: String): String? {
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? return connectionTracker.peerSockets[peerID]?.let { resolveScopedAddress(it) }
fun getNickname(): String? }
fun isFavorite(peerID: String): Boolean
// registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager override fun getDeviceAddressToPeerMapping(): Map<String, String> {
val map = mutableMapOf<String, String>()
connectionTracker.peerSockets.forEach { (pid, sock) ->
map[pid] = resolveScopedAddress(sock) ?: "unknown"
}
return map
}
override fun getTransportDebugInfo(): String {
return connectionTracker.getDebugInfo()
}
}
} }