Fix wifi aware routing (#534)

* Background persistence (#505)

* persistence step 1

* fix build

* messages in the background work, notifications not yet

* app state store

* DM icon shows up

* notification launches when app is closed!

* keep ui updated

* lifecycle fixes

* extensive logging, maybe revert later

* send nickname in announcement

* quit in notification

* setting in about sheet

* fix quit bitchat

* lifecycle fixes

* power mode based on background state

* stats for both direciotns

* fix graph persistence

* better counting

* count per device

* only compute when debug sheet is open? untested

* fix read receipts

* fix read receipts fully

* fix unread badge if messages have been read in focus

* foreground promotion fix

* fix app kill in notification

* adjust to new tor

* nice

* about sheet design

* bump version 1.6.0 (#524)

* Automated update of relay data - Sun Dec 14 06:06:53 UTC 2025

* bump targetSdk (#526)

* Automated update of relay data - Sun Dec 21 06:06:56 UTC 2025

* Automated update of relay data - Sun Dec 28 06:07:12 UTC 2025

* Prevent quit notification from reappearing (#530)

* shutdown sequence

* Prevent quit notification from reappearing

* Restrict force-finish broadcast

* Cancel quit shutdown on relaunch

* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork

* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding

* Fix Wi-Fi Aware connectivity and UI integration post-merge

- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.

* Enhance Wi-Fi Aware robustness and debug UI display

- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.

* Share GossipSyncManager across transports to prevent redundant message synchronization

- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.

* Restore VPN acquisition logic and improve peer cleanup

- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.

* fix wifi aware routing

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalue <>
This commit is contained in:
callebtc
2026-01-04 01:12:40 +07:00
committed by GitHub
co-authored by GitHub Action aidenvalue <>
parent bd272bb5ea
commit c12af45b6b
7 changed files with 330 additions and 150 deletions
@@ -13,6 +13,7 @@ import com.bitchat.android.protocol.SpecialRecipients
import com.bitchat.android.model.RequestSyncPacket
import com.bitchat.android.sync.GossipSyncManager
import com.bitchat.android.util.toHexString
import com.bitchat.android.service.TransportBridgeService
import kotlinx.coroutines.*
import java.util.*
import kotlin.math.sign
@@ -31,7 +32,7 @@ import kotlin.random.Random
* - BluetoothConnectionManager: BLE connections and GATT operations
* - PacketProcessor: Incoming packet routing
*/
class BluetoothMeshService(private val context: Context) {
class BluetoothMeshService(private val context: Context) : TransportBridgeService.TransportLayer {
private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } }
companion object {
@@ -101,9 +102,15 @@ class BluetoothMeshService(private val context: Context) {
// Wire sync manager delegate
gossipSyncManager.delegate = object : GossipSyncManager.Delegate {
override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(RoutedPacket(packet))
dispatchGlobal(RoutedPacket(packet))
}
override fun sendPacketToPeer(peerID: String, packet: BitchatPacket) {
// Point-to-point optimization if possible, but for bridge safety
// we might want to consider dispatchGlobal if peer is on another transport.
// However, sendPacketToPeer in connectionManager is BLE-specific unicast.
// If peer is on Wi-Fi, this won't reach.
// For now, let's keep unicast as-is (it's mostly for sync)
// and assume routing handles the rest via broadcasts if needed.
connectionManager.sendPacketToPeer(peerID, packet)
}
override fun signPacketForBroadcast(packet: BitchatPacket): BitchatPacket {
@@ -111,6 +118,26 @@ class BluetoothMeshService(private val context: Context) {
}
}
Log.d(TAG, "Delegates set up; GossipSyncManager initialized")
// Register with cross-layer transport bridge
TransportBridgeService.register("BLE", this)
}
// TransportLayer implementation
override fun send(packet: RoutedPacket) {
// Received from bridge (e.g. Wi-Fi) -> Send via BLE
// Direct injection prevents routing loops (bridge handles source check)
connectionManager.broadcastPacket(packet)
}
/**
* unified dispatch: Send to local BLE and bridge to other transports
*/
private fun dispatchGlobal(routed: RoutedPacket) {
// 1. Send to local BLE transport
connectionManager.broadcastPacket(routed)
// 2. Bridge to other transports (e.g. Wi-Fi)
TransportBridgeService.broadcast("BLE", routed)
}
/**
@@ -164,8 +191,6 @@ class BluetoothMeshService(private val context: Context) {
// PeerManager delegates to main mesh service delegate
peerManager.delegate = object : PeerManagerDelegate {
override fun onPeerListUpdated(peerIDs: List<String>) {
// Update process-wide state first
try { com.bitchat.android.services.AppStateStore.setPeers(peerIDs) } catch (_: Exception) { }
// Then notify UI delegate if attached
delegate?.didUpdatePeerList(peerIDs)
}
@@ -208,7 +233,7 @@ class BluetoothMeshService(private val context: Context) {
)
// Sign the handshake response
val signedPacket = signPacketBeforeBroadcast(responsePacket)
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
Log.d(TAG, "Sent Noise handshake response to $peerID (${response.size} bytes)")
}
@@ -228,7 +253,7 @@ class BluetoothMeshService(private val context: Context) {
}
override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(RoutedPacket(packet))
dispatchGlobal(RoutedPacket(packet))
}
}
@@ -272,11 +297,11 @@ class BluetoothMeshService(private val context: Context) {
// Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
val routed = RoutedPacket(signedPacket)
connectionManager.broadcastPacket(routed)
dispatchGlobal(routed)
}
override fun relayPacket(routed: RoutedPacket) {
connectionManager.broadcastPacket(routed)
dispatchGlobal(routed)
}
override fun getBroadcastRecipient(): ByteArray {
@@ -323,7 +348,7 @@ class BluetoothMeshService(private val context: Context) {
// Sign the handshake packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
Log.d(TAG, "Initiated Noise handshake with $peerID (${handshakeData.size} bytes)")
} else {
Log.w(TAG, "Failed to generate Noise handshake data for $peerID")
@@ -515,7 +540,7 @@ class BluetoothMeshService(private val context: Context) {
}
override fun relayPacket(routed: RoutedPacket) {
connectionManager.broadcastPacket(routed)
dispatchGlobal(routed)
}
override fun handleRequestSync(routed: RoutedPacket) {
@@ -632,6 +657,9 @@ class BluetoothMeshService(private val context: Context) {
Log.i(TAG, "Stopping Bluetooth mesh service")
isActive = false
// Unregister from bridge
TransportBridgeService.unregister("BLE")
// Send leave announcement
sendLeaveAnnouncement()
@@ -691,7 +719,7 @@ class BluetoothMeshService(private val context: Context) {
// Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
// Track our own broadcast message for sync
try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { }
}
@@ -723,7 +751,7 @@ class BluetoothMeshService(private val context: Context) {
val signed = signPacketBeforeBroadcast(packet)
// Use a stable transferId based on the file TLV payload for progress tracking
val transferId = sha256Hex(payload)
connectionManager.broadcastPacket(RoutedPacket(signed, transferId = transferId))
dispatchGlobal(RoutedPacket(signed, transferId = transferId))
try { gossipSyncManager.onPublicPacketSeen(signed) } catch (_: Exception) { }
}
} catch (e: Exception) {
@@ -781,7 +809,7 @@ class BluetoothMeshService(private val context: Context) {
val signed = signPacketBeforeBroadcast(packet)
// Use a stable transferId based on the unencrypted file TLV payload for progress tracking
val transferId = sha256Hex(filePayload)
connectionManager.broadcastPacket(RoutedPacket(signed, transferId = transferId))
dispatchGlobal(RoutedPacket(signed, transferId = transferId))
Log.d(TAG, "✅ Sent encrypted file to $recipientPeerID")
} catch (e: Exception) {
@@ -861,7 +889,7 @@ class BluetoothMeshService(private val context: Context) {
// Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
Log.d(TAG, "📤 Sent encrypted private message to $recipientPeerID (${encrypted.size} bytes)")
// FIXED: Don't send didReceiveMessage for our own sent messages
@@ -932,7 +960,7 @@ class BluetoothMeshService(private val context: Context) {
// Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
Log.d(TAG, "📤 Sent read receipt to $recipientPeerID for message $messageID")
// Persist as read after successful send
@@ -986,7 +1014,7 @@ class BluetoothMeshService(private val context: Context) {
announcePacket.copy(signature = signature)
} ?: announcePacket
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
Log.d(TAG, "Sent iOS-compatible signed TLV announce (${tlvPayload.size} bytes)")
// Track announce for sync
try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { }
@@ -1035,7 +1063,7 @@ class BluetoothMeshService(private val context: Context) {
packet.copy(signature = signature)
} ?: packet
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
peerManager.markPeerAsAnnouncedTo(peerID)
Log.d(TAG, "Sent iOS-compatible signed TLV peer announce to $peerID (${tlvPayload.size} bytes)")
@@ -1056,7 +1084,7 @@ class BluetoothMeshService(private val context: Context) {
// Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
connectionManager.broadcastPacket(RoutedPacket(signedPacket))
dispatchGlobal(RoutedPacket(signedPacket))
}
/**