mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 03:45:20 +00:00
Improve BLE relay reliability (#665)
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -264,7 +264,9 @@ final class BLEService: NSObject {
|
|||||||
// MARK: - Helpers: IDs, selection, and write backpressure
|
// MARK: - Helpers: IDs, selection, and write backpressure
|
||||||
private func makeMessageID(for packet: BitchatPacket) -> String {
|
private func makeMessageID(for packet: BitchatPacket) -> String {
|
||||||
let senderID = packet.senderID.hexEncodedString()
|
let senderID = packet.senderID.hexEncodedString()
|
||||||
return "\(senderID)-\(packet.timestamp)-\(packet.type)"
|
let digest = SHA256.hash(data: packet.payload)
|
||||||
|
let digestPrefix = digest.prefix(4).map { String(format: "%02x", $0) }.joined()
|
||||||
|
return "\(senderID)-\(packet.timestamp)-\(packet.type)-\(digestPrefix)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private func subsetSizeForFanout(_ n: Int) -> Int {
|
private func subsetSizeForFanout(_ n: Int) -> Int {
|
||||||
@@ -1183,6 +1185,13 @@ final class BLEService: NSObject {
|
|||||||
// Determine last-hop link for this message to avoid echoing back
|
// Determine last-hop link for this message to avoid echoing back
|
||||||
let messageID = makeMessageID(for: packet)
|
let messageID = makeMessageID(for: packet)
|
||||||
let ingressLink: LinkID? = collectionsQueue.sync { ingressByMessageID[messageID]?.link }
|
let ingressLink: LinkID? = collectionsQueue.sync { ingressByMessageID[messageID]?.link }
|
||||||
|
let directedPeerHint: String? = {
|
||||||
|
if let explicit = directedOnlyPeer { return explicit }
|
||||||
|
if let recipient = packet.recipientID?.hexEncodedString(), !recipient.isEmpty {
|
||||||
|
return recipient
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
|
||||||
let states = snapshotPeripheralStates()
|
let states = snapshotPeripheralStates()
|
||||||
var minCentralWriteLen: Int?
|
var minCentralWriteLen: Int?
|
||||||
@@ -1236,7 +1245,7 @@ final class BLEService: NSObject {
|
|||||||
// Special-case control/presence messages: do NOT subset to maximize immediate coverage
|
// Special-case control/presence messages: do NOT subset to maximize immediate coverage
|
||||||
var selectedPeripheralIDs = Set(allowedPeripheralIDs)
|
var selectedPeripheralIDs = Set(allowedPeripheralIDs)
|
||||||
var selectedCentralIDs = Set(allowedCentralIDs)
|
var selectedCentralIDs = Set(allowedCentralIDs)
|
||||||
if directedOnlyPeer == nil
|
if directedPeerHint == nil
|
||||||
&& packet.type != MessageType.fragment.rawValue
|
&& packet.type != MessageType.fragment.rawValue
|
||||||
&& packet.type != MessageType.announce.rawValue
|
&& packet.type != MessageType.announce.rawValue
|
||||||
&& packet.type != MessageType.requestSync.rawValue {
|
&& packet.type != MessageType.requestSync.rawValue {
|
||||||
@@ -1247,7 +1256,7 @@ final class BLEService: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If directed and we currently have no links to forward on, spool for a short window
|
// If directed and we currently have no links to forward on, spool for a short window
|
||||||
if let only = directedOnlyPeer,
|
if let only = directedPeerHint,
|
||||||
selectedPeripheralIDs.isEmpty && selectedCentralIDs.isEmpty,
|
selectedPeripheralIDs.isEmpty && selectedCentralIDs.isEmpty,
|
||||||
(packet.type == MessageType.noiseEncrypted.rawValue || packet.type == MessageType.noiseHandshake.rawValue) {
|
(packet.type == MessageType.noiseEncrypted.rawValue || packet.type == MessageType.noiseHandshake.rawValue) {
|
||||||
spoolDirectedPacket(packet, recipientPeerID: only)
|
spoolDirectedPacket(packet, recipientPeerID: only)
|
||||||
|
|||||||
@@ -18,13 +18,17 @@ struct RelayController {
|
|||||||
isAnnounce: Bool,
|
isAnnounce: Bool,
|
||||||
degree: Int,
|
degree: Int,
|
||||||
highDegreeThreshold: Int) -> RelayDecision {
|
highDegreeThreshold: Int) -> RelayDecision {
|
||||||
|
let ttlCap = min(ttl, TransportConfig.messageTTLDefault)
|
||||||
|
|
||||||
// Suppress obvious non-relays
|
// Suppress obvious non-relays
|
||||||
if ttl <= 1 || senderIsSelf { return RelayDecision(shouldRelay: false, newTTL: ttl, delayMs: 0) }
|
if ttlCap <= 1 || senderIsSelf {
|
||||||
|
return RelayDecision(shouldRelay: false, newTTL: ttlCap, delayMs: 0)
|
||||||
|
}
|
||||||
|
|
||||||
// For session-critical or directed traffic, be deterministic and reliable
|
// For session-critical or directed traffic, be deterministic and reliable
|
||||||
if isHandshake || isDirectedFragment || isDirectedEncrypted {
|
if isHandshake || isDirectedFragment || isDirectedEncrypted {
|
||||||
// Always relay with no TTL cap for these types
|
// Always relay with no TTL cap for these types
|
||||||
let newTTL = (ttl &- 1)
|
let newTTL = ttlCap &- 1
|
||||||
// Slight jitter to desynchronize without adding too much latency
|
// Slight jitter to desynchronize without adding too much latency
|
||||||
// Tighter for faster multi-hop handshakes and directed DMs
|
// Tighter for faster multi-hop handshakes and directed DMs
|
||||||
let delayRange: ClosedRange<Int> = isHandshake ? 10...35 : 20...60
|
let delayRange: ClosedRange<Int> = isHandshake ? 10...35 : 20...60
|
||||||
@@ -32,28 +36,17 @@ struct RelayController {
|
|||||||
return RelayDecision(shouldRelay: true, newTTL: newTTL, delayMs: delayMs)
|
return RelayDecision(shouldRelay: true, newTTL: newTTL, delayMs: delayMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Degree-aware probability to reduce floods in dense graphs (broadcast/public)
|
|
||||||
let baseProb: Double
|
|
||||||
switch degree {
|
|
||||||
case 0...2: baseProb = 1.0
|
|
||||||
case 3...4: baseProb = 0.9
|
|
||||||
case 5...6: baseProb = 0.7
|
|
||||||
case 7...9: baseProb = 0.55
|
|
||||||
default: baseProb = 0.45
|
|
||||||
}
|
|
||||||
let prob = baseProb
|
|
||||||
let shouldRelay = Double.random(in: 0...1) <= prob
|
|
||||||
|
|
||||||
// TTL clamping for broadcast
|
// TTL clamping for broadcast
|
||||||
// - Dense graphs: keep very low to avoid floods
|
// - Dense graphs: keep lower but still allow multi-hop bridging
|
||||||
// - Sparse graphs: allow slightly longer reach for multi-hop discovery
|
// - Announces get a bit more headroom
|
||||||
// - Announces in sparse graphs get a bit more headroom
|
let ttlLimit: UInt8 = {
|
||||||
let ttlCap: UInt8 = {
|
if degree >= highDegreeThreshold {
|
||||||
if degree >= highDegreeThreshold { return 3 }
|
return max(UInt8(2), min(ttlCap, UInt8(5)))
|
||||||
return isAnnounce ? 7 : 6
|
}
|
||||||
|
let preferred = UInt8(isAnnounce ? 7 : 6)
|
||||||
|
return max(UInt8(2), min(ttlCap, preferred))
|
||||||
}()
|
}()
|
||||||
let clamped = max(1, min(ttl, ttlCap))
|
let newTTL = ttlLimit &- 1
|
||||||
let newTTL = clamped &- 1
|
|
||||||
|
|
||||||
// Wider jitter window to allow duplicate suppression to win more often
|
// Wider jitter window to allow duplicate suppression to win more often
|
||||||
// For sparse graphs (<=2), relay quickly to avoid cancellation races
|
// For sparse graphs (<=2), relay quickly to avoid cancellation races
|
||||||
@@ -64,6 +57,6 @@ struct RelayController {
|
|||||||
case 6...9: delayMs = Int.random(in: 80...180)
|
case 6...9: delayMs = Int.random(in: 80...180)
|
||||||
default: delayMs = Int.random(in: 100...220)
|
default: delayMs = Int.random(in: 100...220)
|
||||||
}
|
}
|
||||||
return RelayDecision(shouldRelay: shouldRelay, newTTL: newTTL, delayMs: delayMs)
|
return RelayDecision(shouldRelay: true, newTTL: newTTL, delayMs: delayMs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user