mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 18:25:21 +00:00
source-based routing wip
This commit is contained in:
@@ -247,6 +247,16 @@ class BluetoothConnectionManager(
|
||||
serverManager.getCharacteristic()
|
||||
)
|
||||
}
|
||||
|
||||
fun sendToPeer(peerID: String, routed: RoutedPacket): Boolean {
|
||||
if (!isActive) return false
|
||||
return packetBroadcaster.sendToPeer(
|
||||
peerID,
|
||||
routed,
|
||||
serverManager.getGattServer(),
|
||||
serverManager.getCharacteristic()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Expose role controls for debug UI
|
||||
|
||||
@@ -401,6 +401,10 @@ class BluetoothMeshService(private val context: Context) {
|
||||
override fun relayPacket(routed: RoutedPacket) {
|
||||
connectionManager.broadcastPacket(routed)
|
||||
}
|
||||
|
||||
override fun sendToPeer(peerID: String, routed: RoutedPacket): Boolean {
|
||||
return connectionManager.sendToPeer(peerID, routed)
|
||||
}
|
||||
}
|
||||
|
||||
// BluetoothConnectionManager delegates
|
||||
@@ -987,21 +991,36 @@ class BluetoothMeshService(private val context: Context) {
|
||||
*/
|
||||
private fun signPacketBeforeBroadcast(packet: BitchatPacket): BitchatPacket {
|
||||
return try {
|
||||
// Optionally compute and attach a source route for addressed packets
|
||||
val withRoute = try {
|
||||
val rec = packet.recipientID
|
||||
if (rec != null && !rec.contentEquals(SpecialRecipients.BROADCAST)) {
|
||||
val dest = rec.joinToString("") { b -> "%02x".format(b) }
|
||||
val path = com.bitchat.android.services.meshgraph.RoutePlanner.shortestPath(myPeerID, dest)
|
||||
if (path != null && path.size >= 3) {
|
||||
// Exclude first (sender) and last (recipient); only intermediates
|
||||
val intermediates = path.subList(1, path.size - 1)
|
||||
val hopsBytes = intermediates.map { hexStringToByteArray(it) }
|
||||
packet.copy(route = hopsBytes)
|
||||
} else packet.copy(route = null)
|
||||
} else packet
|
||||
} catch (_: Exception) { packet }
|
||||
|
||||
// Get the canonical packet data for signing (without signature)
|
||||
val packetDataForSigning = packet.toBinaryDataForSigning()
|
||||
val packetDataForSigning = withRoute.toBinaryDataForSigning()
|
||||
if (packetDataForSigning == null) {
|
||||
Log.w(TAG, "Failed to encode packet type ${packet.type} for signing, sending unsigned")
|
||||
return packet
|
||||
return withRoute
|
||||
}
|
||||
|
||||
// Sign the packet data using our signing key
|
||||
val signature = encryptionService.signData(packetDataForSigning)
|
||||
if (signature != null) {
|
||||
Log.d(TAG, "✅ Signed packet type ${packet.type} (signature ${signature.size} bytes)")
|
||||
packet.copy(signature = signature)
|
||||
withRoute.copy(signature = signature)
|
||||
} else {
|
||||
Log.w(TAG, "Failed to sign packet type ${packet.type}, sending unsigned")
|
||||
packet
|
||||
withRoute
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error signing packet type ${packet.type}: ${e.message}, sending unsigned")
|
||||
|
||||
@@ -159,6 +159,46 @@ class BluetoothPacketBroadcaster(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Targeted send to a specific peer (by peerID) if directly connected.
|
||||
* Returns true if sent to at least one matching connection.
|
||||
*/
|
||||
fun sendToPeer(
|
||||
targetPeerID: String,
|
||||
routed: RoutedPacket,
|
||||
gattServer: BluetoothGattServer?,
|
||||
characteristic: BluetoothGattCharacteristic?
|
||||
): Boolean {
|
||||
val packet = routed.packet
|
||||
val data = packet.toBinaryData() ?: return false
|
||||
val typeName = MessageType.fromValue(packet.type)?.name ?: packet.type.toString()
|
||||
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
|
||||
val targetConn = connectionTracker.getConnectedDevices().values
|
||||
.firstOrNull { connectionTracker.addressPeerMap[it.device.address] == targetPeerID }
|
||||
if (targetConn != null) {
|
||||
if (writeToDeviceConn(targetConn, data)) {
|
||||
logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, targetPeerID, targetConn.device.address, packet.ttl)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal broadcast implementation - runs in serialized actor context
|
||||
|
||||
@@ -112,6 +112,9 @@ class PacketProcessor(private val myPeerID: String) {
|
||||
override fun broadcastPacket(routed: RoutedPacket) {
|
||||
delegate?.relayPacket(routed)
|
||||
}
|
||||
override fun sendToPeer(peerID: String, routed: RoutedPacket): Boolean {
|
||||
return delegate?.sendToPeer(peerID, routed) ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,4 +313,5 @@ interface PacketProcessorDelegate {
|
||||
fun sendAnnouncementToPeer(peerID: String)
|
||||
fun sendCachedMessages(peerID: String)
|
||||
fun relayPacket(routed: RoutedPacket)
|
||||
fun sendToPeer(peerID: String, routed: RoutedPacket): Boolean
|
||||
}
|
||||
|
||||
@@ -65,9 +65,35 @@ class PacketRelayManager(private val myPeerID: String) {
|
||||
val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte())
|
||||
Log.d(TAG, "Decremented TTL from ${'$'}{packet.ttl} to ${'$'}{relayPacket.ttl}")
|
||||
|
||||
// Source-based routing: if route is set and includes us, try targeted next-hop forwarding
|
||||
val route = relayPacket.route
|
||||
if (!route.isNullOrEmpty()) {
|
||||
val myIdBytes = hexStringToPeerBytes(myPeerID)
|
||||
val index = route.indexOfFirst { it.contentEquals(myIdBytes) }
|
||||
if (index >= 0) {
|
||||
val nextHopIdHex: String? = run {
|
||||
val nextIndex = index + 1
|
||||
if (nextIndex < route.size) {
|
||||
route[nextIndex].toHexString()
|
||||
} else {
|
||||
// We are the last intermediate; try final recipient as next hop
|
||||
relayPacket.recipientID?.toHexString()
|
||||
}
|
||||
}
|
||||
if (nextHopIdHex != null) {
|
||||
val success = try { delegate?.sendToPeer(nextHopIdHex, RoutedPacket(relayPacket, peerID, routed.relayAddress)) } catch (_: Exception) { false } ?: false
|
||||
if (success) {
|
||||
Log.i(TAG, "📦 Source-route relay: ${myPeerID.take(8)} -> ${nextHopIdHex.take(8)} (type ${'$'}{packet.type}, TTL ${'$'}{relayPacket.ttl})")
|
||||
return
|
||||
} else {
|
||||
Log.w(TAG, "Source-route next hop ${nextHopIdHex.take(8)} not directly connected; falling back to broadcast")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply relay logic based on packet type and debug switch
|
||||
val shouldRelay = isRelayEnabled() && shouldRelayPacket(relayPacket, peerID)
|
||||
|
||||
if (shouldRelay) {
|
||||
relayPacket(RoutedPacket(relayPacket, peerID, routed.relayAddress))
|
||||
} else {
|
||||
@@ -206,4 +232,17 @@ interface PacketRelayManagerDelegate {
|
||||
|
||||
// Packet operations
|
||||
fun broadcastPacket(routed: RoutedPacket)
|
||||
fun sendToPeer(peerID: String, routed: RoutedPacket): Boolean
|
||||
}
|
||||
|
||||
private fun hexStringToPeerBytes(hex: String): ByteArray {
|
||||
val result = ByteArray(8)
|
||||
var idx = 0
|
||||
var out = 0
|
||||
while (idx + 1 < hex.length && out < 8) {
|
||||
val b = hex.substring(idx, idx + 2).toIntOrNull(16)?.toByte() ?: 0
|
||||
result[out++] = b
|
||||
idx += 2
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user