mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 23:45:19 +00:00
more robust
This commit is contained in:
@@ -869,7 +869,7 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic
|
|||||||
|
|
||||||
// Create NOISE_ENCRYPTED packet (not FILE_TRANSFER!)
|
// Create NOISE_ENCRYPTED packet (not FILE_TRANSFER!)
|
||||||
val packet = BitchatPacket(
|
val packet = BitchatPacket(
|
||||||
version = 1u,
|
version = if (encrypted.size > 0xFFFF) 2u else 1u,
|
||||||
type = MessageType.NOISE_ENCRYPTED.value,
|
type = MessageType.NOISE_ENCRYPTED.value,
|
||||||
senderID = hexStringToByteArray(myPeerID),
|
senderID = hexStringToByteArray(myPeerID),
|
||||||
recipientID = hexStringToByteArray(recipientPeerID),
|
recipientID = hexStringToByteArray(recipientPeerID),
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import kotlinx.coroutines.channels.actor
|
import kotlinx.coroutines.channels.actor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +115,7 @@ class BluetoothPacketBroadcaster(
|
|||||||
|
|
||||||
// Actor scope for the broadcaster
|
// Actor scope for the broadcaster
|
||||||
private val broadcasterScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val broadcasterScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
private val transferJobs = ConcurrentHashMap<String, Job>()
|
private val fragmentingSender = FragmentingPacketSender(connectionScope, fragmentManager, TAG)
|
||||||
|
|
||||||
// SERIALIZATION: Actor to serialize all broadcast operations
|
// SERIALIZATION: Actor to serialize all broadcast operations
|
||||||
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
|
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
|
||||||
@@ -139,71 +137,14 @@ class BluetoothPacketBroadcaster(
|
|||||||
gattServer: BluetoothGattServer?,
|
gattServer: BluetoothGattServer?,
|
||||||
characteristic: BluetoothGattCharacteristic?
|
characteristic: BluetoothGattCharacteristic?
|
||||||
) {
|
) {
|
||||||
val packet = routed.packet
|
fragmentingSender.send(routed, "BLE broadcast") { packet ->
|
||||||
val isFile = packet.type == MessageType.FILE_TRANSFER.value
|
broadcastSinglePacket(packet, gattServer, characteristic)
|
||||||
if (isFile) {
|
true
|
||||||
Log.d(TAG, "📤 Broadcasting FILE_TRANSFER: ${packet.payload.size} bytes")
|
|
||||||
}
|
|
||||||
// Prefer caller-provided transferId (e.g., for encrypted media), else derive for FILE_TRANSFER
|
|
||||||
val transferId = routed.transferId ?: (if (isFile) sha256Hex(packet.payload) else null)
|
|
||||||
// Check if we need to fragment
|
|
||||||
if (fragmentManager != null) {
|
|
||||||
val fragments = try {
|
|
||||||
fragmentManager.createFragments(packet)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "❌ Fragment creation failed: ${e.message}", e)
|
|
||||||
if (isFile) {
|
|
||||||
Log.e(TAG, "❌ File fragmentation failed for ${packet.payload.size} byte file")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (fragments.size > 1) {
|
|
||||||
if (isFile) {
|
|
||||||
Log.d(TAG, "🔀 File needs ${fragments.size} fragments")
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Fragmenting packet into ${fragments.size} fragments")
|
|
||||||
if (transferId != null) {
|
|
||||||
TransferProgressManager.start(transferId, fragments.size)
|
|
||||||
}
|
|
||||||
val job = connectionScope.launch {
|
|
||||||
var sent = 0
|
|
||||||
fragments.forEach { fragment ->
|
|
||||||
if (!isActive) return@launch
|
|
||||||
// If cancelled, stop sending remaining fragments
|
|
||||||
if (transferId != null && transferJobs[transferId]?.isCancelled == true) return@launch
|
|
||||||
broadcastSinglePacket(RoutedPacket(fragment, transferId = transferId), gattServer, characteristic)
|
|
||||||
// 20ms delay between fragments
|
|
||||||
delay(20)
|
|
||||||
if (transferId != null) {
|
|
||||||
sent += 1
|
|
||||||
TransferProgressManager.progress(transferId, sent, fragments.size)
|
|
||||||
if (sent == fragments.size) TransferProgressManager.complete(transferId, fragments.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (transferId != null) {
|
|
||||||
transferJobs[transferId] = job
|
|
||||||
job.invokeOnCompletion { transferJobs.remove(transferId) }
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send single packet if no fragmentation needed
|
|
||||||
if (transferId != null) {
|
|
||||||
TransferProgressManager.start(transferId, 1)
|
|
||||||
}
|
|
||||||
broadcastSinglePacket(routed, gattServer, characteristic)
|
|
||||||
if (transferId != null) {
|
|
||||||
TransferProgressManager.progress(transferId, 1, 1)
|
|
||||||
TransferProgressManager.complete(transferId, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelTransfer(transferId: String): Boolean {
|
fun cancelTransfer(transferId: String): Boolean {
|
||||||
val job = transferJobs.remove(transferId) ?: return false
|
return fragmentingSender.cancelTransfer(transferId)
|
||||||
job.cancel()
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -215,6 +156,18 @@ class BluetoothPacketBroadcaster(
|
|||||||
targetPeerID: String,
|
targetPeerID: String,
|
||||||
gattServer: BluetoothGattServer?,
|
gattServer: BluetoothGattServer?,
|
||||||
characteristic: BluetoothGattCharacteristic?
|
characteristic: BluetoothGattCharacteristic?
|
||||||
|
): Boolean {
|
||||||
|
if (!hasPeerConnection(targetPeerID)) return false
|
||||||
|
return fragmentingSender.send(routed, "BLE peer ${targetPeerID.take(8)}") { packet ->
|
||||||
|
sendSinglePacketToPeer(packet, targetPeerID, gattServer, characteristic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendSinglePacketToPeer(
|
||||||
|
routed: RoutedPacket,
|
||||||
|
targetPeerID: String,
|
||||||
|
gattServer: BluetoothGattServer?,
|
||||||
|
characteristic: BluetoothGattCharacteristic?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val packet = routed.packet
|
val packet = routed.packet
|
||||||
val data = packet.toBinaryData() ?: return false
|
val data = packet.toBinaryData() ?: return false
|
||||||
@@ -222,11 +175,6 @@ class BluetoothPacketBroadcaster(
|
|||||||
if (isFile) {
|
if (isFile) {
|
||||||
Log.d(TAG, "📤 Broadcasting FILE_TRANSFER: ${packet.payload.size} bytes")
|
Log.d(TAG, "📤 Broadcasting FILE_TRANSFER: ${packet.payload.size} bytes")
|
||||||
}
|
}
|
||||||
// Prefer caller-provided transferId (e.g., for encrypted media), else derive for FILE_TRANSFER
|
|
||||||
val transferId = routed.transferId ?: (if (isFile) sha256Hex(packet.payload) else null)
|
|
||||||
if (transferId != null) {
|
|
||||||
TransferProgressManager.start(transferId, 1)
|
|
||||||
}
|
|
||||||
val typeName = MessageType.fromValue(packet.type)?.name ?: packet.type.toString()
|
val typeName = MessageType.fromValue(packet.type)?.name ?: packet.type.toString()
|
||||||
val senderPeerID = routed.peerID ?: packet.senderID.toHexString()
|
val senderPeerID = routed.peerID ?: packet.senderID.toHexString()
|
||||||
val incomingAddr = routed.relayAddress
|
val incomingAddr = routed.relayAddress
|
||||||
@@ -241,10 +189,6 @@ class BluetoothPacketBroadcaster(
|
|||||||
if (serverTarget != null) {
|
if (serverTarget != null) {
|
||||||
if (notifyDevice(serverTarget, data, gattServer, characteristic)) {
|
if (notifyDevice(serverTarget, data, gattServer, characteristic)) {
|
||||||
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, serverTarget.address, packet.ttl, packet.version, routeInfo)
|
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, serverTarget.address, packet.ttl, packet.version, routeInfo)
|
||||||
if (transferId != null) {
|
|
||||||
TransferProgressManager.progress(transferId, 1, 1)
|
|
||||||
TransferProgressManager.complete(transferId, 1)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,10 +199,6 @@ class BluetoothPacketBroadcaster(
|
|||||||
if (clientTarget != null) {
|
if (clientTarget != null) {
|
||||||
if (writeToDeviceConn(clientTarget, data)) {
|
if (writeToDeviceConn(clientTarget, data)) {
|
||||||
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, clientTarget.device.address, packet.ttl, packet.version, routeInfo)
|
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, clientTarget.device.address, packet.ttl, packet.version, routeInfo)
|
||||||
if (transferId != null) {
|
|
||||||
TransferProgressManager.progress(transferId, 1, 1)
|
|
||||||
TransferProgressManager.complete(transferId, 1)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,12 +206,6 @@ class BluetoothPacketBroadcaster(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
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) }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public entry point for broadcasting - submits request to actor for serialization
|
* Public entry point for broadcasting - submits request to actor for serialization
|
||||||
@@ -303,34 +237,19 @@ class BluetoothPacketBroadcaster(
|
|||||||
gattServer: BluetoothGattServer?,
|
gattServer: BluetoothGattServer?,
|
||||||
characteristic: BluetoothGattCharacteristic?
|
characteristic: BluetoothGattCharacteristic?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val packet = routed.packet
|
if (!hasPeerConnection(targetPeerID)) return false
|
||||||
val data = packet.toBinaryData() ?: return false
|
return fragmentingSender.send(routed, "BLE peer ${targetPeerID.take(8)}") { packet ->
|
||||||
val typeName = MessageType.fromValue(packet.type)?.name ?: packet.type.toString()
|
sendSinglePacketToPeer(packet, targetPeerID, gattServer, characteristic)
|
||||||
val senderPeerID = routed.peerID ?: packet.senderID.toHexString()
|
|
||||||
val incomingAddr = routed.relayAddress
|
|
||||||
val incomingPeer = incomingAddr?.let { connectionTracker.addressPeerMap[it] }
|
|
||||||
val senderNick = senderPeerID.let { pid -> nicknameResolver?.invoke(pid) }
|
|
||||||
|
|
||||||
// Try server-side connections first
|
|
||||||
val targetDevice = connectionTracker.getSubscribedDevices()
|
|
||||||
.firstOrNull { connectionTracker.addressPeerMap[it.address] == targetPeerID }
|
|
||||||
if (targetDevice != null) {
|
|
||||||
if (notifyDevice(targetDevice, data, gattServer, characteristic)) {
|
|
||||||
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, targetDevice.address, packet.ttl)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try client-side connections next
|
private fun hasPeerConnection(targetPeerID: String): Boolean {
|
||||||
val targetConn = connectionTracker.getConnectedDevices().values
|
val hasServerTarget = connectionTracker.getSubscribedDevices()
|
||||||
.firstOrNull { connectionTracker.addressPeerMap[it.device.address] == targetPeerID }
|
.any { connectionTracker.addressPeerMap[it.address] == targetPeerID }
|
||||||
if (targetConn != null) {
|
if (hasServerTarget) return true
|
||||||
if (writeToDeviceConn(targetConn, data)) {
|
|
||||||
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, targetConn.device.address, packet.ttl)
|
return connectionTracker.getConnectedDevices().values
|
||||||
return true
|
.any { connectionTracker.addressPeerMap[it.device.address] == targetPeerID }
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.bitchat.android.mesh
|
package com.bitchat.android.mesh
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -131,6 +132,8 @@ abstract class MeshConnectionTracker(
|
|||||||
if (expired.isNotEmpty()) {
|
if (expired.isNotEmpty()) {
|
||||||
Log.d(tag, "Cleaned up ${expired.size} expired connection attempts")
|
Log.d(tag, "Cleaned up ${expired.size} expired connection attempts")
|
||||||
}
|
}
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
break
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(tag, "Error in periodic cleanup: ${e.message}")
|
Log.w(tag, "Error in periodic cleanup: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ class MeshCore(
|
|||||||
val np = NoisePayload(type = NoisePayloadType.FILE_TRANSFER, data = tlv).encode()
|
val np = NoisePayload(type = NoisePayloadType.FILE_TRANSFER, data = tlv).encode()
|
||||||
val enc = encryptionService.encrypt(np, recipientPeerID)
|
val enc = encryptionService.encrypt(np, recipientPeerID)
|
||||||
val packet = BitchatPacket(
|
val packet = BitchatPacket(
|
||||||
version = 1u,
|
version = if (enc.size > 0xFFFF) 2u else 1u,
|
||||||
type = MessageType.NOISE_ENCRYPTED.value,
|
type = MessageType.NOISE_ENCRYPTED.value,
|
||||||
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
|
senderID = MeshPacketUtils.hexStringToByteArray(myPeerID),
|
||||||
recipientID = MeshPacketUtils.hexStringToByteArray(recipientPeerID),
|
recipientID = MeshPacketUtils.hexStringToByteArray(recipientPeerID),
|
||||||
|
|||||||
@@ -255,6 +255,10 @@ object BinaryProtocol {
|
|||||||
if (packet.version >= 2u.toUByte()) {
|
if (packet.version >= 2u.toUByte()) {
|
||||||
buffer.putInt(payloadDataSize) // 4 bytes for v2+
|
buffer.putInt(payloadDataSize) // 4 bytes for v2+
|
||||||
} else {
|
} else {
|
||||||
|
if (payloadDataSize > 0xFFFF || (originalPayloadSize ?: 0) > 0xFFFF) {
|
||||||
|
Log.w("BinaryProtocol", "Cannot encode oversized v1 packet payload: $payloadDataSize bytes")
|
||||||
|
return null
|
||||||
|
}
|
||||||
buffer.putShort(payloadDataSize.toShort()) // 2 bytes for v1
|
buffer.putShort(payloadDataSize.toShort()) // 2 bytes for v1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,12 @@ class MeshForegroundService : Service() {
|
|||||||
|
|
||||||
private fun ensureMeshStarted() {
|
private fun ensureMeshStarted() {
|
||||||
if (isShuttingDown) return
|
if (isShuttingDown) return
|
||||||
|
try {
|
||||||
|
com.bitchat.android.wifiaware.WifiAwareController.startIfPossible()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("MeshForegroundService", "Failed to ensure Wi-Fi Aware transport: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
val bleEnabled = try {
|
val bleEnabled = try {
|
||||||
com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true)
|
com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|||||||
@@ -48,17 +48,20 @@ class WifiAwareConnectionTracker(
|
|||||||
|
|
||||||
// Ensure any pending/active network request is explicitly released
|
// Ensure any pending/active network request is explicitly released
|
||||||
releaseNetworkRequest(canonicalId)
|
releaseNetworkRequest(canonicalId)
|
||||||
|
removePendingConnection(id)
|
||||||
|
removePendingConnection(canonicalId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releaseNetworkRequest(id: String) {
|
fun releaseNetworkRequest(id: String) {
|
||||||
if (!networkCallbacks.containsKey(id)) return
|
val canonicalId = resolveCanonicalPeerId(id)
|
||||||
|
if (!networkCallbacks.containsKey(canonicalId)) return
|
||||||
|
|
||||||
// 3. Unregister network callback properly from ConnectivityManager
|
// 3. Unregister network callback properly from ConnectivityManager
|
||||||
networkCallbacks.remove(id)?.let {
|
networkCallbacks.remove(canonicalId)?.let {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Unregistering network callback for $id")
|
Log.d(TAG, "Unregistering network callback for $canonicalId")
|
||||||
cm.unregisterNetworkCallback(it)
|
cm.unregisterNetworkCallback(it)
|
||||||
} catch (e: Exception) { Log.w(TAG, "Error unregistering callback for $id: ${e.message}") }
|
} catch (e: Exception) { Log.w(TAG, "Error unregistering callback for $canonicalId: ${e.message}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,11 +128,37 @@ class WifiAwareConnectionTracker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addServerSocket(peerId: String, socket: ServerSocket) {
|
fun addServerSocket(peerId: String, socket: ServerSocket) {
|
||||||
serverSockets[peerId] = socket
|
val canonicalId = resolveCanonicalPeerId(peerId)
|
||||||
|
serverSockets.put(canonicalId, socket)?.let {
|
||||||
|
try { it.close() } catch (e: Exception) { Log.w(TAG, "Error closing replaced server socket for $peerId: ${e.message}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasOpenServerSocket(peerId: String): Boolean {
|
||||||
|
val canonicalId = resolveCanonicalPeerId(peerId)
|
||||||
|
val socket = serverSockets[canonicalId] ?: return false
|
||||||
|
if (!socket.isClosed) return true
|
||||||
|
serverSockets.remove(canonicalId)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeServerSocket(peerId: String) {
|
||||||
|
val canonicalId = resolveCanonicalPeerId(peerId)
|
||||||
|
serverSockets.remove(canonicalId)?.let {
|
||||||
|
try { it.close() } catch (e: Exception) { Log.w(TAG, "Error closing server socket for $peerId: ${e.message}") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addNetworkCallback(peerId: String, callback: ConnectivityManager.NetworkCallback) {
|
fun addNetworkCallback(peerId: String, callback: ConnectivityManager.NetworkCallback) {
|
||||||
networkCallbacks[peerId] = callback
|
val canonicalId = resolveCanonicalPeerId(peerId)
|
||||||
|
networkCallbacks.put(canonicalId, callback)?.let {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Replacing network callback for $canonicalId")
|
||||||
|
cm.unregisterNetworkCallback(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Error unregistering replaced callback for $canonicalId: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ object WifiAwareController {
|
|||||||
|
|
||||||
private var service: WifiAwareMeshService? = null
|
private var service: WifiAwareMeshService? = null
|
||||||
private var appContext: Context? = null
|
private var appContext: Context? = null
|
||||||
|
private val lifecycleLock = Any()
|
||||||
|
private var starting = false
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
@@ -71,23 +73,33 @@ object WifiAwareController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startIfPossible() {
|
fun startIfPossible() {
|
||||||
if (_running.value) {
|
val reusableService = synchronized(lifecycleLock) {
|
||||||
if (!_enabled.value) return
|
if (!_enabled.value) return
|
||||||
// If already marked running but we are calling this (e.g. after a drop),
|
val existing = service
|
||||||
// ensure we clean up the old service before restarting.
|
if (existing?.isRunning() == true) {
|
||||||
stop()
|
_running.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (starting) return
|
||||||
|
starting = true
|
||||||
|
existing
|
||||||
|
}
|
||||||
|
|
||||||
|
val ctx = appContext ?: run {
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (!_enabled.value) return
|
|
||||||
val ctx = appContext ?: return
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
Log.w(TAG, "Wi‑Fi Aware requires Android 10 (Q)+; disabled.")
|
Log.w(TAG, "Wi‑Fi Aware requires Android 10 (Q)+; disabled.")
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware not supported on this device (requires Android 10+)")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware not supported on this device (requires Android 10+)")) } catch (_: Exception) {}
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val awareManager = ctx.getSystemService(android.net.wifi.aware.WifiAwareManager::class.java)
|
val awareManager = ctx.getSystemService(android.net.wifi.aware.WifiAwareManager::class.java)
|
||||||
if (awareManager == null || !awareManager.isAvailable) {
|
if (awareManager == null || !awareManager.isAvailable) {
|
||||||
Log.w(TAG, "Wi-Fi Aware is not currently available; not starting")
|
Log.w(TAG, "Wi-Fi Aware is not currently available; not starting")
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware is not available on this device right now")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware is not available on this device right now")) } catch (_: Exception) {}
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +116,7 @@ object WifiAwareController {
|
|||||||
if (!locationEnabled) {
|
if (!locationEnabled) {
|
||||||
Log.w(TAG, "Location services are disabled; Wi-Fi Aware cannot start.")
|
Log.w(TAG, "Location services are disabled; Wi-Fi Aware cannot start.")
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Enable Location Services to start Wi-Fi Aware")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Enable Location Services to start Wi-Fi Aware")) } catch (_: Exception) {}
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,34 +126,54 @@ object WifiAwareController {
|
|||||||
if (!granted) {
|
if (!granted) {
|
||||||
Log.w(TAG, "Missing NEARBY_WIFI_DEVICES permission; not starting Wi‑Fi Aware")
|
Log.w(TAG, "Missing NEARBY_WIFI_DEVICES permission; not starting Wi‑Fi Aware")
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Grant Nearby Wi‑Fi Devices to start Wi‑Fi Aware")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Grant Nearby Wi‑Fi Devices to start Wi‑Fi Aware")) } catch (_: Exception) {}
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!_enabled.value) {
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "Instantiating WifiAwareMeshService...")
|
val startedService = reusableService ?: run {
|
||||||
val startedService = WifiAwareMeshService(ctx)
|
Log.i(TAG, "Instantiating WifiAwareMeshService...")
|
||||||
|
WifiAwareMeshService(ctx)
|
||||||
|
}
|
||||||
startedService.startServices()
|
startedService.startServices()
|
||||||
if (startedService.isRunning()) {
|
if (startedService.isRunning()) {
|
||||||
service = startedService
|
synchronized(lifecycleLock) {
|
||||||
_running.value = true
|
service = startedService
|
||||||
|
_running.value = true
|
||||||
|
}
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware started")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware started")) } catch (_: Exception) {}
|
||||||
} else {
|
} else {
|
||||||
try { startedService.stopServices() } catch (_: Exception) { }
|
if (reusableService == null) {
|
||||||
service = null
|
try { startedService.stopServices() } catch (_: Exception) { }
|
||||||
_running.value = false
|
}
|
||||||
|
synchronized(lifecycleLock) {
|
||||||
|
if (service === startedService) service = null
|
||||||
|
_running.value = false
|
||||||
|
}
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware did not start")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware did not start")) } catch (_: Exception) {}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e(TAG, "Failed to start WifiAwareMeshService", e)
|
Log.e(TAG, "Failed to start WifiAwareMeshService", e)
|
||||||
_running.value = false
|
_running.value = false
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware failed to start: ${e.message}")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware failed to start: ${e.message}")) } catch (_: Exception) {}
|
||||||
|
} finally {
|
||||||
|
synchronized(lifecycleLock) { starting = false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
try { service?.stopServices() } catch (_: Exception) { }
|
val stopped = synchronized(lifecycleLock) {
|
||||||
service = null
|
val current = service
|
||||||
_running.value = false
|
service = null
|
||||||
|
starting = false
|
||||||
|
_running.value = false
|
||||||
|
current
|
||||||
|
}
|
||||||
|
try { stopped?.stopServices() } catch (_: Exception) { }
|
||||||
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
|
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
|
||||||
_connectedPeers.value = emptyMap()
|
_connectedPeers.value = emptyMap()
|
||||||
_knownPeers.value = emptyMap()
|
_knownPeers.value = emptyMap()
|
||||||
@@ -149,7 +182,8 @@ object WifiAwareController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun onServiceStopped(stoppedService: WifiAwareMeshService) {
|
internal fun onServiceStopped(stoppedService: WifiAwareMeshService) {
|
||||||
if (service === stoppedService) {
|
synchronized(lifecycleLock) {
|
||||||
|
if (service !== stoppedService) return
|
||||||
service = null
|
service = null
|
||||||
_running.value = false
|
_running.value = false
|
||||||
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
|
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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.mesh.FragmentingPacketSender
|
||||||
import com.bitchat.android.mesh.MeshCore
|
import com.bitchat.android.mesh.MeshCore
|
||||||
import com.bitchat.android.mesh.MeshService
|
import com.bitchat.android.mesh.MeshService
|
||||||
import com.bitchat.android.mesh.MeshTransport
|
import com.bitchat.android.mesh.MeshTransport
|
||||||
@@ -25,6 +26,7 @@ import com.bitchat.android.protocol.SpecialRecipients
|
|||||||
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
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@@ -40,6 +42,7 @@ import java.nio.ByteBuffer
|
|||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WifiAware mesh service - LATEST
|
* WifiAware mesh service - LATEST
|
||||||
@@ -69,6 +72,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
private val wifiTransport = WifiAwareTransport()
|
private val wifiTransport = WifiAwareTransport()
|
||||||
private lateinit var meshCore: MeshCore
|
private lateinit var meshCore: MeshCore
|
||||||
|
private lateinit var fragmentingSender: FragmentingPacketSender
|
||||||
|
|
||||||
// Service-level notification manager for background (no-UI) DMs
|
// Service-level notification manager for background (no-UI) DMs
|
||||||
private val serviceNotificationManager = com.bitchat.android.ui.NotificationManager(
|
private val serviceNotificationManager = com.bitchat.android.ui.NotificationManager(
|
||||||
@@ -84,6 +88,8 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
private var subscribeSession: SubscribeDiscoverySession? = null
|
private var subscribeSession: SubscribeDiscoverySession? = null
|
||||||
private val listenerExec = Executors.newCachedThreadPool()
|
private val listenerExec = Executors.newCachedThreadPool()
|
||||||
private var isActive = false
|
private var isActive = false
|
||||||
|
@Volatile private var recoveryInProgress = false
|
||||||
|
private val sessionGeneration = AtomicInteger(0)
|
||||||
|
|
||||||
// Delegate
|
// Delegate
|
||||||
override var delegate: WifiAwareMeshDelegate? = null
|
override var delegate: WifiAwareMeshDelegate? = null
|
||||||
@@ -139,6 +145,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
fragmentingSender = FragmentingPacketSender(serviceScope, meshCore.fragmentManager, TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMessageReceived(message: BitchatMessage) {
|
private fun handleMessageReceived(message: BitchatMessage) {
|
||||||
@@ -206,7 +213,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
val packet = routed.packet
|
val packet = routed.packet
|
||||||
if (packet.senderID.toHexString() == myPeerID && !packet.route.isNullOrEmpty()) {
|
if (packet.senderID.toHexString() == myPeerID && !packet.route.isNullOrEmpty()) {
|
||||||
val firstHop = packet.route!![0].toHexString()
|
val firstHop = packet.route!![0].toHexString()
|
||||||
if (sendPacketToPeer(firstHop, packet)) {
|
if (sendRoutedPacketToPeer(firstHop, routed)) {
|
||||||
Log.d(TAG, "TX: source-routed packet sent only to first Wi-Fi hop ${firstHop.take(8)}")
|
Log.d(TAG, "TX: source-routed packet sent only to first Wi-Fi hop ${firstHop.take(8)}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -215,15 +222,15 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
|
|
||||||
val recipientId = packet.recipientID?.toHexString()
|
val recipientId = packet.recipientID?.toHexString()
|
||||||
if (recipientId != null && !packet.recipientID.contentEquals(SpecialRecipients.BROADCAST)) {
|
if (recipientId != null && !packet.recipientID.contentEquals(SpecialRecipients.BROADCAST)) {
|
||||||
if (sendPacketToPeer(recipientId, packet)) {
|
if (sendRoutedPacketToPeer(recipientId, routed)) {
|
||||||
Log.d(TAG, "TX: addressed packet sent directly to Wi-Fi peer ${recipientId.take(8)}")
|
Log.d(TAG, "TX: addressed packet sent directly to Wi-Fi peer ${recipientId.take(8)}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wi-Fi Aware uses full packets; no fragmentation
|
fragmentingSender.send(routed, "Wi-Fi Aware broadcast") { single ->
|
||||||
val data = packet.toBinaryData() ?: return
|
broadcastSinglePacket(single)
|
||||||
serviceScope.launch { broadcastRaw(data) }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose a public method so BLE can forward relays to Wi-Fi Aware
|
// Expose a public method so BLE can forward relays to Wi-Fi Aware
|
||||||
@@ -235,22 +242,40 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
* Send packet to connected peer.
|
* Send packet to connected peer.
|
||||||
*/
|
*/
|
||||||
private fun sendPacketToPeer(peerID: String, packet: BitchatPacket): Boolean {
|
private fun sendPacketToPeer(peerID: String, packet: BitchatPacket): Boolean {
|
||||||
// Wi-Fi Aware uses full packets; no fragmentation
|
return sendRoutedPacketToPeer(peerID, RoutedPacket(packet))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendRoutedPacketToPeer(peerID: String, routed: RoutedPacket): Boolean {
|
||||||
|
if (connectionTracker.getSocketForPeer(peerID) == null) {
|
||||||
|
Log.w(TAG, "TX: no socket for ${peerID.take(8)}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fragmentingSender.send(routed, "Wi-Fi Aware peer ${peerID.take(8)}") { single ->
|
||||||
|
sendSinglePacketToPeer(peerID, single.packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastSinglePacket(routed: RoutedPacket): Boolean {
|
||||||
|
val data = routed.packet.toBinaryData() ?: return false
|
||||||
|
broadcastRaw(data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendSinglePacketToPeer(peerID: String, packet: BitchatPacket): Boolean {
|
||||||
val data = packet.toBinaryData() ?: return false
|
val data = packet.toBinaryData() ?: return false
|
||||||
val sock = connectionTracker.getSocketForPeer(peerID)
|
val sock = connectionTracker.getSocketForPeer(peerID)
|
||||||
if (sock == null) {
|
if (sock == null) {
|
||||||
Log.w(TAG, "TX: no socket for ${peerID.take(8)}")
|
Log.w(TAG, "TX: no socket for ${peerID.take(8)}")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
serviceScope.launch {
|
try {
|
||||||
try {
|
sock.write(data)
|
||||||
sock.write(data)
|
Log.d(TAG, "TX: packet type=${packet.type} to ${peerID.take(8)} (bytes=${data.size})")
|
||||||
Log.d(TAG, "TX: packet type=${packet.type} to ${peerID.take(8)} (bytes=${data.size})")
|
return true
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "TX: write to ${peerID.take(8)} failed: ${e.message}")
|
Log.e(TAG, "TX: write to ${peerID.take(8)} failed: ${e.message}")
|
||||||
}
|
return false
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -273,12 +298,17 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
Log.i(TAG, "Wi-Fi Aware transport disabled by debug settings; not starting")
|
Log.i(TAG, "Wi-Fi Aware transport disabled by debug settings; not starting")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (recoveryInProgress) {
|
||||||
|
Log.i(TAG, "Wi-Fi Aware recovery cleanup still in progress; deferring start")
|
||||||
|
return
|
||||||
|
}
|
||||||
val manager = awareManager
|
val manager = awareManager
|
||||||
if (manager == null || !manager.isAvailable) {
|
if (manager == null || !manager.isAvailable) {
|
||||||
Log.w(TAG, "Wi-Fi Aware manager unavailable; not starting")
|
Log.w(TAG, "Wi-Fi Aware manager unavailable; not starting")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isActive = true
|
isActive = true
|
||||||
|
val generation = sessionGeneration.incrementAndGet()
|
||||||
Log.i(TAG, "Starting Wi-Fi Aware mesh with peer ID: $myPeerID")
|
Log.i(TAG, "Starting Wi-Fi Aware mesh with peer ID: $myPeerID")
|
||||||
|
|
||||||
manager.attach(object : AttachCallback() {
|
manager.attach(object : AttachCallback() {
|
||||||
@@ -288,6 +318,10 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
Manifest.permission.NEARBY_WIFI_DEVICES
|
Manifest.permission.NEARBY_WIFI_DEVICES
|
||||||
])
|
])
|
||||||
override fun onAttached(session: WifiAwareSession) {
|
override fun onAttached(session: WifiAwareSession) {
|
||||||
|
if (!isCurrentSession(generation)) {
|
||||||
|
session.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
wifiAwareSession = session
|
wifiAwareSession = session
|
||||||
Log.i(TAG, "Wi-Fi Aware attached; starting publish & subscribe (peerID=$myPeerID)")
|
Log.i(TAG, "Wi-Fi Aware attached; starting publish & subscribe (peerID=$myPeerID)")
|
||||||
|
|
||||||
@@ -299,6 +333,10 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
.build(),
|
.build(),
|
||||||
object : DiscoverySessionCallback() {
|
object : DiscoverySessionCallback() {
|
||||||
override fun onPublishStarted(pub: PublishDiscoverySession) {
|
override fun onPublishStarted(pub: PublishDiscoverySession) {
|
||||||
|
if (!isCurrentSession(generation)) {
|
||||||
|
pub.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
publishSession = pub
|
publishSession = pub
|
||||||
Log.d(TAG, "PUBLISH: onPublishStarted()")
|
Log.d(TAG, "PUBLISH: onPublishStarted()")
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi-Fi Aware Publish Started")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi-Fi Aware Publish Started")) } catch (_: Exception) {}
|
||||||
@@ -308,6 +346,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
serviceSpecificInfo: ByteArray,
|
serviceSpecificInfo: ByteArray,
|
||||||
matchFilter: List<ByteArray>
|
matchFilter: List<ByteArray>
|
||||||
) {
|
) {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
val peerId = try { String(serviceSpecificInfo) } catch (_: Exception) { "" }
|
val peerId = try { String(serviceSpecificInfo) } catch (_: Exception) { "" }
|
||||||
handleToPeerId[peerHandle] = peerId
|
handleToPeerId[peerHandle] = peerId
|
||||||
if (peerId.isNotBlank()) {
|
if (peerId.isNotBlank()) {
|
||||||
@@ -322,6 +361,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
peerHandle: PeerHandle,
|
peerHandle: PeerHandle,
|
||||||
message: ByteArray
|
message: ByteArray
|
||||||
) {
|
) {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
if (message.isEmpty()) return
|
if (message.isEmpty()) return
|
||||||
val subscriberId = try { String(message) } catch (_: Exception) { "" }
|
val subscriberId = try { String(message) } catch (_: Exception) { "" }
|
||||||
if (subscriberId == myPeerID) return
|
if (subscriberId == myPeerID) return
|
||||||
@@ -333,10 +373,11 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionTerminated() {
|
override fun onSessionTerminated() {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
Log.e(TAG, "PUBLISH: onSessionTerminated()")
|
Log.e(TAG, "PUBLISH: onSessionTerminated()")
|
||||||
publishSession = null
|
publishSession = null
|
||||||
val shouldRestart = isActive && com.bitchat.android.wifiaware.WifiAwareController.enabled.value
|
val shouldRestart = isActive && com.bitchat.android.wifiaware.WifiAwareController.enabled.value
|
||||||
handleUnexpectedStop()
|
handleUnexpectedStop(generation)
|
||||||
if (shouldRestart) {
|
if (shouldRestart) {
|
||||||
Log.i(TAG, "PUBLISH: Scheduling Wi-Fi Aware restart")
|
Log.i(TAG, "PUBLISH: Scheduling Wi-Fi Aware restart")
|
||||||
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(2000)
|
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(2000)
|
||||||
@@ -353,6 +394,10 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
.build(),
|
.build(),
|
||||||
object : DiscoverySessionCallback() {
|
object : DiscoverySessionCallback() {
|
||||||
override fun onSubscribeStarted(sub: SubscribeDiscoverySession) {
|
override fun onSubscribeStarted(sub: SubscribeDiscoverySession) {
|
||||||
|
if (!isCurrentSession(generation)) {
|
||||||
|
sub.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
subscribeSession = sub
|
subscribeSession = sub
|
||||||
Log.d(TAG, "SUBSCRIBE: onSubscribeStarted()")
|
Log.d(TAG, "SUBSCRIBE: onSubscribeStarted()")
|
||||||
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi-Fi Aware Subscribe Started")) } catch (_: Exception) {}
|
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi-Fi Aware Subscribe Started")) } catch (_: Exception) {}
|
||||||
@@ -362,6 +407,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
serviceSpecificInfo: ByteArray,
|
serviceSpecificInfo: ByteArray,
|
||||||
matchFilter: List<ByteArray>
|
matchFilter: List<ByteArray>
|
||||||
) {
|
) {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
val peerId = try { String(serviceSpecificInfo) } catch (_: Exception) { "" }
|
val peerId = try { String(serviceSpecificInfo) } catch (_: Exception) { "" }
|
||||||
handleToPeerId[peerHandle] = peerId
|
handleToPeerId[peerHandle] = peerId
|
||||||
val msgId = (System.nanoTime() and 0x7fffffff).toInt()
|
val msgId = (System.nanoTime() and 0x7fffffff).toInt()
|
||||||
@@ -375,6 +421,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
peerHandle: PeerHandle,
|
peerHandle: PeerHandle,
|
||||||
message: ByteArray
|
message: ByteArray
|
||||||
) {
|
) {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
if (message.isEmpty()) return
|
if (message.isEmpty()) return
|
||||||
val peerId = handleToPeerId[peerHandle] ?: return
|
val peerId = handleToPeerId[peerHandle] ?: return
|
||||||
if (peerId == myPeerID) return
|
if (peerId == myPeerID) return
|
||||||
@@ -384,10 +431,11 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionTerminated() {
|
override fun onSessionTerminated() {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
Log.e(TAG, "SUBSCRIBE: onSessionTerminated()")
|
Log.e(TAG, "SUBSCRIBE: onSessionTerminated()")
|
||||||
subscribeSession = null
|
subscribeSession = null
|
||||||
val shouldRestart = isActive && com.bitchat.android.wifiaware.WifiAwareController.enabled.value
|
val shouldRestart = isActive && com.bitchat.android.wifiaware.WifiAwareController.enabled.value
|
||||||
handleUnexpectedStop()
|
handleUnexpectedStop(generation)
|
||||||
if (shouldRestart) {
|
if (shouldRestart) {
|
||||||
Log.i(TAG, "SUBSCRIBE: Scheduling Wi-Fi Aware restart")
|
Log.i(TAG, "SUBSCRIBE: Scheduling Wi-Fi Aware restart")
|
||||||
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(2000)
|
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(2000)
|
||||||
@@ -398,15 +446,20 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun onAttachFailed() {
|
override fun onAttachFailed() {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
Log.e(TAG, "Wi-Fi Aware attach failed")
|
Log.e(TAG, "Wi-Fi Aware attach failed")
|
||||||
handleUnexpectedStop()
|
handleUnexpectedStop(generation)
|
||||||
|
if (com.bitchat.android.wifiaware.WifiAwareController.enabled.value) {
|
||||||
|
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(3000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAwareSessionTerminated() {
|
override fun onAwareSessionTerminated() {
|
||||||
|
if (!isCurrentSession(generation)) return
|
||||||
Log.e(TAG, "Aware Session Terminated unexpectedly")
|
Log.e(TAG, "Aware Session Terminated unexpectedly")
|
||||||
wifiAwareSession = null
|
wifiAwareSession = null
|
||||||
val shouldRestart = com.bitchat.android.wifiaware.WifiAwareController.enabled.value
|
val shouldRestart = com.bitchat.android.wifiaware.WifiAwareController.enabled.value
|
||||||
handleUnexpectedStop()
|
handleUnexpectedStop(generation)
|
||||||
if (shouldRestart) {
|
if (shouldRestart) {
|
||||||
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(3000)
|
com.bitchat.android.wifiaware.WifiAwareController.restartIfStillEnabled(3000)
|
||||||
}
|
}
|
||||||
@@ -427,6 +480,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
override fun stopServices() {
|
override fun stopServices() {
|
||||||
val wasActive = isActive
|
val wasActive = isActive
|
||||||
isActive = false
|
isActive = false
|
||||||
|
sessionGeneration.incrementAndGet()
|
||||||
Log.i(TAG, "Stopping Wi-Fi Aware mesh")
|
Log.i(TAG, "Stopping Wi-Fi Aware mesh")
|
||||||
|
|
||||||
// Unregister from bridge
|
// Unregister from bridge
|
||||||
@@ -456,27 +510,38 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnexpectedStop() {
|
private fun isCurrentSession(generation: Int): Boolean {
|
||||||
|
return generation == sessionGeneration.get() && isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleUnexpectedStop(generation: Int) {
|
||||||
|
if (generation != sessionGeneration.get()) return
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
com.bitchat.android.wifiaware.WifiAwareController.onServiceStopped(this)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
recoveryInProgress = true
|
||||||
isActive = false
|
isActive = false
|
||||||
TransportBridgeService.unregister("WIFI")
|
TransportBridgeService.unregister("WIFI")
|
||||||
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
|
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
|
||||||
|
val oldPublishSession = publishSession
|
||||||
|
val oldSubscribeSession = subscribeSession
|
||||||
|
val oldWifiAwareSession = wifiAwareSession
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
try { meshCore.stopCore() } catch (_: Exception) { }
|
try {
|
||||||
try { connectionTracker.stop() } catch (_: Exception) { }
|
try { meshCore.stopCore() } catch (_: Exception) { }
|
||||||
try { publishSession?.close() } catch (_: Exception) { }
|
try { connectionTracker.stop() } catch (_: Exception) { }
|
||||||
try { subscribeSession?.close() } catch (_: Exception) { }
|
try { oldPublishSession?.close() } catch (_: Exception) { }
|
||||||
try { wifiAwareSession?.close() } catch (_: Exception) { }
|
try { oldSubscribeSession?.close() } catch (_: Exception) { }
|
||||||
publishSession = null
|
try { oldWifiAwareSession?.close() } catch (_: Exception) { }
|
||||||
subscribeSession = null
|
if (generation == sessionGeneration.get() && !isActive) {
|
||||||
wifiAwareSession = null
|
if (publishSession === oldPublishSession) publishSession = null
|
||||||
handleToPeerId.clear()
|
if (subscribeSession === oldSubscribeSession) subscribeSession = null
|
||||||
try { meshCore.shutdown() } catch (_: Exception) { }
|
if (wifiAwareSession === oldWifiAwareSession) wifiAwareSession = null
|
||||||
com.bitchat.android.wifiaware.WifiAwareController.onServiceStopped(this@WifiAwareMeshService)
|
handleToPeerId.clear()
|
||||||
serviceScope.cancel()
|
}
|
||||||
|
} finally {
|
||||||
|
recoveryInProgress = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,16 +576,16 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
if (!connectionTracker.isConnectionAttemptAllowed(peerId)) continue
|
if (!connectionTracker.isConnectionAttemptAllowed(peerId)) continue
|
||||||
|
|
||||||
Log.i(TAG, "🔄 Maintenance: attempting reconnect to ${peerId.take(8)}")
|
Log.i(TAG, "🔄 Maintenance: attempting reconnect to ${peerId.take(8)}")
|
||||||
if (connectionTracker.addPendingConnection(peerId)) {
|
// Resend ping to trigger a fresh server-ready/data-path attempt.
|
||||||
// Resend ping to trigger handshake
|
val msgId = (System.nanoTime() and 0x7fffffff).toInt()
|
||||||
val msgId = (System.nanoTime() and 0x7fffffff).toInt()
|
try {
|
||||||
try {
|
subscribeSession?.sendMessage(handle, msgId, myPeerID.toByteArray())
|
||||||
subscribeSession?.sendMessage(handle, msgId, myPeerID.toByteArray())
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
Log.w(TAG, "Failed to send maintenance ping to ${peerId.take(8)}: ${e.message}")
|
||||||
Log.w(TAG, "Failed to send maintenance ping to ${peerId.take(8)}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
break
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error in connection maintenance: ${e.message}")
|
Log.e(TAG, "Error in connection maintenance: ${e.message}")
|
||||||
}
|
}
|
||||||
@@ -542,16 +607,23 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
val peerId = handleToPeerId[peerHandle] ?: return
|
val peerId = handleToPeerId[peerHandle] ?: return
|
||||||
if (!amIServerFor(peerId)) return
|
if (!amIServerFor(peerId)) return
|
||||||
|
|
||||||
if (connectionTracker.serverSockets.containsKey(peerId)) {
|
if (connectionTracker.hasOpenServerSocket(peerId)) {
|
||||||
Log.v(TAG, "↪ already serving $peerId, skipping")
|
Log.v(TAG, "↪ already serving $peerId, skipping")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!connectionTracker.addPendingConnection(peerId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val ss = ServerSocket()
|
val ss = ServerSocket()
|
||||||
try {
|
try {
|
||||||
ss.reuseAddress = true
|
ss.reuseAddress = true
|
||||||
ss.bind(java.net.InetSocketAddress(0))
|
ss.bind(java.net.InetSocketAddress(0))
|
||||||
} catch (e: Exception) { Log.e(TAG, "Failed to bind server socket", e) }
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to bind server socket", e)
|
||||||
|
handleNetworkFailure(peerId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
connectionTracker.addServerSocket(peerId, ss)
|
connectionTracker.addServerSocket(peerId, ss)
|
||||||
val port = ss.localPort
|
val port = ss.localPort
|
||||||
@@ -597,6 +669,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
serviceScope.launch { delay(150); sendBroadcastAnnounce() }
|
serviceScope.launch { delay(150); sendBroadcastAnnounce() }
|
||||||
} catch (ioe: IOException) {
|
} catch (ioe: IOException) {
|
||||||
Log.e(TAG, "SERVER: accept failed for ${peerId.take(8)}", ioe)
|
Log.e(TAG, "SERVER: accept failed for ${peerId.take(8)}", ioe)
|
||||||
|
handleNetworkFailure(peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,6 +760,9 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
Log.v(TAG, "↪ already client-connected to $peerId, skipping")
|
Log.v(TAG, "↪ already client-connected to $peerId, skipping")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!connectionTracker.addPendingConnection(peerId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val port = ByteBuffer.wrap(payload).order(ByteOrder.BIG_ENDIAN).int
|
val port = ByteBuffer.wrap(payload).order(ByteOrder.BIG_ENDIAN).int
|
||||||
Log.i(TAG, "CLIENT: Received server-ready from ${peerId.take(8)} on port $port. Requesting network...")
|
Log.i(TAG, "CLIENT: Received server-ready from ${peerId.take(8)} on port $port. Requesting network...")
|
||||||
@@ -758,6 +834,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
serviceScope.launch { delay(150); sendBroadcastAnnounce() }
|
serviceScope.launch { delay(150); sendBroadcastAnnounce() }
|
||||||
} catch (ioe: IOException) {
|
} catch (ioe: IOException) {
|
||||||
Log.e(TAG, "CLIENT: socket connect failed to ${peerId.take(8)}", ioe)
|
Log.e(TAG, "CLIENT: socket connect failed to ${peerId.take(8)}", ioe)
|
||||||
|
handleNetworkFailure(peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
@@ -867,11 +944,9 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
private fun handleNetworkFailure(peerId: String) {
|
private fun handleNetworkFailure(peerId: String) {
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
Log.d(TAG, "Network failure cleanup for: $peerId")
|
Log.d(TAG, "Network failure cleanup for: $peerId")
|
||||||
// Specifically release the callback if it didn't happen automatically
|
|
||||||
connectionTracker.releaseNetworkRequest(peerId)
|
|
||||||
|
|
||||||
if (!connectionTracker.isConnected(peerId)) {
|
if (!connectionTracker.isConnected(peerId)) {
|
||||||
val canonicalPeerId = connectionTracker.canonicalPeerId(peerId)
|
val canonicalPeerId = connectionTracker.canonicalPeerId(peerId)
|
||||||
|
connectionTracker.disconnect(peerId)
|
||||||
meshCore.removePeer(canonicalPeerId)
|
meshCore.removePeer(canonicalPeerId)
|
||||||
if (canonicalPeerId != peerId) {
|
if (canonicalPeerId != peerId) {
|
||||||
meshCore.removePeer(peerId)
|
meshCore.removePeer(peerId)
|
||||||
@@ -1133,6 +1208,9 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
override fun sendPacketToPeer(peerID: String, packet: BitchatPacket): Boolean {
|
override fun sendPacketToPeer(peerID: String, packet: BitchatPacket): Boolean {
|
||||||
return this@WifiAwareMeshService.sendPacketToPeer(peerID, packet)
|
return this@WifiAwareMeshService.sendPacketToPeer(peerID, packet)
|
||||||
}
|
}
|
||||||
|
override fun cancelTransfer(transferId: String): Boolean {
|
||||||
|
return fragmentingSender.cancelTransfer(transferId)
|
||||||
|
}
|
||||||
override fun getDeviceAddressForPeer(peerID: String): String? {
|
override fun getDeviceAddressForPeer(peerID: String): String? {
|
||||||
return connectionTracker.getSocketForPeer(peerID)?.let { resolveScopedAddress(it.rawSocket) }
|
return connectionTracker.getSocketForPeer(peerID)?.let { resolveScopedAddress(it.rawSocket) }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user