mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 07:25:19 +00:00
Debugging the integration build on real iOS 26.5 devices over the air. - BitLogger DEBUG default minimumLevel now .debug (was .info) so a tap-launched sideload surfaces .debug decision logs without the BITCHAT_LOG_LEVEL env var; env still wins; release unchanged (.info, emits nothing). Extracted defaultMinimumLevel + a test asserting it. - Greppable bracket decision tags at .info (visible even at .info) via SecureLogger, ID-prefixes only, sanitized: [ROUTE] source-route vs flood origination (+reason) + fragment targeted-resync; [GW] uplink accept/reject, downlink rebroadcast/drop, loop skips, drain-timer; [WIFI] offer/accept/decline, AWDL connect, transfer start/complete+bytes, fallback+reason; [PREKEY] seal choice, consume, unknown-prekey open fail, bundle ingest, re-gossip; [SYNC] one concise summary per cycle + per-request served counts (no per-packet spam); [PING]/[TRACE] RTT + computed path. - New OSLog categories: mesh, gateway, transport. - Refactored BLESourceRouteOriginationPolicy.route -> decide returning a Decision enum (flood(reason)/route(hops)) so the flood reason is greppable and unit-tested. - DEBUG-only in-memory ring buffer (LogExportBuffer, ~2000 lines/512KB, thread-safe, off main thread) + App Info "Export Logs" share sheet (iOS UIActivityViewController / macOS NSSavePanel), VoiceOver labels. - DEBUG-only UDP LAN log sink (LogNetworkSink): opt-in, off by default, fire-and-forget, never blocks/throws; each line prefixed with the device nickname for demux; configured via App Info host:port fields. Same sanitized formatted line feeds buffer, sink, and export. All new code is #if DEBUG; release emits nothing and ships no export/sink UI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
60 lines
2.7 KiB
Swift
60 lines
2.7 KiB
Swift
import BitFoundation
|
|
import Foundation
|
|
|
|
/// Decides whether an outbound directed packet should carry a v2 source
|
|
/// route. Pure gating logic so BLEService's hot send path stays a thin wire.
|
|
enum BLESourceRouteOriginationPolicy {
|
|
/// Why a packet kept flood/direct-write behavior instead of routing.
|
|
/// The `rawValue` doubles as the greppable `[ROUTE]` log reason.
|
|
enum FloodReason: String {
|
|
case relayedNotOriginator = "not originator"
|
|
case broadcast = "broadcast recipient"
|
|
case noTTLHeadroom = "link-local ttl"
|
|
case recipientDirect = "recipient direct"
|
|
case routeSuppressed = "route suppressed→flood"
|
|
case noPath = "no v2 path"
|
|
}
|
|
|
|
/// The routing decision for an originated packet.
|
|
enum Decision: Equatable {
|
|
/// Keep flood/direct-write behavior unchanged; carries the reason.
|
|
case flood(FloodReason)
|
|
/// Originate a v2 source route over these intermediate hops.
|
|
case route([Data])
|
|
}
|
|
|
|
/// Returns whether to originate a v2 source route (with its hops) or keep
|
|
/// flood/direct-write, with the reason for the latter.
|
|
///
|
|
/// Routes are only originated when every gate passes:
|
|
/// - we authored the packet (relays must not rewrite and re-sign someone
|
|
/// else's packet; route-following for in-flight routed packets lives in
|
|
/// `BLERouteForwardingPolicy`),
|
|
/// - the packet is directed at a single peer (not broadcast),
|
|
/// - the packet has TTL headroom to traverse hops (link-local TTL-0
|
|
/// packets like REQUEST_SYNC never route),
|
|
/// - the recipient is not directly connected (a direct write already
|
|
/// delivers in one hop),
|
|
/// - routing to the recipient is not suppressed by a recent unconfirmed
|
|
/// routed send, and
|
|
/// - the topology yields a complete path.
|
|
static func decide(
|
|
for packet: BitchatPacket,
|
|
to recipient: PeerID,
|
|
localPeerIDData: Data,
|
|
isRecipientConnected: (PeerID) -> Bool,
|
|
shouldAttemptRoute: (PeerID) -> Bool,
|
|
computeRoute: (PeerID) -> [Data]?
|
|
) -> Decision {
|
|
guard packet.senderID == localPeerIDData else { return .flood(.relayedNotOriginator) }
|
|
guard let recipientData = packet.recipientID,
|
|
recipientData.count == 8,
|
|
!recipientData.allSatisfy({ $0 == 0xFF }) else { return .flood(.broadcast) }
|
|
guard packet.ttl > 1 else { return .flood(.noTTLHeadroom) }
|
|
guard !isRecipientConnected(recipient) else { return .flood(.recipientDirect) }
|
|
guard shouldAttemptRoute(recipient) else { return .flood(.routeSuppressed) }
|
|
guard let route = computeRoute(recipient), !route.isEmpty else { return .flood(.noPath) }
|
|
return .route(route)
|
|
}
|
|
}
|