Add source-based routing support (#862)

* Add source-based routing support

* include neighbors in ANNOUNCE

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
jack
2025-10-28 12:38:44 +01:00
committed by GitHub
co-authored by jack callebtc
parent 2edbe2bbbd
commit 01256f3041
9 changed files with 504 additions and 26 deletions
+100 -10
View File
@@ -75,6 +75,7 @@ final class BLEService: NSObject {
formatter.dateFormat = "yyyyMMdd_HHmmss"
return formatter
}()
private let meshTopology = MeshTopologyTracker()
// 5. Fragment Reassembly (necessary for messages > MTU)
private struct FragmentKey: Hashable { let sender: UInt64; let id: UInt64 }
@@ -569,6 +570,7 @@ final class BLEService: NSObject {
peerToPeripheralUUID.removeAll()
subscribedCentrals.removeAll()
centralToPeerID.removeAll()
meshTopology.reset()
}
// MARK: Connectivity and peers
@@ -726,6 +728,8 @@ final class BLEService: NSObject {
version: 2
)
self.applyRouteIfAvailable(&packet, to: peerID)
if let signed = self.noiseService.signPacket(packet) {
packet = signed
}
@@ -745,7 +749,7 @@ final class BLEService: NSObject {
SecureLogger.debug("📤 Sending READ receipt for message \(receipt.originalMessageID) to \(peerID)", category: .session)
do {
let encrypted = try noiseService.encrypt(payload, for: peerID)
let packet = BitchatPacket(
var packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: myPeerIDData,
recipientID: Data(hexString: peerID.id),
@@ -754,6 +758,7 @@ final class BLEService: NSObject {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&packet, to: peerID)
broadcastPacket(packet)
} catch {
SecureLogger.error("Failed to send read receipt: \(error)")
@@ -1226,7 +1231,7 @@ final class BLEService: NSObject {
if noiseService.hasEstablishedSession(with: peerID) {
do {
let encrypted = try noiseService.encrypt(payload, for: peerID)
let packet = BitchatPacket(
var packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: myPeerIDData,
recipientID: Data(hexString: peerID.id),
@@ -1235,6 +1240,7 @@ final class BLEService: NSObject {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&packet, to: peerID)
broadcastPacket(packet)
} catch {
SecureLogger.error("Failed to send delivery ACK: \(error)")
@@ -1407,10 +1413,15 @@ final class BLEService: NSObject {
let noisePub = noiseService.getStaticPublicKeyData() // For noise handshakes and peer identification
let signingPub = noiseService.getSigningPublicKeyData() // For signature verification
let connectedPeerIDs: [Data] = collectionsQueue.sync {
peers.values.filter { $0.isConnected }.compactMap { $0.peerID.routingData }
}
let announcement = AnnouncementPacket(
nickname: myNickname,
noisePublicKey: noisePub,
signingPublicKey: signingPub
signingPublicKey: signingPub,
directNeighbors: connectedPeerIDs
)
guard let payload = announcement.encode() else {
@@ -1739,6 +1750,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph
peers[peerID] = info
}
}
clearDirectLink(with: peerID)
}
// Restart scanning with allow duplicates for faster rediscovery
@@ -2026,6 +2038,7 @@ extension BLEService: CBPeripheralDelegate {
peripherals[peripheralUUID] = state
}
peerToPeripheralUUID[senderID] = peripheralUUID
registerDirectLink(with: senderID)
}
let msgID = makeMessageID(for: packet)
@@ -2192,6 +2205,7 @@ extension BLEService: CBPeripheralManagerDelegate {
// Clean up mappings
centralToPeerID.removeValue(forKey: centralUUID)
clearDirectLink(with: peerID)
// Update UI immediately
notifyUI { [weak self] in
@@ -2308,7 +2322,10 @@ extension BLEService: CBPeripheralManagerDelegate {
subscribedCentrals.append(sorted[0].central)
}
if packet.type == MessageType.announce.rawValue {
if packet.ttl == messageTTL { centralToPeerID[centralUUID] = senderID }
if packet.ttl == messageTTL {
centralToPeerID[centralUUID] = senderID
registerDirectLink(with: senderID)
}
// Record ingress link for last-hop suppression then process
let msgID = makeMessageID(for: packet)
collectionsQueue.async(flags: .barrier) { [weak self] in
@@ -2418,6 +2435,63 @@ extension BLEService {
)
}
private func routingData(for peerID: PeerID) -> Data? {
peerID.toShort().routingData
}
private func registerDirectLink(with peerID: PeerID) {
meshTopology.recordDirectLink(between: myPeerIDData, and: routingData(for: peerID))
}
private func clearDirectLink(with peerID: PeerID) {
meshTopology.removeDirectLink(between: myPeerIDData, and: routingData(for: peerID))
}
private func registerRoute(_ route: [Data]?) {
guard let hops = route, !hops.isEmpty else { return }
meshTopology.recordRoute(hops)
}
private func computeRoute(to peerID: PeerID) -> [Data]? {
meshTopology.computeRoute(from: myPeerIDData, to: routingData(for: peerID))
}
private func applyRouteIfAvailable(_ packet: inout BitchatPacket, to recipient: PeerID) {
guard let route = computeRoute(to: recipient), route.count >= 2 else { return }
packet.route = route
meshTopology.recordRoute(route)
}
private func routingPeer(from data: Data) -> PeerID? {
PeerID(routingData: data)
}
private func forwardAlongRouteIfNeeded(_ packet: BitchatPacket) -> Bool {
guard let route = packet.route, !route.isEmpty else { return false }
let myRoutingData = routingData(for: myPeerID) ?? (myPeerIDData.isEmpty ? nil : myPeerIDData)
guard let selfData = myRoutingData,
let index = route.firstIndex(of: selfData) else { return false }
// No further hops: respect explicit route termination
if index == route.count - 1 {
return true
}
guard packet.ttl > 1 else { return true }
let nextHopData = route[index + 1]
guard let nextPeer = routingPeer(from: nextHopData),
isPeerConnected(nextPeer) else {
return false
}
registerDirectLink(with: nextPeer)
var relayPacket = packet
relayPacket.ttl = packet.ttl - 1
sendPacketDirected(relayPacket, to: nextPeer)
return true
}
/// Safely fetch the current direct-link state for a peer using the BLE queue.
private func linkState(for peerID: PeerID) -> (hasPeripheral: Bool, hasCentral: Bool) {
let computeState = { () -> (Bool, Bool) in
@@ -2451,6 +2525,7 @@ extension BLEService {
let fingerprint = noiseService.getIdentityFingerprint()
myPeerID = PeerID(str: fingerprint.prefix(16))
myPeerIDData = Data(hexString: myPeerID.id) ?? Data()
meshTopology.reset()
}
private func restartGossipManager() {
@@ -2469,7 +2544,7 @@ extension BLEService {
}
do {
let encrypted = try noiseService.encrypt(typedPayload, for: peerID)
let packet = BitchatPacket(
var packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: myPeerIDData,
recipientID: Data(hexString: peerID.id),
@@ -2478,6 +2553,7 @@ extension BLEService {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&packet, to: peerID)
broadcastPacket(packet)
} catch {
SecureLogger.error("Failed to send verification payload: \(error)")
@@ -2696,7 +2772,7 @@ extension BLEService {
}
}
let packet = BitchatPacket(
var packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: myPeerIDData,
recipientID: recipientData,
@@ -2705,6 +2781,7 @@ extension BLEService {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&packet, to: recipientID)
broadcastPacket(packet)
@@ -2744,7 +2821,7 @@ extension BLEService {
let handshakeData = try noiseService.initiateHandshake(with: peerID)
// Send handshake init
let packet = BitchatPacket(
var packet = BitchatPacket(
type: MessageType.noiseHandshake.rawValue,
senderID: myPeerIDData,
recipientID: Data(hexString: peerID.id),
@@ -2753,6 +2830,7 @@ extension BLEService {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&packet, to: peerID)
broadcastPacket(packet)
} catch {
SecureLogger.error("Failed to initiate handshake: \(error)")
@@ -2786,7 +2864,7 @@ extension BLEService {
let encrypted = try noiseService.encrypt(messagePayload, for: peerID)
let packet = BitchatPacket(
var packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: myPeerIDData,
recipientID: Data(hexString: peerID.id),
@@ -2795,6 +2873,7 @@ extension BLEService {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&packet, to: peerID)
// We're already on messageQueue from the callback
broadcastPacket(packet)
@@ -2946,7 +3025,8 @@ extension BLEService {
timestamp: packet.timestamp,
payload: payload,
signature: nil,
ttl: packet.ttl
ttl: packet.ttl,
route: packet.route
)
let workItem = DispatchWorkItem { [weak self] in
@@ -3161,6 +3241,11 @@ extension BLEService {
}
return
}
registerRoute(packet.route)
if peerID != myPeerID && packet.ttl == messageTTL {
registerDirectLink(with: peerID)
}
// Deduplication (thread-safe)
let senderID = PeerID(hexData: packet.senderID)
@@ -3245,6 +3330,10 @@ extension BLEService {
break
}
if forwardAlongRouteIfNeeded(packet) {
return
}
// Relay if TTL > 1 and we're not the original sender
// Relay decision and scheduling (extracted via RelayController)
do {
@@ -3591,7 +3680,7 @@ extension BLEService {
do {
if let response = try noiseService.processHandshakeMessage(from: peerID, message: packet.payload) {
// Send response
let responsePacket = BitchatPacket(
var responsePacket = BitchatPacket(
type: MessageType.noiseHandshake.rawValue,
senderID: myPeerIDData,
recipientID: Data(hexString: peerID.id),
@@ -3600,6 +3689,7 @@ extension BLEService {
signature: nil,
ttl: messageTTL
)
applyRouteIfAvailable(&responsePacket, to: peerID)
// We're on messageQueue from delegate callback
broadcastPacket(responsePacket)
}