mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-27 06:45:22 +00:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user