mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 09:25:19 +00:00
Merge pull request #57 from GUVWAF/routingOpt
Cheap routing optimizations
This commit is contained in:
@@ -9,6 +9,8 @@ import android.os.ParcelUuid
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
|
import com.bitchat.android.protocol.SpecialRecipients
|
||||||
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -53,6 +55,7 @@ class BluetoothConnectionManager(
|
|||||||
// Simplified connection tracking - reduced memory footprint
|
// Simplified connection tracking - reduced memory footprint
|
||||||
private val connectedDevices = ConcurrentHashMap<String, DeviceConnection>()
|
private val connectedDevices = ConcurrentHashMap<String, DeviceConnection>()
|
||||||
private val subscribedDevices = CopyOnWriteArrayList<BluetoothDevice>()
|
private val subscribedDevices = CopyOnWriteArrayList<BluetoothDevice>()
|
||||||
|
public val addressPeerMap = ConcurrentHashMap<String, String>()
|
||||||
|
|
||||||
// Connection attempt tracking with automatic cleanup
|
// Connection attempt tracking with automatic cleanup
|
||||||
private val pendingConnections = ConcurrentHashMap<String, ConnectionAttempt>()
|
private val pendingConnections = ConcurrentHashMap<String, ConnectionAttempt>()
|
||||||
@@ -199,47 +202,106 @@ class BluetoothConnectionManager(
|
|||||||
powerManager.setAppBackgroundState(inBackground)
|
powerManager.setAppBackgroundState(inBackground)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to send data to a single device (server side)
|
||||||
|
private fun notifyDevice(device: BluetoothDevice, data: ByteArray): Boolean {
|
||||||
|
return try {
|
||||||
|
characteristic?.let { char ->
|
||||||
|
char.value = data
|
||||||
|
val result = gattServer?.notifyCharacteristicChanged(device, char, false) ?: false
|
||||||
|
result
|
||||||
|
} ?: false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Error sending to server connection ${device.address}: ${e.message}")
|
||||||
|
connectionScope.launch {
|
||||||
|
delay(CLEANUP_DELAY)
|
||||||
|
subscribedDevices.remove(device)
|
||||||
|
addressPeerMap.remove(device.address)
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send data to a single device (client side)
|
||||||
|
private fun writeToDeviceConn(deviceConn: DeviceConnection, data: ByteArray): Boolean {
|
||||||
|
return try {
|
||||||
|
deviceConn.characteristic?.let { char ->
|
||||||
|
char.value = data
|
||||||
|
val result = deviceConn.gatt?.writeCharacteristic(char) ?: false
|
||||||
|
result
|
||||||
|
} ?: false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Error sending to client connection ${deviceConn.device.address}: ${e.message}")
|
||||||
|
connectionScope.launch {
|
||||||
|
delay(CLEANUP_DELAY)
|
||||||
|
cleanupDeviceConnection(deviceConn.device.address)
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast packet to connected devices with connection limit enforcement
|
* Broadcast packet to connected devices with connection limit enforcement
|
||||||
*/
|
*/
|
||||||
fun broadcastPacket(packet: BitchatPacket) {
|
fun broadcastPacket(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
|
||||||
if (!isActive) return
|
if (!isActive) return
|
||||||
|
|
||||||
val data = packet.toBinaryData() ?: return
|
val data = packet.toBinaryData() ?: return
|
||||||
|
|
||||||
|
if (packet.recipientID != SpecialRecipients.BROADCAST) {
|
||||||
|
val recipientID = packet.recipientID?.let {
|
||||||
|
String(it).replace("\u0000", "").trim()
|
||||||
|
} ?: ""
|
||||||
|
|
||||||
|
// Try to find the recipient in server connections (subscribedDevices)
|
||||||
|
val targetDevice = subscribedDevices.firstOrNull { addressPeerMap[it.address] == recipientID }
|
||||||
|
// If found, send directly
|
||||||
|
if (targetDevice != null) {
|
||||||
|
Log.d(TAG, "Send packet type ${packet.type} directly to target device for recipient $recipientID: ${targetDevice.address}")
|
||||||
|
if (notifyDevice(targetDevice, data))
|
||||||
|
return // Sent, no need to continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the recipient in client connections (connectedDevices)
|
||||||
|
val targetDeviceConn = connectedDevices.values.firstOrNull { addressPeerMap[it.device.address] == recipientID }
|
||||||
|
// If found, send directly
|
||||||
|
if (targetDeviceConn != null) {
|
||||||
|
Log.d(TAG, "Send packet type ${packet.type} directly to target client connection for recipient $recipientID: ${targetDeviceConn.device.address}")
|
||||||
|
if (writeToDeviceConn(targetDeviceConn, data))
|
||||||
|
return // Sent, no need to continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, continue with broadcasting to all devices
|
||||||
Log.d(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections")
|
Log.d(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections")
|
||||||
|
|
||||||
|
val senderID = String(packet.senderID).replace("\u0000", "")
|
||||||
// Send to server connections (devices connected to our GATT server)
|
// Send to server connections (devices connected to our GATT server)
|
||||||
subscribedDevices.forEach { device ->
|
subscribedDevices.forEach { device ->
|
||||||
try {
|
if (device.address == routed.relayAddress) {
|
||||||
characteristic?.let { char ->
|
Log.d(TAG, "Skipping broadcast back to relayer: ${device.address}")
|
||||||
char.value = data
|
return@forEach
|
||||||
gattServer?.notifyCharacteristicChanged(device, char, false)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Error sending to server connection ${device.address}: ${e.message}")
|
|
||||||
// Clean up failed connection
|
|
||||||
connectionScope.launch {
|
|
||||||
delay(CLEANUP_DELAY)
|
|
||||||
subscribedDevices.remove(device)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (addressPeerMap[device.address] == senderID) {
|
||||||
|
Log.d(TAG, "Skipping broadcast back to sender: ${device.address}")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
notifyDevice(device, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to client connections
|
// Send to client connections
|
||||||
connectedDevices.values.forEach { deviceConn ->
|
connectedDevices.values.forEach { deviceConn ->
|
||||||
if (deviceConn.isClient && deviceConn.gatt != null && deviceConn.characteristic != null) {
|
if (deviceConn.isClient && deviceConn.gatt != null && deviceConn.characteristic != null) {
|
||||||
try {
|
if (deviceConn.device.address == routed.relayAddress) {
|
||||||
deviceConn.characteristic.value = data
|
Log.d(TAG, "Skipping broadcast back to relayer: ${deviceConn.device.address}")
|
||||||
deviceConn.gatt.writeCharacteristic(deviceConn.characteristic)
|
return@forEach
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Error sending to client connection ${deviceConn.device.address}: ${e.message}")
|
|
||||||
// Clean up failed connection
|
|
||||||
connectionScope.launch {
|
|
||||||
delay(CLEANUP_DELAY)
|
|
||||||
cleanupDeviceConnection(deviceConn.device.address)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (addressPeerMap[deviceConn.device.address] == senderID) {
|
||||||
|
Log.d(TAG, "Skipping broadcast back to sender: ${deviceConn.device.address}")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
writeToDeviceConn(deviceConn, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,6 +978,7 @@ class BluetoothConnectionManager(
|
|||||||
private fun cleanupDeviceConnection(deviceAddress: String) {
|
private fun cleanupDeviceConnection(deviceAddress: String) {
|
||||||
connectedDevices.remove(deviceAddress)?.let { deviceConn ->
|
connectedDevices.remove(deviceAddress)?.let { deviceConn ->
|
||||||
subscribedDevices.removeAll { it.address == deviceAddress }
|
subscribedDevices.removeAll { it.address == deviceAddress }
|
||||||
|
addressPeerMap.remove(deviceAddress)
|
||||||
}
|
}
|
||||||
// CRITICAL FIX: Always remove from pending connections when cleaning up
|
// CRITICAL FIX: Always remove from pending connections when cleaning up
|
||||||
// This prevents failed connections from blocking future attempts
|
// This prevents failed connections from blocking future attempts
|
||||||
@@ -944,6 +1007,7 @@ class BluetoothConnectionManager(
|
|||||||
private fun clearAllConnections() {
|
private fun clearAllConnections() {
|
||||||
connectedDevices.clear()
|
connectedDevices.clear()
|
||||||
subscribedDevices.clear()
|
subscribedDevices.clear()
|
||||||
|
addressPeerMap.clear()
|
||||||
pendingConnections.clear()
|
pendingConnections.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.util.Log
|
|||||||
import com.bitchat.android.crypto.EncryptionService
|
import com.bitchat.android.crypto.EncryptionService
|
||||||
import com.bitchat.android.crypto.MessagePadding
|
import com.bitchat.android.crypto.MessagePadding
|
||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import com.bitchat.android.model.DeliveryAck
|
import com.bitchat.android.model.DeliveryAck
|
||||||
import com.bitchat.android.model.ReadReceipt
|
import com.bitchat.android.model.ReadReceipt
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
@@ -101,10 +102,14 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
// SecurityManager delegate for key exchange notifications
|
// SecurityManager delegate for key exchange notifications
|
||||||
securityManager.delegate = object : SecurityManagerDelegate {
|
securityManager.delegate = object : SecurityManagerDelegate {
|
||||||
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) {
|
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) {
|
||||||
// Notify delegate about key exchange completion so it can register peer fingerprint
|
// Notify delegate about key exchange completion so it can register peer fingerprint
|
||||||
delegate?.registerPeerPublicKey(peerID, peerPublicKeyData)
|
delegate?.registerPeerPublicKey(peerID, peerPublicKeyData)
|
||||||
|
|
||||||
|
receivedAddress?.let { address ->
|
||||||
|
connectionManager.addressPeerMap[address] = peerID
|
||||||
|
}
|
||||||
|
|
||||||
// Send announcement and cached messages after key exchange
|
// Send announcement and cached messages after key exchange
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
delay(100)
|
delay(100)
|
||||||
@@ -127,7 +132,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun sendPacket(packet: BitchatPacket) {
|
override fun sendPacket(packet: BitchatPacket) {
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,11 +165,11 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
// Packet operations
|
// Packet operations
|
||||||
override fun sendPacket(packet: BitchatPacket) {
|
override fun sendPacket(packet: BitchatPacket) {
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun relayPacket(packet: BitchatPacket) {
|
override fun relayPacket(routed: RoutedPacket) {
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(routed)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBroadcastRecipient(): ByteArray {
|
override fun getBroadcastRecipient(): ByteArray {
|
||||||
@@ -221,32 +226,32 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
peerManager.updatePeerLastSeen(peerID)
|
peerManager.updatePeerLastSeen(peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean {
|
override fun handleKeyExchange(routed: RoutedPacket): Boolean {
|
||||||
return runBlocking { securityManager.handleKeyExchange(packet, peerID) }
|
return runBlocking { securityManager.handleKeyExchange(routed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAnnounce(packet: BitchatPacket, peerID: String) {
|
override fun handleAnnounce(routed: RoutedPacket) {
|
||||||
serviceScope.launch { messageHandler.handleAnnounce(packet, peerID) }
|
serviceScope.launch { messageHandler.handleAnnounce(routed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleMessage(packet: BitchatPacket, peerID: String) {
|
override fun handleMessage(routed: RoutedPacket) {
|
||||||
serviceScope.launch { messageHandler.handleMessage(packet, peerID) }
|
serviceScope.launch { messageHandler.handleMessage(routed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleLeave(packet: BitchatPacket, peerID: String) {
|
override fun handleLeave(routed: RoutedPacket) {
|
||||||
serviceScope.launch { messageHandler.handleLeave(packet, peerID) }
|
serviceScope.launch { messageHandler.handleLeave(routed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
|
override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
|
||||||
return fragmentManager.handleFragment(packet)
|
return fragmentManager.handleFragment(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleDeliveryAck(packet: BitchatPacket, peerID: String) {
|
override fun handleDeliveryAck(routed: RoutedPacket) {
|
||||||
serviceScope.launch { messageHandler.handleDeliveryAck(packet, peerID) }
|
serviceScope.launch { messageHandler.handleDeliveryAck(routed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleReadReceipt(packet: BitchatPacket, peerID: String) {
|
override fun handleReadReceipt(routed: RoutedPacket) {
|
||||||
serviceScope.launch { messageHandler.handleReadReceipt(packet, peerID) }
|
serviceScope.launch { messageHandler.handleReadReceipt(routed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendAnnouncementToPeer(peerID: String) {
|
override fun sendAnnouncementToPeer(peerID: String) {
|
||||||
@@ -257,15 +262,15 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
storeForwardManager.sendCachedMessages(peerID)
|
storeForwardManager.sendCachedMessages(peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun relayPacket(packet: BitchatPacket) {
|
override fun relayPacket(routed: RoutedPacket) {
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(routed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BluetoothConnectionManager delegates
|
// BluetoothConnectionManager delegates
|
||||||
connectionManager.delegate = object : BluetoothConnectionManagerDelegate {
|
connectionManager.delegate = object : BluetoothConnectionManagerDelegate {
|
||||||
override fun onPacketReceived(packet: BitchatPacket, peerID: String, device: android.bluetooth.BluetoothDevice?) {
|
override fun onPacketReceived(packet: BitchatPacket, peerID: String, device: android.bluetooth.BluetoothDevice?) {
|
||||||
packetProcessor.processPacket(packet, peerID)
|
packetProcessor.processPacket(RoutedPacket(packet, peerID, device?.address))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) {
|
override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) {
|
||||||
@@ -370,7 +375,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
// Send with random delay and retry for reliability
|
// Send with random delay and retry for reliability
|
||||||
// delay(Random.nextLong(50, 500))
|
// delay(Random.nextLong(50, 500))
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,7 +428,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
// Send with delay
|
// Send with delay
|
||||||
delay(Random.nextLong(50, 500))
|
delay(Random.nextLong(50, 500))
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -449,13 +454,13 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
// Send multiple times for reliability
|
// Send multiple times for reliability
|
||||||
delay(Random.nextLong(0, 500))
|
delay(Random.nextLong(0, 500))
|
||||||
connectionManager.broadcastPacket(announcePacket)
|
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
|
||||||
|
|
||||||
delay(500 + Random.nextLong(0, 500))
|
delay(500 + Random.nextLong(0, 500))
|
||||||
connectionManager.broadcastPacket(announcePacket)
|
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
|
||||||
|
|
||||||
delay(1000 + Random.nextLong(0, 500))
|
delay(1000 + Random.nextLong(0, 500))
|
||||||
connectionManager.broadcastPacket(announcePacket)
|
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +478,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
payload = nickname.toByteArray()
|
payload = nickname.toByteArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
peerManager.markPeerAsAnnouncedTo(peerID)
|
peerManager.markPeerAsAnnouncedTo(peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,7 +494,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
payload = publicKeyData
|
payload = publicKeyData
|
||||||
)
|
)
|
||||||
|
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
Log.d(TAG, "Sent key exchange")
|
Log.d(TAG, "Sent key exchange")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +510,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
payload = nickname.toByteArray()
|
payload = nickname.toByteArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
connectionManager.broadcastPacket(packet)
|
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.bitchat.android.crypto.MessagePadding
|
|||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
import com.bitchat.android.model.DeliveryAck
|
import com.bitchat.android.model.DeliveryAck
|
||||||
import com.bitchat.android.model.ReadReceipt
|
import com.bitchat.android.model.ReadReceipt
|
||||||
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -30,7 +31,10 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
/**
|
/**
|
||||||
* Handle announce message
|
* Handle announce message
|
||||||
*/
|
*/
|
||||||
suspend fun handleAnnounce(packet: BitchatPacket, peerID: String): Boolean {
|
suspend fun handleAnnounce(routed: RoutedPacket): Boolean {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
|
|
||||||
if (peerID == myPeerID) return false
|
if (peerID == myPeerID) return false
|
||||||
|
|
||||||
val nickname = String(packet.payload, Charsets.UTF_8)
|
val nickname = String(packet.payload, Charsets.UTF_8)
|
||||||
@@ -43,7 +47,7 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
if (packet.ttl > 1u) {
|
if (packet.ttl > 1u) {
|
||||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
||||||
delay(Random.nextLong(100, 300))
|
delay(Random.nextLong(100, 300))
|
||||||
delegate?.relayPacket(relayPacket)
|
delegate?.relayPacket(RoutedPacket(relayPacket, peerID, routed.relayAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
return isFirstAnnounce
|
return isFirstAnnounce
|
||||||
@@ -52,27 +56,31 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
/**
|
/**
|
||||||
* Handle broadcast or private message
|
* Handle broadcast or private message
|
||||||
*/
|
*/
|
||||||
suspend fun handleMessage(packet: BitchatPacket, peerID: String) {
|
suspend fun handleMessage(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
if (peerID == myPeerID) return
|
if (peerID == myPeerID) return
|
||||||
|
|
||||||
val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) }
|
val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) }
|
||||||
|
|
||||||
if (recipientID == null) {
|
if (recipientID == null) {
|
||||||
// BROADCAST MESSAGE
|
// BROADCAST MESSAGE
|
||||||
handleBroadcastMessage(packet, peerID)
|
handleBroadcastMessage(routed)
|
||||||
} else if (String(recipientID).replace("\u0000", "") == myPeerID) {
|
} else if (String(recipientID).replace("\u0000", "") == myPeerID) {
|
||||||
// PRIVATE MESSAGE FOR US
|
// PRIVATE MESSAGE FOR US
|
||||||
handlePrivateMessage(packet, peerID)
|
handlePrivateMessage(packet, peerID)
|
||||||
} else if (packet.ttl > 0u) {
|
} else if (packet.ttl > 0u) {
|
||||||
// RELAY MESSAGE
|
// RELAY MESSAGE
|
||||||
relayMessage(packet)
|
relayMessage(routed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle broadcast message
|
* Handle broadcast message
|
||||||
*/
|
*/
|
||||||
private suspend fun handleBroadcastMessage(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleBroadcastMessage(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
try {
|
try {
|
||||||
// Parse message
|
// Parse message
|
||||||
val message = BitchatMessage.fromBinaryPayload(packet.payload)
|
val message = BitchatMessage.fromBinaryPayload(packet.payload)
|
||||||
@@ -104,7 +112,7 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Relay broadcast messages
|
// Relay broadcast messages
|
||||||
relayMessage(packet)
|
relayMessage(routed)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to process broadcast message: ${e.message}")
|
Log.e(TAG, "Failed to process broadcast message: ${e.message}")
|
||||||
@@ -162,7 +170,9 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
/**
|
/**
|
||||||
* Handle leave message
|
* Handle leave message
|
||||||
*/
|
*/
|
||||||
suspend fun handleLeave(packet: BitchatPacket, peerID: String) {
|
suspend fun handleLeave(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
val content = String(packet.payload, Charsets.UTF_8)
|
val content = String(packet.payload, Charsets.UTF_8)
|
||||||
|
|
||||||
if (content.startsWith("#")) {
|
if (content.startsWith("#")) {
|
||||||
@@ -180,14 +190,16 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
// Relay if TTL > 0
|
// Relay if TTL > 0
|
||||||
if (packet.ttl > 1u) {
|
if (packet.ttl > 1u) {
|
||||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
||||||
delegate?.relayPacket(relayPacket)
|
delegate?.relayPacket(routed.copy(packet = relayPacket))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle delivery acknowledgment
|
* Handle delivery acknowledgment
|
||||||
*/
|
*/
|
||||||
suspend fun handleDeliveryAck(packet: BitchatPacket, peerID: String) {
|
suspend fun handleDeliveryAck(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
|
if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
|
||||||
try {
|
try {
|
||||||
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
|
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
|
||||||
@@ -200,17 +212,19 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to decrypt delivery ACK: ${e.message}")
|
Log.e(TAG, "Failed to decrypt delivery ACK: ${e.message}")
|
||||||
}
|
}
|
||||||
} else if (packet.ttl > 0u) {
|
} else if (packet.ttl > 0u && String(packet.senderID).replace("\u0000", "") != myPeerID) {
|
||||||
// Relay
|
// Relay
|
||||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
||||||
delegate?.relayPacket(relayPacket)
|
delegate?.relayPacket(routed.copy(packet = relayPacket))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle read receipt
|
* Handle read receipt
|
||||||
*/
|
*/
|
||||||
suspend fun handleReadReceipt(packet: BitchatPacket, peerID: String) {
|
suspend fun handleReadReceipt(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
|
if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
|
||||||
try {
|
try {
|
||||||
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
|
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
|
||||||
@@ -223,17 +237,18 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to decrypt read receipt: ${e.message}")
|
Log.e(TAG, "Failed to decrypt read receipt: ${e.message}")
|
||||||
}
|
}
|
||||||
} else if (packet.ttl > 0u) {
|
} else if (packet.ttl > 0u && String(packet.senderID).replace("\u0000", "") != myPeerID) {
|
||||||
// Relay
|
// Relay
|
||||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
||||||
delegate?.relayPacket(relayPacket)
|
delegate?.relayPacket(routed.copy(packet = relayPacket))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relay message with adaptive probability (same as iOS)
|
* Relay message with adaptive probability (same as iOS)
|
||||||
*/
|
*/
|
||||||
private suspend fun relayMessage(packet: BitchatPacket) {
|
private suspend fun relayMessage(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
if (packet.ttl == 0u.toUByte()) return
|
if (packet.ttl == 0u.toUByte()) return
|
||||||
|
|
||||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
||||||
@@ -253,7 +268,7 @@ class MessageHandler(private val myPeerID: String) {
|
|||||||
if (shouldRelay) {
|
if (shouldRelay) {
|
||||||
val delay = Random.nextLong(50, 500) // Random delay like iOS
|
val delay = Random.nextLong(50, 500) // Random delay like iOS
|
||||||
delay(delay)
|
delay(delay)
|
||||||
delegate?.relayPacket(relayPacket)
|
delegate?.relayPacket(routed.copy(packet = relayPacket))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +341,7 @@ interface MessageHandlerDelegate {
|
|||||||
|
|
||||||
// Packet operations
|
// Packet operations
|
||||||
fun sendPacket(packet: BitchatPacket)
|
fun sendPacket(packet: BitchatPacket)
|
||||||
fun relayPacket(packet: BitchatPacket)
|
fun relayPacket(routed: RoutedPacket)
|
||||||
fun getBroadcastRecipient(): ByteArray
|
fun getBroadcastRecipient(): ByteArray
|
||||||
|
|
||||||
// Cryptographic operations
|
// Cryptographic operations
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.bitchat.android.mesh
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,16 +25,19 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
/**
|
/**
|
||||||
* Process received packet - main entry point for all incoming packets
|
* Process received packet - main entry point for all incoming packets
|
||||||
*/
|
*/
|
||||||
fun processPacket(packet: BitchatPacket, peerID: String) {
|
fun processPacket(routed: RoutedPacket) {
|
||||||
processorScope.launch {
|
processorScope.launch {
|
||||||
handleReceivedPacket(packet, peerID)
|
handleReceivedPacket(routed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle received packet - core protocol logic (exact same as iOS)
|
* Handle received packet - core protocol logic (exact same as iOS)
|
||||||
*/
|
*/
|
||||||
private suspend fun handleReceivedPacket(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleReceivedPacket(routed: RoutedPacket) {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
|
|
||||||
// Basic validation and security checks
|
// Basic validation and security checks
|
||||||
if (!delegate?.validatePacketSecurity(packet, peerID)!!) {
|
if (!delegate?.validatePacketSecurity(packet, peerID)!!) {
|
||||||
Log.d(TAG, "Packet failed security validation from $peerID")
|
Log.d(TAG, "Packet failed security validation from $peerID")
|
||||||
@@ -47,15 +51,15 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
|
|
||||||
// Process based on message type (exact same logic as iOS)
|
// Process based on message type (exact same logic as iOS)
|
||||||
when (MessageType.fromValue(packet.type)) {
|
when (MessageType.fromValue(packet.type)) {
|
||||||
MessageType.KEY_EXCHANGE -> handleKeyExchange(packet, peerID)
|
MessageType.KEY_EXCHANGE -> handleKeyExchange(routed)
|
||||||
MessageType.ANNOUNCE -> handleAnnounce(packet, peerID)
|
MessageType.ANNOUNCE -> handleAnnounce(routed)
|
||||||
MessageType.MESSAGE -> handleMessage(packet, peerID)
|
MessageType.MESSAGE -> handleMessage(routed)
|
||||||
MessageType.LEAVE -> handleLeave(packet, peerID)
|
MessageType.LEAVE -> handleLeave(routed)
|
||||||
MessageType.FRAGMENT_START,
|
MessageType.FRAGMENT_START,
|
||||||
MessageType.FRAGMENT_CONTINUE,
|
MessageType.FRAGMENT_CONTINUE,
|
||||||
MessageType.FRAGMENT_END -> handleFragment(packet, peerID)
|
MessageType.FRAGMENT_END -> handleFragment(routed)
|
||||||
MessageType.DELIVERY_ACK -> handleDeliveryAck(packet, peerID)
|
MessageType.DELIVERY_ACK -> handleDeliveryAck(routed)
|
||||||
MessageType.READ_RECEIPT -> handleReadReceipt(packet, peerID)
|
MessageType.READ_RECEIPT -> handleReadReceipt(routed)
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(TAG, "Unknown message type: ${packet.type}")
|
Log.w(TAG, "Unknown message type: ${packet.type}")
|
||||||
}
|
}
|
||||||
@@ -65,10 +69,11 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
/**
|
/**
|
||||||
* Handle key exchange message
|
* Handle key exchange message
|
||||||
*/
|
*/
|
||||||
private suspend fun handleKeyExchange(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleKeyExchange(routed: RoutedPacket) {
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
Log.d(TAG, "Processing key exchange from $peerID")
|
Log.d(TAG, "Processing key exchange from $peerID")
|
||||||
|
|
||||||
val success = delegate?.handleKeyExchange(packet, peerID) ?: false
|
val success = delegate?.handleKeyExchange(routed) ?: false
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// Key exchange successful, send announce and cached messages
|
// Key exchange successful, send announce and cached messages
|
||||||
@@ -83,60 +88,60 @@ class PacketProcessor(private val myPeerID: String) {
|
|||||||
/**
|
/**
|
||||||
* Handle announce message
|
* Handle announce message
|
||||||
*/
|
*/
|
||||||
private suspend fun handleAnnounce(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleAnnounce(routed: RoutedPacket) {
|
||||||
Log.d(TAG, "Processing announce from $peerID")
|
Log.d(TAG, "Processing announce from ${routed.peerID}")
|
||||||
delegate?.handleAnnounce(packet, peerID)
|
delegate?.handleAnnounce(routed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle regular message
|
* Handle regular message
|
||||||
*/
|
*/
|
||||||
private suspend fun handleMessage(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleMessage(routed: RoutedPacket) {
|
||||||
Log.d(TAG, "Processing message from $peerID")
|
Log.d(TAG, "Processing message from ${routed.peerID}")
|
||||||
delegate?.handleMessage(packet, peerID)
|
delegate?.handleMessage(routed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle leave message
|
* Handle leave message
|
||||||
*/
|
*/
|
||||||
private suspend fun handleLeave(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleLeave(routed: RoutedPacket) {
|
||||||
Log.d(TAG, "Processing leave from $peerID")
|
Log.d(TAG, "Processing leave from ${routed.peerID}")
|
||||||
delegate?.handleLeave(packet, peerID)
|
delegate?.handleLeave(routed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle message fragments
|
* Handle message fragments
|
||||||
*/
|
*/
|
||||||
private suspend fun handleFragment(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleFragment(routed: RoutedPacket) {
|
||||||
Log.d(TAG, "Processing fragment from $peerID")
|
Log.d(TAG, "Processing fragment from ${routed.peerID}")
|
||||||
|
|
||||||
val reassembledPacket = delegate?.handleFragment(packet)
|
val reassembledPacket = delegate?.handleFragment(routed.packet)
|
||||||
if (reassembledPacket != null) {
|
if (reassembledPacket != null) {
|
||||||
Log.d(TAG, "Fragment reassembled, processing complete message")
|
Log.d(TAG, "Fragment reassembled, processing complete message")
|
||||||
handleReceivedPacket(reassembledPacket, peerID)
|
handleReceivedPacket(RoutedPacket(reassembledPacket, routed.peerID, routed.relayAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relay fragment regardless of reassembly
|
// Relay fragment regardless of reassembly
|
||||||
if (packet.ttl > 0u) {
|
if (routed.packet.ttl > 0u) {
|
||||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
val relayPacket = routed.packet.copy(ttl = (routed.packet.ttl - 1u).toUByte())
|
||||||
delegate?.relayPacket(relayPacket)
|
delegate?.relayPacket(RoutedPacket(relayPacket, routed.peerID, routed.relayAddress))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle delivery acknowledgment
|
* Handle delivery acknowledgment
|
||||||
*/
|
*/
|
||||||
private suspend fun handleDeliveryAck(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleDeliveryAck(routed: RoutedPacket) {
|
||||||
Log.d(TAG, "Processing delivery ACK from $peerID")
|
Log.d(TAG, "Processing delivery ACK from ${routed.peerID}")
|
||||||
delegate?.handleDeliveryAck(packet, peerID)
|
delegate?.handleDeliveryAck(routed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle read receipt
|
* Handle read receipt
|
||||||
*/
|
*/
|
||||||
private suspend fun handleReadReceipt(packet: BitchatPacket, peerID: String) {
|
private suspend fun handleReadReceipt(routed: RoutedPacket) {
|
||||||
Log.d(TAG, "Processing read receipt from $peerID")
|
Log.d(TAG, "Processing read receipt from ${routed.peerID}")
|
||||||
delegate?.handleReadReceipt(packet, peerID)
|
delegate?.handleReadReceipt(routed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,16 +174,16 @@ interface PacketProcessorDelegate {
|
|||||||
fun updatePeerLastSeen(peerID: String)
|
fun updatePeerLastSeen(peerID: String)
|
||||||
|
|
||||||
// Message type handlers
|
// Message type handlers
|
||||||
fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean
|
fun handleKeyExchange(routed: RoutedPacket): Boolean
|
||||||
fun handleAnnounce(packet: BitchatPacket, peerID: String)
|
fun handleAnnounce(routed: RoutedPacket)
|
||||||
fun handleMessage(packet: BitchatPacket, peerID: String)
|
fun handleMessage(routed: RoutedPacket)
|
||||||
fun handleLeave(packet: BitchatPacket, peerID: String)
|
fun handleLeave(routed: RoutedPacket)
|
||||||
fun handleFragment(packet: BitchatPacket): BitchatPacket?
|
fun handleFragment(packet: BitchatPacket): BitchatPacket?
|
||||||
fun handleDeliveryAck(packet: BitchatPacket, peerID: String)
|
fun handleDeliveryAck(routed: RoutedPacket)
|
||||||
fun handleReadReceipt(packet: BitchatPacket, peerID: String)
|
fun handleReadReceipt(routed: RoutedPacket)
|
||||||
|
|
||||||
// Communication
|
// Communication
|
||||||
fun sendAnnouncementToPeer(peerID: String)
|
fun sendAnnouncementToPeer(peerID: String)
|
||||||
fun sendCachedMessages(peerID: String)
|
fun sendCachedMessages(peerID: String)
|
||||||
fun relayPacket(packet: BitchatPacket)
|
fun relayPacket(routed: RoutedPacket)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.util.Log
|
|||||||
import com.bitchat.android.crypto.EncryptionService
|
import com.bitchat.android.crypto.EncryptionService
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
import com.bitchat.android.protocol.MessageType
|
import com.bitchat.android.protocol.MessageType
|
||||||
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.mutableSetOf
|
import kotlin.collections.mutableSetOf
|
||||||
@@ -88,7 +89,10 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
|||||||
/**
|
/**
|
||||||
* Handle key exchange packet
|
* Handle key exchange packet
|
||||||
*/
|
*/
|
||||||
suspend fun handleKeyExchange(packet: BitchatPacket, peerID: String): Boolean {
|
suspend fun handleKeyExchange(routed: RoutedPacket): Boolean {
|
||||||
|
val packet = routed.packet
|
||||||
|
val peerID = routed.peerID ?: "unknown"
|
||||||
|
|
||||||
if (peerID == myPeerID) return false
|
if (peerID == myPeerID) return false
|
||||||
|
|
||||||
if (packet.payload.isEmpty()) {
|
if (packet.payload.isEmpty()) {
|
||||||
@@ -113,7 +117,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
|||||||
Log.d(TAG, "Successfully processed key exchange from $peerID")
|
Log.d(TAG, "Successfully processed key exchange from $peerID")
|
||||||
|
|
||||||
// Notify delegate
|
// Notify delegate
|
||||||
delegate?.onKeyExchangeCompleted(peerID, packet.payload)
|
delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -315,5 +319,5 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
|||||||
* Delegate interface for security manager callbacks
|
* Delegate interface for security manager callbacks
|
||||||
*/
|
*/
|
||||||
interface SecurityManagerDelegate {
|
interface SecurityManagerDelegate {
|
||||||
fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray)
|
fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.bitchat.android.model
|
||||||
|
|
||||||
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a routed packet with additional metadata
|
||||||
|
* Used for processing and routing packets in the mesh network
|
||||||
|
*/
|
||||||
|
data class RoutedPacket(
|
||||||
|
val packet: BitchatPacket,
|
||||||
|
val peerID: String? = null, // Who sent it (parsed from packet.senderID)
|
||||||
|
val relayAddress: String? = null // Address it came from (for avoiding loopback)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user