mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 13:45:22 +00:00
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so platform-specific code is never touched), with test targets indexed and the share extension built. 277 dead declarations removed or demoted: dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed- feature remnants (autocomplete command suggestions, back-swipe tuning, MediaSendError, GeohashParticipantTracker), unused Tor dormancy bindings, assign-only properties, unused parameters (renamed to _), and redundant public accessibility. 13 orphaned localization keys deleted across all 29 locales (old pre-#1392 location-notes UI, app_info warnings). Two real tests were flagged as unused because they never ran: Swift Testing methods missing @Test (NostrProtocolTests. testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests. testAssemblesCompressedLargeFrame). Re-armed both; they pass. Deliberately kept, now recorded in .periphery.baseline.json: iOS-only code invisible to the CI macOS scan, C FFI signatures, keep-alive NWPathMonitor reference, InboundEventKey.eventID (dedup semantics), wifiBulk capability bit (reserved for Wi-Fi bulk work, used by BitFoundation package tests), and the String secureClear cluster (exercised by package tests). New: .periphery.yml config and an advisory Dead Code CI job (mirrors the SwiftLint precedent from #1361) that fails on findings not in the committed baseline. Verified: full macOS app suite, BitFoundation (119) and BitLogger (13) package tests green; periphery scan --strict exits clean. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
98 lines
4.3 KiB
Swift
98 lines
4.3 KiB
Swift
import Foundation
|
|
|
|
// RelayDecision encapsulates a single relay scheduling choice.
|
|
struct RelayDecision {
|
|
let shouldRelay: Bool
|
|
let newTTL: UInt8
|
|
let delayMs: Int
|
|
}
|
|
|
|
// RelayController centralizes flood control policy for relays.
|
|
struct RelayController {
|
|
static func decide(ttl: UInt8,
|
|
senderIsSelf: Bool,
|
|
recipientIsSelf: Bool = false,
|
|
isEncrypted _: Bool,
|
|
isDirectedEncrypted: Bool,
|
|
isFragment: Bool,
|
|
isDirectedFragment: Bool,
|
|
isHandshake: Bool,
|
|
isAnnounce: Bool,
|
|
isRequestSync: Bool = false,
|
|
isUrgentBoardPost: Bool = false,
|
|
isVoiceFrame: Bool = false,
|
|
degree: Int,
|
|
highDegreeThreshold: Int) -> RelayDecision {
|
|
let ttlCap = min(ttl, TransportConfig.messageTTLDefault)
|
|
|
|
// REQUEST_SYNC is link-local: never relay it, even when a peer crafts
|
|
// one with TTL headroom to turn every reachable node into a responder.
|
|
if isRequestSync {
|
|
return RelayDecision(shouldRelay: false, newTTL: ttlCap, delayMs: 0)
|
|
}
|
|
|
|
// Suppress obvious non-relays
|
|
if ttlCap <= 1 || senderIsSelf || recipientIsSelf {
|
|
return RelayDecision(shouldRelay: false, newTTL: ttlCap, delayMs: 0)
|
|
}
|
|
|
|
// For session-critical or directed traffic, be deterministic and reliable
|
|
if isHandshake || isDirectedFragment || isDirectedEncrypted {
|
|
// Always relay with no TTL cap for these types
|
|
let newTTL = ttlCap &- 1
|
|
// Slight jitter to desynchronize without adding too much latency
|
|
// Tighter for faster multi-hop handshakes and directed DMs
|
|
let delayRange: ClosedRange<Int> = isHandshake ? 10...35 : 20...60
|
|
let delayMs = Int.random(in: delayRange)
|
|
return RelayDecision(shouldRelay: true, newTTL: newTTL, delayMs: delayMs)
|
|
}
|
|
|
|
// Live voice floods with the fragment policy: the dense clamp
|
|
// contains the sustained ~15 pkt/s per-talker stream, and the tight
|
|
// jitter window keeps per-hop latency inside the receiver's ~350 ms
|
|
// jitter buffer across multi-hop paths.
|
|
if isFragment || isVoiceFrame {
|
|
// Dense graphs clamp harder to contain full-fanout fragment floods;
|
|
// sparse graphs get full depth so media reaches as far as text.
|
|
let fragmentCap = degree >= highDegreeThreshold
|
|
? TransportConfig.bleFragmentRelayTtlCapDense
|
|
: TransportConfig.bleFragmentRelayTtlCap
|
|
let ttlLimit = min(ttlCap, fragmentCap)
|
|
guard ttlLimit > 1 else {
|
|
return RelayDecision(shouldRelay: false, newTTL: ttlLimit, delayMs: 0)
|
|
}
|
|
let newTTL = ttlLimit &- 1
|
|
let delayMs = Int.random(in: TransportConfig.bleFragmentRelayMinDelayMs...TransportConfig.bleFragmentRelayMaxDelayMs)
|
|
return RelayDecision(shouldRelay: true, newTTL: newTTL, delayMs: delayMs)
|
|
}
|
|
|
|
// TTL clamping for broadcast
|
|
// - Dense graphs: keep lower but still allow multi-hop bridging
|
|
// - Thin chains (degree <= 2): every hop counts and flood cost is
|
|
// minimal, so relay at full incoming depth
|
|
// - Announces (and urgent board posts) get a bit more headroom
|
|
let ttlLimit: UInt8 = {
|
|
if degree >= highDegreeThreshold {
|
|
return max(UInt8(2), min(ttlCap, UInt8(5)))
|
|
}
|
|
if degree <= 2 {
|
|
return ttlCap
|
|
}
|
|
let preferred = UInt8((isAnnounce || isUrgentBoardPost) ? 7 : 6)
|
|
return max(UInt8(2), min(ttlCap, preferred))
|
|
}()
|
|
let newTTL = ttlLimit &- 1
|
|
|
|
// Wider jitter window to allow duplicate suppression to win more often
|
|
// For sparse graphs (<=2), relay quickly to avoid cancellation races
|
|
let delayMs: Int
|
|
switch degree {
|
|
case 0...2: delayMs = Int.random(in: 10...40)
|
|
case 3...5: delayMs = Int.random(in: 60...150)
|
|
case 6...9: delayMs = Int.random(in: 80...180)
|
|
default: delayMs = Int.random(in: 100...220)
|
|
}
|
|
return RelayDecision(shouldRelay: true, newTTL: newTTL, delayMs: delayMs)
|
|
}
|
|
}
|