* Improve BLE mesh reliability for large transfers
Major reliability improvements for fragment-based transfers (photos, files):
**Notification Queue Fixes**
- Fix silent packet loss when notification queue is full - now queues for retry
- Fix retry queue bug where remaining items were lost when one retry failed
- Add periodic drain mechanism as backup (every 5 seconds)
**Write Queue Fixes**
- Fix drainPendingWrites to use atomic take-send-requeue pattern
- Add logging when peripheral is ready for more writes
- Add periodic drain for pending writes as backup
**Fragment Pacing**
- Increase fragment spacing from 4-5ms to 25-30ms to prevent buffer overflow
- Conservative pacing prevents packet loss on congested BLE connections
**Thread Safety**
- Fix race conditions in stopServices() and emergencyDisconnectAll()
- Synchronize access to peripherals/centrals dictionaries during cleanup
- Clear pending message queues in emergencyDisconnectAll()
**Error Recovery**
- Add handlers for all BLE state transitions (poweredOff, unauthorized, etc.)
- Clear Noise session and re-initiate handshake on decryption failure
- Queue ACKs/receipts for delivery after handshake instead of dropping
- Re-queue failed pending messages for retry
**Other Improvements**
- Add route freshness validation in MeshTopologyTracker (60s threshold)
- Add TTL (24h) and size limits (100 per peer) for MessageRouter outbox
- Fix connection timeout to check peripheral.state before canceling
- Add NoiseEncryptionService.clearSession(for:) method
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix race condition and increase test timeouts
- Fix race in sendNoisePayload: use sync barrier instead of async
to ensure payload is queued before initiating handshake
(addresses Codex review feedback)
- Increase BLEServiceTests sleep from 0.5s to 1.0s for CI reliability
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Move updateNeighbors call to after signature verification passes,
preventing spoofed or stale announces from injecting bad routing data.
Also reset isNewPeer/isReconnectedPeer flags on verification failure
to prevent spurious peer connection notifications.
Addresses review feedback from PR #938.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When selectedPrivateChatPeer was migrated to a 64-hex stable Noise key
after session establishment, file transfers would silently fail because:
1. Sender used raw 64-hex key, truncated to first 8 bytes by BinaryProtocol
2. Receiver's myPeerID is SHA256-fingerprint-derived (different value)
3. Recipient check failed, causing silent packet drop
The fix normalizes peerID to short form (SHA256-derived 16-hex) in
sendFilePrivate before constructing recipientData, ensuring the wire
format matches what receivers expect.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The original PR introduced a PendingFileManager that held incoming files
in memory until user acceptance. However, this approach had issues:
1. No UI was implemented for users to accept/decline files
2. Files would expire after 5 minutes, losing legitimate transfers
3. The app only allows specific media types (photos, audio) that users
explicitly choose to send, so manual acceptance adds friction
This commit replaces the pending file system with disk quota management:
- Auto-save files immediately (preserving original UX)
- Enforce 100 MB storage quota for incoming files
- Auto-delete oldest files when quota is exceeded
- Logs cleanup activity for visibility
This directly addresses the DoS vulnerability (disk exhaustion from
file spam) while maintaining good UX for legitimate transfers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update lastAnnounceTime to 'now' when rate-limiting subscription attempts.
This ensures each blocked attempt extends the suppression window, preventing
attackers from waiting out the backoff while continuously spamming attempts.
Previously, the backoff timer was only based on the last successful announce,
allowing attackers to receive an announce as soon as the initial backoff
expired regardless of how many blocked attempts occurred in between.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
P1: Wire didReceivePendingFileTransfer into ChatViewModel
- Add implementation that creates system message for pending files
- Route to appropriate chat (public/private)
- Send local notification for incoming files
- Auto-decline files from blocked users
P2: Keep pending files in queue if save fails
- Only remove file from pendingFiles after successful save
- Allows user to retry accept if initial save failed
Also adds test for save failure scenario.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add proper error classification to distinguish expected states (item not
found) from critical failures (access denied, storage full) and
recoverable errors (device locked, auth failed).
Changes:
- Add KeychainReadResult and KeychainSaveResult enums with error types
- Add getIdentityKeyWithResult/saveIdentityKeyWithResult protocol methods
- Implement retry logic with exponential backoff for transient errors
- Add consistent SecureLogger logging for all keychain operations
- Update NoiseEncryptionService identity loading to handle errors gracefully
- Update MockKeychain and TrackingMockKeychain with error simulation
- Add comprehensive KeychainErrorHandlingTests (18 new tests)
Security: Applications can now properly detect and respond to critical
keychain failures, improving resilience during device lock states and
providing actionable diagnostics for access issues.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds rate-limiting to prevent device enumeration attacks via rapid
subscription/disconnect cycles. Without this fix, an attacker could
enumerate ~120 bitchat devices per minute by connecting, subscribing,
capturing the announce packet, then disconnecting and moving to the next.
Key changes:
- Add per-central rate-limit tracking with exponential backoff
- Minimum 2-second interval between announces per central
- Exponential backoff (2x) up to 30 seconds for rapid reconnects
- After 5 rapid attempts, suppress announces entirely (likely attack)
- Clean up stale rate-limit entries after 60-second window
- Clear rate-limit state during panic mode
Configuration:
- bleSubscriptionRateLimitMinSeconds: 2.0
- bleSubscriptionRateLimitBackoffFactor: 2.0
- bleSubscriptionRateLimitMaxBackoffSeconds: 30.0
- bleSubscriptionRateLimitWindowSeconds: 60.0
- bleSubscriptionRateLimitMaxAttempts: 5
Security audit reference: Cure53 BCH-01-004 (Medium severity)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Incoming files are now held in memory via PendingFileManager instead of
being auto-saved to disk. Users must explicitly accept files before they
are written. This prevents attackers from exhausting device storage.
Key changes:
- Add PendingFileManager with configurable limits (max 10 files, 5MB total)
- Files auto-expire after 5 minutes if not accepted
- LRU eviction when limits are exceeded
- Pending files cleared during panic mode (emergencyDisconnectAll)
- Add didReceivePendingFileTransfer delegate method
- Add acceptPendingFile/declinePendingFile to Transport protocol
Security audit reference: Cure53 BCH-01-002 (Medium severity)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added @MainActor annotation to both LRUDeduplicationCache and
MessageDeduplicationService classes. This provides compile-time
enforcement of thread safety since all callers (ChatViewModel,
NostrRelayManager) are already on MainActor.
Benefits:
- Compile-time enforcement prevents future misuse
- Simpler than internal locking mechanisms
- Consistent with existing patterns in codebase
Also adds:
- @MainActor annotation to existing test suites
- 5 new concurrency tests verifying thread safety behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Synchronized access to readQueue and isSendingReadAcks using the
existing concurrent DispatchQueue with barrier flags:
- sendReadReceipt(): wrap enqueue in barrier async
- processReadQueueIfNeeded(): extract item within barrier context
- scheduleNextReadAck(): wrap callback in barrier async
This fixes race conditions where concurrent calls could corrupt the
read queue or cause check-then-act bugs on isSendingReadAcks.
Also adds thread safety tests:
- concurrentReadReceiptEnqueue: 100 concurrent enqueue operations
- readQueueProcessingUnderLoad: concurrent enqueue during processing
- isPeerReachableThreadSafety: concurrent read access test
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Merge KeychainHelper functionality into KeychainManager to provide
a single, unified API for all keychain operations.
- Remove KeychainHelper.swift and KeychainHelperProtocol
- Add generic save/load/delete methods to KeychainManagerProtocol
- Update NostrIdentityBridge to use KeychainManagerProtocol
- Update FavoritesPersistenceService to use KeychainManagerProtocol
- Update PreviewKeychainManager with new methods
- Update MockKeychain and add MockKeychainHelper typealias for backwards compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Decouple MessageRouter from specific Mesh/Nostr implementations
- Update NostrTransport to implement isPeerReachable using a local cache of favorited peers
- Update ChatViewModel to inject transports as a list
- Mark NostrTransport as @unchecked Sendable to handle internal thread safety
Pass the UserDefaults-backed sentReadReceipts from ChatViewModel to
consolidateMessages() to correctly identify already-read messages
after app restart. This prevents duplicate read receipts and incorrect
unread badges when reopening a chat shortly after reading it.
Addresses PR review feedback.
- Remove duplicate Regexes enum, use MessageFormattingEngine.Patterns
- Delete unused formatMessage() function (views use formatMessageAsText)
- Move message consolidation logic to PrivateChatManager
- Add consolidateMessages() and syncReadReceiptsForSentMessages()
- Wire PrivateChatManager to UnifiedPeerService for peer lookup
Reduces ChatViewModel from 5998 to 5713 lines (-285 lines)
All 303 tests pass
Add isRunningTests check to LocationStateManager to skip CoreLocation
delegate and permission initialization in test/CI environments.
This prevents potential blocking or unexpected behavior when tests
access LocationStateManager.shared.
Add checks for GITHUB_ACTIONS, CI, and XCTestBundlePath environment
variables to reliably detect test/CI environments where notification
APIs should be skipped.
Remove Bundle.main.bundleIdentifier == nil from isRunningTests
detection as it may have unintended side effects in CI environments.
The XCTestCase class check and XCTestConfigurationFilePath environment
variable are sufficient for detecting both XCTest and Swift Testing.
Merge two singleton location managers into a unified LocationStateManager:
- CoreLocation permissions and channel computation
- Channel selection and teleport state tracking
- Bookmark persistence and friendly name resolution
- Reverse geocoding (shared, deduplicated)
Provides backward-compatible typealiases so existing code continues to work:
- typealias LocationChannelManager = LocationStateManager
- typealias GeohashBookmarksStore = LocationStateManager
Reduces 4 location-related files to 3 (keeping LocationNotesManager and
GeohashParticipantTracker separate due to different lifecycles).
Files: 2 deleted, 1 created (~525 lines -> ~420 lines)
Refactor message formatting logic into a dedicated, testable class:
- Create MessageFormattingEngine with Patterns enum (precompiled regexes)
- Create MessageFormattingContext protocol for dependency injection
- Extract extractMentions(), containsCashuToken(), and pattern matchers
- Add 28 comprehensive tests for formatting utilities
- Update ChatViewModel to conform to MessageFormattingContext
This prepares for further message formatting extraction and makes
regex patterns and utility functions testable in isolation.
Replace blocking Thread.sleep in stopServices() with a cooperative
RunLoop-based delay. This allows BLE callbacks to fire during the
shutdown delay, improving reliability of leave message transmission.
The delay is needed to give the leave message time to transmit before
disconnecting peers and stopping the BLE stack.
Refactor participant tracking logic into a dedicated, testable class:
- Create GeohashParticipantTracker with GeohashParticipantContext protocol
- Move GeoPerson struct to new file
- Extract participant recording, refresh, and count logic
- Add 18 comprehensive tests for the tracker
- Update ChatViewModel to use tracker via dependency injection
- Subscribe to tracker's objectWillChange for SwiftUI observation
This reduces coupling in ChatViewModel and makes participant tracking
testable in isolation.
Introduce CommandContextProvider protocol to define what CommandProcessor
needs from its context. This follows the Dependency Inversion Principle:
- Create CommandContextProvider protocol with 15 methods/properties
- Create CommandGeoParticipant struct for geo participant data
- Add getVisibleGeoParticipants() to ChatViewModel
- ChatViewModel now conforms to CommandContextProvider
- CommandProcessor depends on protocol, not concrete ChatViewModel
Benefits:
- Breaks the tight coupling between CommandProcessor and ChatViewModel
- Makes CommandProcessor more testable (can use mock context)
- Clear contract for what CommandProcessor needs
- Backwards-compatible via chatViewModel property alias
Also fixes a concurrency bug in BitchatApp where notification delegate
was accessing main-actor-isolated property from arbitrary thread.
Add proper error logging for Bech32 decode failures instead of silently
returning. This improves debuggability by making it clear when and why
npub decoding fails for favorite notifications, delivery acks, and read
receipts.
The previous empty catch blocks made it difficult to diagnose issues
with malformed or invalid npub addresses. Now all decoding errors are
properly logged with context about which operation failed.
* Harden background BLE restoration and notifications
* Guard BLE restoration paths to iOS
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Simplify validation, compression heuristics, and notification scheduling
* Consolidate notification logic and add InputValidator monitoring
Follow-up improvements to address PR feedback:
1. Consolidate notification functions
- Add interruptionLevel parameter to sendLocalNotification
- Refactor sendNetworkAvailableNotification to use consolidated function
- Removes 12 lines of duplicate code
2. Add monitoring to InputValidator
- Log control character rejections for production monitoring
- Privacy-preserving: logs length + count, not actual content
- Uses .security category for proper log routing
3. Add comprehensive InputValidator tests
- 28 test cases covering validation, control characters, unicode, edge cases
- Ensures behavioral changes are well-tested and documented
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Improve mesh media throughput
* Reserve media slots atomically
* Prioritize small fragment trains
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>