mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 22:05:18 +00:00
Moves inbound Nostr Schnorr verification and NIP-17 gift-wrap decryption off the main actor into per-relay bounded AsyncStream pipelines (parallel across relays, ordered per relay), dedups before crypto, and removes the redundant second re-verify in NostrInboundPipeline/GeoPresenceTracker. Adds a panic-wipe generation counter so in-flight decrypts are discarded after a wipe. Rebased onto main; #1451's 256 KiB pre-parse byte cap and tag limits are preserved and still enforced before any crypto/allocation.
398 lines
23 KiB
Swift
398 lines
23 KiB
Swift
import Foundation
|
||
|
||
/// Centralized knobs for transport- and UI-related limits.
|
||
/// Keep values aligned with existing behavior when replacing magic numbers.
|
||
enum TransportConfig {
|
||
// BLE / Protocol
|
||
static let bleDefaultFragmentSize: Int = 469 // ~512 MTU minus protocol overhead
|
||
static let messageTTLDefault: UInt8 = 7 // Default TTL for mesh flooding
|
||
static let bleMaxInFlightAssemblies: Int = 128 // Cap concurrent fragment assemblies
|
||
static let bleHighDegreeThreshold: Int = 6 // For adaptive TTL/probabilistic relays
|
||
static let bleMaxConcurrentTransfers: Int = 2 // Limit simultaneous large media sends
|
||
static let bleFragmentRelayMinDelayMs: Int = 8 // Faster forwarding for media fragments
|
||
static let bleFragmentRelayMaxDelayMs: Int = 25 // Upper jitter bound for fragment relays
|
||
// Fragment relay TTL in sparse graphs; matches messageTTLDefault so media
|
||
// reaches as far as text. Dense graphs clamp harder in RelayController.
|
||
static let bleFragmentRelayTtlCap: UInt8 = 7
|
||
static let bleFragmentRelayTtlCapDense: UInt8 = 5 // Contain fragment floods in dense graphs
|
||
|
||
// Live voice (push-to-talk)
|
||
// Burst-content budget per voice packet. Sized so the Noise ciphertext
|
||
// (content + 1 type byte + 16 tag bytes) stays within MessagePadding's
|
||
// 256-byte bucket and the whole directed packet (16 header + 8 sender +
|
||
// 8 recipient + 256 payload = 288 bytes) rides one BLE frame — live audio
|
||
// must never enter the fragment scheduler, which caps concurrent
|
||
// transfers at 2 and would let voice starve file sends.
|
||
static let pttMaxBurstContentBytes: Int = 210
|
||
static let pttJitterBufferSeconds: TimeInterval = 0.35 // buffered audio before live playback starts
|
||
static let pttJitterDeadlineSeconds: TimeInterval = 0.5 // start anyway after this wall-clock wait
|
||
static let pttBurstEndTimeoutSeconds: TimeInterval = 3.0 // no frames -> burst considered ended
|
||
static let pttMaxConcurrentAssemblies: Int = 8 // concurrent inbound bursts cap
|
||
static let pttMaxBurstBytes: Int = 384 * 1024 // 120s at ~2KB/s + generous slack
|
||
static let pttFinishedBurstRegistrySeconds: TimeInterval = 600 // window to absorb the finalized note
|
||
// Inbound flood guard: a real burst arrives at ~2KB/s; allow 3x plus a
|
||
// small settling allowance before dropping a sender's frames.
|
||
static let pttInboundMaxBytesPerSecond: Int = 6_000
|
||
// Public bursts are live-only traffic: frames older than this are relay
|
||
// stragglers or replays, not audio anyone should start hearing.
|
||
static let pttPublicFrameMaxAgeSeconds: TimeInterval = 30
|
||
|
||
// Mesh diagnostics (/ping)
|
||
static let meshPingTimeoutSeconds: TimeInterval = 10 // Give up on a probe after this window
|
||
static let meshPingInboundMaxPerLink: Int = 5 // Inbound ping budget per ingress link (claimed sender is spoofable)...
|
||
static let meshPingInboundWindowSeconds: TimeInterval = 10 // ...per sliding window (anti-amplification)
|
||
|
||
// UI / Storage Caps
|
||
static let privateChatCap: Int = 1337
|
||
static let meshTimelineCap: Int = 1337
|
||
static let geoTimelineCap: Int = 1337
|
||
static let geoNicknameParticipantsCap: Int = 1337
|
||
static let contentLRUCap: Int = 2000
|
||
static let geoSamplingEventLRUCap: Int = 2000
|
||
|
||
// Timers
|
||
static let networkResetGraceSeconds: TimeInterval = 600 // 10 minutes
|
||
static let networkNotificationCooldownSeconds: TimeInterval = 300 // 5 minutes
|
||
static let basePublicFlushInterval: TimeInterval = 0.08 // ~12.5 fps batching
|
||
|
||
// BLE duty/announce/connect
|
||
static let bleConnectRateLimitInterval: TimeInterval = 0.5
|
||
static let bleMaxCentralLinks: Int = 6
|
||
static let bleDutyOnDuration: TimeInterval = 5.0
|
||
static let bleDutyOffDuration: TimeInterval = 10.0
|
||
static let bleAnnounceMinInterval: TimeInterval = 1.0
|
||
|
||
// BLE discovery/quality thresholds
|
||
static let bleDynamicRSSIThresholdDefault: Int = -90
|
||
static let bleConnectionCandidatesMax: Int = 100
|
||
static let blePendingWriteBufferCapBytes: Int = 1_000_000
|
||
static let bleNotificationAssemblerHardCapBytes: Int = 8 * 1024 * 1024
|
||
static let bleAssemblerStallResetMs: Int = 250
|
||
static let blePendingNotificationsCapCount: Int = 128
|
||
static let bleNotificationRetryDelayMs: Int = 25
|
||
static let bleNotificationRetryMaxAttempts: Int = 80
|
||
// Sample interval for notification backpressure logs (fire per fragment
|
||
// during media transfers).
|
||
static let bleBackpressureLogInterval: Int = 25
|
||
|
||
// Nostr
|
||
static let nostrReadAckInterval: TimeInterval = 0.35 // ~3 per second
|
||
static let nostrInboundEventDedupCap: Int = 4096
|
||
static let nostrInboundEventDedupTrimTarget: Int = 3072
|
||
static let nostrDuplicateEventLogInterval: Int = 50
|
||
// Sample interval for per-event debug logs on the inbound hot path.
|
||
static let nostrInboundEventLogInterval: Int = 100
|
||
// Reject oversized/untrusted relay frames before JSON parse / store.
|
||
static let nostrMaxInboundMessageBytes: Int = 256 * 1024
|
||
static let nostrMaxEventTags: Int = 64
|
||
static let nostrMaxEventTagValues: Int = 16
|
||
static let nostrMaxEventTagValueBytes: Int = 1024
|
||
// Bounded per-relay inbound frame buffer. Each relay connection owns its
|
||
// own serial verify pipeline; if a relay floods faster than its Schnorr
|
||
// verification drains, the oldest buffered frames for THAT relay are
|
||
// dropped (bufferingNewest) so one relay cannot stall other relays.
|
||
// Nostr inbound is already best-effort (relays are redundant and events
|
||
// replay), so dropping a flooding relay's backlog is safe. Together with
|
||
// nostrInboundMaxFrameBytes this caps buffered inbound bytes at
|
||
// cap × maxFrameBytes (128 MiB) per hostile relay — bounded, not zero.
|
||
static let nostrInboundPerRelayBufferCap: Int = 256
|
||
// Hard per-frame byte bound, applied as URLSessionWebSocketTask
|
||
// .maximumMessageSize (oversized frames fail the receive instead of
|
||
// buffering). BitChat's legitimate Nostr traffic is small: geohash chat /
|
||
// presence events (kind 20000/20001), kind-1 notes, and NIP-17
|
||
// gift-wrapped DMs carrying text payloads or receipts are all a few KiB,
|
||
// and most public relays reject events beyond ~64–256 KiB anyway. 512 KiB
|
||
// leaves an order-of-magnitude margin over anything we produce or expect
|
||
// while halving the URLSession default (1 MiB), so a hostile relay's
|
||
// worst-case buffered pile-up per connection is
|
||
// nostrInboundPerRelayBufferCap × 512 KiB = 128 MiB instead of 256 MiB.
|
||
static let nostrInboundMaxFrameBytes: Int = 512 * 1024
|
||
|
||
// Conversation store diagnostics (field observability)
|
||
// Sample interval for the periodic store-audit "OK" heartbeat line
|
||
// (first + every Nth audit); violations always log at error level.
|
||
static let conversationStoreAuditLogInterval: Int = 10
|
||
// Sample interval for the mirrored-republish debug line in the ID-only
|
||
// delivery fan-out (first + every Nth republish).
|
||
static let conversationStoreMirroredRepublishLogInterval: Int = 25
|
||
|
||
// UI thresholds
|
||
static let uiProcessedNostrEventsCap: Int = 2000
|
||
|
||
// UI rate limiters (token buckets)
|
||
static let uiSenderRateBucketCapacity: Double = 5
|
||
static let uiSenderRateBucketRefillPerSec: Double = 1.0
|
||
static let uiContentRateBucketCapacity: Double = 3
|
||
static let uiContentRateBucketRefillPerSec: Double = 0.5
|
||
// Bound attacker-keyed bucket maps (sender IDs / content digests).
|
||
static let uiSenderRateBucketMaxEntries: Int = 2000
|
||
static let uiContentRateBucketMaxEntries: Int = 2000
|
||
static let uiRateBucketIdleTTL: TimeInterval = 10 * 60
|
||
// Cap teleported-participant markers so remote events cannot grow the set.
|
||
static let geoTeleportedParticipantsCap: Int = 1337
|
||
|
||
// UI sleeps/delays
|
||
static let uiStartupInitialDelaySeconds: TimeInterval = 1.0
|
||
static let uiStartupPhaseDurationSeconds: TimeInterval = 2.0
|
||
static let uiReadReceiptRetryShortSeconds: TimeInterval = 0.1
|
||
static let uiReadReceiptRetryLongSeconds: TimeInterval = 0.5
|
||
static let uiBatchDispatchStaggerSeconds: TimeInterval = 0.15
|
||
static let uiScrollThrottleSeconds: TimeInterval = 0.5
|
||
static let uiAnimationMediumSeconds: TimeInterval = 0.2
|
||
static let uiRecentCutoffFiveMinutesSeconds: TimeInterval = 5 * 60
|
||
static let uiMeshEmptyConfirmationSeconds: TimeInterval = 30.0
|
||
|
||
// BLE maintenance & thresholds
|
||
static let bleMaintenanceInterval: TimeInterval = 5.0
|
||
static let bleMaintenanceLeewaySeconds: Int = 1
|
||
static let bleIsolationRelaxThresholdSeconds: TimeInterval = 30
|
||
// Isolated nodes accept the weakest usable links — a fringe connection
|
||
// beats no connection. Relaxed floor sits at CoreBluetooth's practical
|
||
// reporting limit so prolonged isolation gates on nothing but decode.
|
||
static let bleRSSIIsolatedBase: Int = -95
|
||
static let bleRSSIIsolatedRelaxed: Int = -100
|
||
static let bleRSSIConnectedThreshold: Int = -85
|
||
// How long without seeing traffic before we sanity-check the direct link
|
||
// Lowered to make connected→reachable icon changes react faster when walking out of range
|
||
static let blePeerInactivityTimeoutSeconds: TimeInterval = 8.0
|
||
// How long to retain a peer as "reachable" (not directly connected) since lastSeen.
|
||
// Must comfortably exceed the worst-case dense announce interval (38s) plus a
|
||
// missed cycle, so duty-cycled nodes don't forget peers between announces.
|
||
static let bleReachabilityRetentionVerifiedSeconds: TimeInterval = 60.0 // verified/favorites
|
||
static let bleReachabilityRetentionUnverifiedSeconds: TimeInterval = 45.0 // unknown/unverified
|
||
static let bleFragmentLifetimeSeconds: TimeInterval = 30.0
|
||
static let bleIngressRecordLifetimeSeconds: TimeInterval = 3.0
|
||
// At most one rotation rebind per link per window: TTL is not signed, so
|
||
// a replayed announce can forge "direct", and without a cooldown two
|
||
// identities could fight over a link in a rebind flip-flop.
|
||
static let bleLinkRebindCooldownSeconds: TimeInterval = 60.0
|
||
static let bleConnectTimeoutBackoffWindowSeconds: TimeInterval = 120.0
|
||
static let bleRecentPacketWindowSeconds: TimeInterval = 30.0
|
||
static let bleRecentPacketWindowMaxCount: Int = 100
|
||
// Keep scanning fully ON when we saw traffic very recently
|
||
static let bleRecentTrafficForceScanSeconds: TimeInterval = 10.0
|
||
static let bleThreadSleepWriteShortDelaySeconds: TimeInterval = 0.05
|
||
static let bleExpectedWritePerFragmentMs: Int = 20
|
||
static let bleExpectedWriteMaxMs: Int = 5000
|
||
// Fragment pacing: Conservative spacing to prevent BLE buffer overflow
|
||
// Aggressive pacing causes packet loss; needs 25-30ms between fragments for reliable delivery
|
||
static let bleFragmentSpacingMs: Int = 30
|
||
static let bleFragmentSpacingDirectedMs: Int = 25
|
||
static let bleAnnounceIntervalSeconds: TimeInterval = 4.0
|
||
static let bleDutyOnDurationDense: TimeInterval = 3.0
|
||
static let bleDutyOffDurationDense: TimeInterval = 15.0
|
||
static let bleConnectedAnnounceBaseSecondsDense: TimeInterval = 30.0
|
||
static let bleConnectedAnnounceBaseSecondsSparse: TimeInterval = 15.0
|
||
static let bleConnectedAnnounceJitterDense: TimeInterval = 8.0
|
||
static let bleConnectedAnnounceJitterSparse: TimeInterval = 4.0
|
||
|
||
// Location
|
||
static let locationDistanceFilterMeters: Double = 1000
|
||
// Live (channel sheet open) distance threshold for meaningful updates
|
||
static let locationDistanceFilterLiveMeters: Double = 10.0
|
||
static let locationLiveRefreshInterval: TimeInterval = 5.0
|
||
|
||
// Notifications (geohash)
|
||
static let uiGeoNotifyCooldownSeconds: TimeInterval = 60.0
|
||
static let uiGeoNotifySnippetMaxLen: Int = 80
|
||
|
||
// Nostr geohash
|
||
static let nostrGeohashInitialLookbackSeconds: TimeInterval = 3600
|
||
static let nostrGeohashInitialLimit: Int = 200
|
||
static let nostrGeoRelayCount: Int = 5
|
||
static let nostrGeohashSampleLookbackSeconds: TimeInterval = 300
|
||
static let nostrGeohashSampleLimit: Int = 100
|
||
static let nostrDMSubscribeLookbackSeconds: TimeInterval = 86400
|
||
// A sampled chat message this recent means "a conversation is happening
|
||
// there" for the empty-timeline nearby-activity hint.
|
||
static let uiGeohashChatActivityWindowSeconds: TimeInterval = 900
|
||
// Startup delay before reading the gossip archive for "heard here
|
||
// earlier" echoes; covers the archive's async disk restore.
|
||
static let uiArchivedEchoLoadDelaySeconds: TimeInterval = 1.5
|
||
// Dead drops: location notes left via /drop expire after this long.
|
||
static let locationDropExpirySeconds: TimeInterval = 24 * 60 * 60
|
||
// Poll cadence while geo notes wait for a relay connection (Tor warming
|
||
// up); re-subscribes as soon as one comes up.
|
||
static let uiGeoNotesConnectivityRetrySeconds: TimeInterval = 3.0
|
||
|
||
// Message deduplication
|
||
static let messageDedupMaxAgeSeconds: TimeInterval = 300
|
||
static let messageDedupMaxCount: Int = 1000
|
||
|
||
// Verification QR
|
||
static let verificationQRMaxAgeSeconds: TimeInterval = 5 * 60
|
||
|
||
// Nostr relay backoff
|
||
static let nostrRelayInitialBackoffSeconds: TimeInterval = 1.0
|
||
static let nostrRelayMaxBackoffSeconds: TimeInterval = 300.0
|
||
static let nostrRelayBackoffMultiplier: Double = 2.0
|
||
static let nostrRelayMaxReconnectAttempts: Int = 10
|
||
// Reconnect delays get ±20% random jitter so relays that dropped together
|
||
// (e.g. a network blip) don't thundering-herd the same reconnect instant.
|
||
static let nostrRelayBackoffJitterRatio: Double = 0.2
|
||
static let nostrRelayDefaultFetchLimit: Int = 100
|
||
// How many consecutive Tor-readiness waits (each bounded by TorManager's
|
||
// bootstrap deadline) to attempt before unblocking pending EOSE callers.
|
||
static let nostrTorReadyMaxWaitAttempts: Int = 3
|
||
static let nostrPendingSendQueueCap: Int = 200
|
||
// Sample interval for the send-queue overflow warning (first + every Nth
|
||
// dropped event). Drops are ephemeral presence/geo traffic — log-only.
|
||
static let nostrPendingSendDropLogInterval: Int = 10
|
||
// Pending (not-yet-flushed) REQs are bounded per relay: oldest-by-insertion
|
||
// eviction at the cap, plus an age sweep on connect attempts. Durable
|
||
// subscription intent survives in subscriptionRequestState either way.
|
||
static let nostrPendingSubscriptionsPerRelayCap: Int = 64
|
||
static let nostrPendingSubscriptionTTLSeconds: TimeInterval = 600.0
|
||
// Fallback deadline for treating a subscription's initial fetch as complete
|
||
// when a relay never sends EOSE (generous to cover Tor circuit setup).
|
||
static let nostrSubscriptionEOSEFallbackSeconds: TimeInterval = 10.0
|
||
// A bridge drop is durable only after NIP-20 OK. Relays that omit OK must
|
||
// not pin the router's in-flight state indefinitely.
|
||
static let nostrConfirmedSendAckTimeoutSeconds: TimeInterval = 10.0
|
||
// After this long, a relay marked permanently failed gets another chance.
|
||
static let nostrRelayFailureCooldownSeconds: TimeInterval = 600.0
|
||
|
||
// Geo relay directory
|
||
static let geoRelayFetchIntervalSeconds: TimeInterval = 60 * 60 * 24
|
||
static let geoRelayRefreshCheckIntervalSeconds: TimeInterval = 60 * 60
|
||
static let geoRelayRetryInitialSeconds: TimeInterval = 60
|
||
static let geoRelayRetryMaxSeconds: TimeInterval = 60 * 60
|
||
|
||
// BLE operational delays
|
||
static let bleInitialAnnounceDelaySeconds: TimeInterval = 0.6
|
||
static let bleConnectTimeoutSeconds: TimeInterval = 8.0
|
||
static let bleRestartScanDelaySeconds: TimeInterval = 0.1
|
||
static let blePostSubscribeAnnounceDelaySeconds: TimeInterval = 0.05
|
||
static let blePostAnnounceDelaySeconds: TimeInterval = 0.4
|
||
static let bleForceAnnounceMinIntervalSeconds: TimeInterval = 0.15
|
||
|
||
// BCH-01-004: Rate-limiting for subscription-triggered announces
|
||
// Prevents rapid enumeration attacks by rate-limiting announce responses
|
||
static let bleSubscriptionRateLimitMinSeconds: TimeInterval = 2.0 // Minimum interval between announces per central
|
||
static let bleSubscriptionRateLimitBackoffFactor: Double = 2.0 // Exponential backoff multiplier
|
||
static let bleSubscriptionRateLimitMaxBackoffSeconds: TimeInterval = 30.0 // Maximum backoff period
|
||
static let bleSubscriptionRateLimitWindowSeconds: TimeInterval = 60.0 // Window for tracking subscription attempts
|
||
static let bleSubscriptionRateLimitMaxAttempts: Int = 5 // Max attempts before extended cooldown
|
||
|
||
// Source routing (v2 directed packets)
|
||
// Longest path we will originate, in intermediate hops between us and the
|
||
// recipient. Keep small: every hop must be a fresh, confirmed, v2-capable
|
||
// node, and long stale paths fail more often than floods.
|
||
static let bleSourceRouteMaxIntermediateHops: Int = 4
|
||
// A routed send with no inbound traffic from the recipient within this
|
||
// window counts as a route failure.
|
||
static let bleSourceRouteConfirmationWindowSeconds: TimeInterval = 10.0
|
||
// After a route failure, directed sends to that recipient flood instead
|
||
// of routing until this lapses.
|
||
static let bleSourceRouteSuppressionSeconds: TimeInterval = 60.0
|
||
|
||
// Targeted fragment resync (REQUEST_SYNC fragmentIdFilter)
|
||
// A broadcast reassembly with no new fragment for this long is stalled
|
||
// and triggers a targeted REQUEST_SYNC naming its fragment stream.
|
||
static let bleFragmentResyncStallSeconds: TimeInterval = 5.0
|
||
// Minimum spacing between targeted resync requests for the same stream.
|
||
static let bleFragmentResyncRetrySeconds: TimeInterval = 10.0
|
||
|
||
// Store-and-forward for directed packets at relays. Spooled packets retry
|
||
// on each maintenance flush until the window lapses; a longer window lets
|
||
// brief link gaps (walking between rooms, reconnect churn) heal themselves.
|
||
static let bleDirectedSpoolWindowSeconds: TimeInterval = 60.0
|
||
|
||
// Log/UI debounce windows
|
||
// Shorter debounce so UI reacts faster while still suppressing duplicate callbacks
|
||
static let bleDisconnectNotifyDebounceSeconds: TimeInterval = 0.9
|
||
static let bleReconnectLogDebounceSeconds: TimeInterval = 2.0
|
||
|
||
// Background wake-on-proximity (iOS). Pending connects issued on
|
||
// backgrounding never expire at the OS level: the Bluetooth controller
|
||
// completes them whenever the peer reappears in range and relaunches the
|
||
// app via state restoration. Entries older than the BLE address-rotation
|
||
// window no longer map to a reachable address, so the cache prunes them.
|
||
static let bleRecentPeripheralCacheCap: Int = 16
|
||
static let bleRecentPeripheralMaxAgeSeconds: TimeInterval = 15 * 60
|
||
// Central slots kept free for connects driven by live background discovery
|
||
static let bleBackgroundPendingConnectSlotReserve: Int = 2
|
||
|
||
// Weak-link cooldown after connection timeouts
|
||
static let bleWeakLinkCooldownSeconds: TimeInterval = 30.0
|
||
static let bleWeakLinkRSSICutoff: Int = -90
|
||
// Rediscovery ignore windows after a failed link, by failure kind:
|
||
// a connect attempt that timed out means the peer likely isn't reachable,
|
||
// so back off; a dropped established connection (walked out of range)
|
||
// usually returns, so only pause long enough for CoreBluetooth to settle.
|
||
static let bleTimeoutDiscoveryIgnoreSeconds: TimeInterval = 15.0
|
||
static let bleDisconnectDiscoveryIgnoreSeconds: TimeInterval = 3.0
|
||
|
||
// Content hashing / formatting
|
||
static let contentKeyPrefixLength: Int = 256
|
||
static let uiLongMessageLengthThreshold: Int = 2000
|
||
static let uiVeryLongTokenThreshold: Int = 512
|
||
static let uiLongMessageLineLimit: Int = 30
|
||
static let uiFingerprintSampleCount: Int = 3
|
||
|
||
// UI color tuning
|
||
static let uiColorHueAvoidanceDelta: Double = 0.05
|
||
static let uiColorHueOffset: Double = 0.12
|
||
// Peer list palette
|
||
static let uiPeerPaletteSlots: Int = 36
|
||
static let uiPeerPaletteRingBrightnessDeltaLight: Double = 0.07
|
||
static let uiPeerPaletteRingBrightnessDeltaDark: Double = -0.07
|
||
|
||
// UI windowing (infinite scroll)
|
||
static let uiWindowInitialCountPublic: Int = 300
|
||
static let uiWindowInitialCountPrivate: Int = 300
|
||
static let uiWindowStepCount: Int = 200
|
||
|
||
// Share extension
|
||
static let uiShareExtensionDismissDelaySeconds: TimeInterval = 2.0
|
||
static let uiShareAcceptWindowSeconds: TimeInterval = 30.0
|
||
static let uiMigrationCutoffSeconds: TimeInterval = 24 * 60 * 60
|
||
|
||
// Gossip Sync Configuration
|
||
static let syncSeenCapacity: Int = 1000
|
||
static let syncGCSMaxBytes: Int = 400
|
||
static let syncGCSTargetFpr: Double = 0.01
|
||
// Fragments and file transfers keep the short window; whole public
|
||
// messages get hours so a phone walking between partitions carries the
|
||
// room's recent history with it (see syncPublicMessageMaxAgeSeconds).
|
||
static let syncMaxMessageAgeSeconds: TimeInterval = 900
|
||
// How far back public broadcast messages stay sync-able. Must not exceed
|
||
// the receive-side acceptance window (BLEPublicMessagePolicy uses this
|
||
// same constant) or served packets would be dropped as stale.
|
||
static let syncPublicMessageMaxAgeSeconds: TimeInterval = 6 * 60 * 60
|
||
static let syncMaintenanceIntervalSeconds: TimeInterval = 30.0
|
||
static let syncStalePeerCleanupIntervalSeconds: TimeInterval = 60.0
|
||
static let syncStalePeerTimeoutSeconds: TimeInterval = 60.0
|
||
static let syncFragmentCapacity: Int = 600
|
||
static let syncFileTransferCapacity: Int = 200
|
||
static let syncFragmentIntervalSeconds: TimeInterval = 30.0
|
||
static let syncFileTransferIntervalSeconds: TimeInterval = 60.0
|
||
static let syncMessageIntervalSeconds: TimeInterval = 15.0
|
||
static let syncResponseRateLimitMaxResponses: Int = 8
|
||
static let syncResponseRateLimitWindowSeconds: TimeInterval = 30.0
|
||
|
||
// Courier store-and-forward
|
||
// Initial spray-and-wait budget per deposited envelope: each courier may
|
||
// hand half its remaining copies to another courier on encounter, so a
|
||
// message diffuses through a moving crowd instead of riding one person.
|
||
static let courierInitialCopies: UInt8 = 4
|
||
// Cooldown between speculative multi-hop handovers of the same envelope
|
||
// toward a recipient heard only via relayed announces.
|
||
static let courierRemoteHandoverCooldownSeconds: TimeInterval = 10 * 60
|
||
// Recently opened courier inner message IDs kept for receiver-side dedup
|
||
// (redundant copies ride distinct seals, so only the inner ID matches).
|
||
static let courierOpenedMessageIDCap: Int = 512
|
||
|
||
// One-time prekey bundles (forward-secret courier sealing)
|
||
// Own gossip-sync round for bundles: modest cadence, bounded peer count,
|
||
// and a long freshness window so bundles persist mesh-wide while their
|
||
// owners are away.
|
||
static let syncPrekeyBundleCapacity: Int = 200
|
||
static let syncPrekeyBundleIntervalSeconds: TimeInterval = 60.0
|
||
static let syncPrekeyBundleMaxAgeSeconds: TimeInterval = 24 * 60 * 60
|
||
// Unforced re-broadcasts of our own (unchanged) bundle, piggybacked on
|
||
// announces, keep it alive in peers' gossip stores; changed bundles are
|
||
// sent immediately.
|
||
static let prekeyBundleRebroadcastSeconds: TimeInterval = 60 * 60
|
||
}
|