diff --git a/bitchat/Identity/SecureIdentityStateManager.swift b/bitchat/Identity/SecureIdentityStateManager.swift index fecdc793..2fda9a2d 100644 --- a/bitchat/Identity/SecureIdentityStateManager.swift +++ b/bitchat/Identity/SecureIdentityStateManager.swift @@ -129,7 +129,7 @@ final class SecureIdentityStateManager { // Try to load from keychain if let keyData = keychain.getIdentityKey(forKey: encryptionKeyName) { loadedKey = SymmetricKey(data: keyData) - SecureLogger.logKeyOperation("load", keyType: "identity cache encryption key", success: true) + SecureLogger.logKeyOperation(.load, keyType: "identity cache encryption key", success: true) } // Generate new key if needed else { @@ -137,7 +137,7 @@ final class SecureIdentityStateManager { let keyData = loadedKey.withUnsafeBytes { Data($0) } // Save to keychain let saved = keychain.saveIdentityKey(keyData, forKey: encryptionKeyName) - SecureLogger.logKeyOperation("generate", keyType: "identity cache encryption key", success: saved) + SecureLogger.logKeyOperation(.generate, keyType: "identity cache encryption key", success: saved) } self.encryptionKey = loadedKey @@ -160,7 +160,7 @@ final class SecureIdentityStateManager { cache = try JSONDecoder().decode(IdentityCache.self, from: decryptedData) } catch { // Log error but continue with empty cache - SecureLogger.logError(error, context: "Failed to load identity cache", category: SecureLogger.security) + SecureLogger.error(error, context: "Failed to load identity cache", category: .security) } } @@ -191,10 +191,10 @@ final class SecureIdentityStateManager { let sealedBox = try AES.GCM.seal(data, using: encryptionKey) let saved = keychain.saveIdentityKey(sealedBox.combined!, forKey: cacheKey) if saved { - SecureLogger.log("Identity cache saved to keychain", category: SecureLogger.security, level: .debug) + SecureLogger.debug("Identity cache saved to keychain", category: .security) } } catch { - SecureLogger.logError(error, context: "Failed to save identity cache", category: SecureLogger.security) + SecureLogger.error(error, context: "Failed to save identity cache", category: .security) } } @@ -395,7 +395,7 @@ final class SecureIdentityStateManager { } func setBlocked(_ fingerprint: String, isBlocked: Bool) { - SecureLogger.log("User \(isBlocked ? "blocked" : "unblocked"): \(fingerprint)", category: SecureLogger.security, level: .info) + SecureLogger.info("User \(isBlocked ? "blocked" : "unblocked"): \(fingerprint)", category: .security) queue.async(flags: .barrier) { if var identity = self.cache.socialIdentities[fingerprint] { @@ -519,7 +519,7 @@ final class SecureIdentityStateManager { // MARK: - Cleanup func clearAllIdentityData() { - SecureLogger.log("Clearing all identity data", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Clearing all identity data", category: .security) queue.async(flags: .barrier) { self.cache = IdentityCache() @@ -529,7 +529,7 @@ final class SecureIdentityStateManager { // Delete from keychain let deleted = self.keychain.deleteIdentityKey(forKey: self.cacheKey) - SecureLogger.logKeyOperation("delete", keyType: "identity cache", success: deleted) + SecureLogger.logKeyOperation(.delete, keyType: "identity cache", success: deleted) } } @@ -543,7 +543,7 @@ final class SecureIdentityStateManager { // MARK: - Verification func setVerified(fingerprint: String, verified: Bool) { - SecureLogger.log("Fingerprint \(verified ? "verified" : "unverified"): \(fingerprint)", category: SecureLogger.security, level: .info) + SecureLogger.info("Fingerprint \(verified ? "verified" : "unverified"): \(fingerprint)", category: .security) queue.async(flags: .barrier) { if verified { diff --git a/bitchat/Noise/NoiseHandshakeCoordinator.swift b/bitchat/Noise/NoiseHandshakeCoordinator.swift index 0a14c273..7bd817d6 100644 --- a/bitchat/Noise/NoiseHandshakeCoordinator.swift +++ b/bitchat/Noise/NoiseHandshakeCoordinator.swift @@ -68,8 +68,7 @@ final class NoiseHandshakeCoordinator { switch state { case .initiating(_, let lastAttempt): if Date().timeIntervalSince(lastAttempt) > handshakeTimeout { - SecureLogger.log("Forcing new handshake with \(remotePeerID) - previous stuck in initiating", - category: SecureLogger.handshake, level: .warning) + SecureLogger.warning("Forcing new handshake with \(remotePeerID) - previous stuck in initiating", category: .handshake) return true } default: @@ -77,8 +76,7 @@ final class NoiseHandshakeCoordinator { } } - SecureLogger.log("Already in active handshake with \(remotePeerID), state: \(state)", - category: SecureLogger.handshake, level: .debug) + SecureLogger.debug("Already in active handshake with \(remotePeerID), state: \(state)", category: .handshake) return false } @@ -107,8 +105,7 @@ final class NoiseHandshakeCoordinator { handshakeQueue.async(flags: .barrier) { let attempt = self.getCurrentAttempt(for: peerID) + 1 self.handshakeStates[peerID] = .initiating(attempt: attempt, lastAttempt: Date()) - SecureLogger.log("Recording handshake initiation with \(peerID), attempt \(attempt)", - category: SecureLogger.handshake, level: .info) + SecureLogger.info("Recording handshake initiation with \(peerID), attempt \(attempt)", category: .handshake) } } @@ -116,8 +113,7 @@ final class NoiseHandshakeCoordinator { func recordHandshakeResponse(peerID: String) { handshakeQueue.async(flags: .barrier) { self.handshakeStates[peerID] = .responding(since: Date()) - SecureLogger.log("Recording handshake response to \(peerID)", - category: SecureLogger.handshake, level: .info) + SecureLogger.info("Recording handshake response to \(peerID)", category: .handshake) } } @@ -125,8 +121,7 @@ final class NoiseHandshakeCoordinator { func recordHandshakeSuccess(peerID: String) { handshakeQueue.async(flags: .barrier) { self.handshakeStates[peerID] = .established(since: Date()) - SecureLogger.log("Handshake successfully established with \(peerID)", - category: SecureLogger.handshake, level: .info) + SecureLogger.info("Handshake successfully established with \(peerID)", category: .handshake) } } @@ -136,8 +131,7 @@ final class NoiseHandshakeCoordinator { let attempts = self.getCurrentAttempt(for: peerID) let canRetry = attempts < self.maxHandshakeAttempts self.handshakeStates[peerID] = .failed(reason: reason, canRetry: canRetry, lastAttempt: Date()) - SecureLogger.log("Handshake failed with \(peerID): \(reason), canRetry: \(canRetry)", - category: SecureLogger.handshake, level: .warning) + SecureLogger.warning("Handshake failed with \(peerID): \(reason), canRetry: \(canRetry)", category: .handshake) } } @@ -146,8 +140,7 @@ final class NoiseHandshakeCoordinator { return handshakeQueue.sync { // If we're already established, reject new handshakes if case .established = handshakeStates[remotePeerID] { - SecureLogger.log("Rejecting handshake from \(remotePeerID) - already established", - category: SecureLogger.handshake, level: .debug) + SecureLogger.debug("Rejecting handshake from \(remotePeerID) - already established", category: .handshake) return false } @@ -157,8 +150,7 @@ final class NoiseHandshakeCoordinator { if role == .initiator { if case .initiating = handshakeStates[remotePeerID] { // They shouldn't be initiating, but accept it to recover from race condition - SecureLogger.log("Accepting handshake from \(remotePeerID) despite being initiator (race condition recovery)", - category: SecureLogger.handshake, level: .warning) + SecureLogger.warning("Accepting handshake from \(remotePeerID) despite being initiator (race condition recovery)", category: .handshake) return true } } @@ -215,8 +207,7 @@ final class NoiseHandshakeCoordinator { func resetHandshakeState(for peerID: String) { handshakeQueue.async(flags: .barrier) { self.handshakeStates.removeValue(forKey: peerID) - SecureLogger.log("Reset handshake state for \(peerID)", - category: SecureLogger.handshake, level: .debug) + SecureLogger.debug("Reset handshake state for \(peerID)", category: .handshake) } } @@ -256,8 +247,7 @@ final class NoiseHandshakeCoordinator { if isStale { stalePeerIDs.append(peerID) - SecureLogger.log("Found stale handshake state for \(peerID): \(state)", - category: SecureLogger.handshake, level: .warning) + SecureLogger.warning("Found stale handshake state for \(peerID): \(state)", category: .handshake) } } @@ -270,8 +260,7 @@ final class NoiseHandshakeCoordinator { for i in 0.. Self.HIGH_NONCE_WARNING_THRESHOLD { - SecureLogger.log("High nonce value detected: \(currentNonce) - consider rekeying", category: SecureLogger.encryption, level: .warning) + SecureLogger.warning("High nonce value detected: \(currentNonce) - consider rekeying", category: .encryption) } return combinedPayload @@ -307,13 +306,13 @@ final class NoiseCipherState { if useExtractedNonce { // Extract nonce and ciphertext from combined payload guard let (extractedNonce, actualCiphertext) = try extractNonceFromCiphertextPayload(ciphertext) else { - SecureLogger.log("Decrypt failed: Could not extract nonce from payload") + SecureLogger.debug("Decrypt failed: Could not extract nonce from payload") throw NoiseError.invalidCiphertext } // Validate nonce with sliding window replay protection guard isValidNonce(extractedNonce) else { - SecureLogger.log("Replay attack detected: nonce \(extractedNonce) rejected") + SecureLogger.debug("Replay attack detected: nonce \(extractedNonce) rejected") throw NoiseError.replayDetected } @@ -342,7 +341,7 @@ final class NoiseCipherState { // Log high nonce values that might indicate issues if decryptionNonce > Self.HIGH_NONCE_WARNING_THRESHOLD { - SecureLogger.log("High nonce value detected: \(decryptionNonce) - consider rekeying", category: SecureLogger.encryption, level: .warning) + SecureLogger.warning("High nonce value detected: \(decryptionNonce) - consider rekeying", category: .encryption) } do { @@ -355,9 +354,9 @@ final class NoiseCipherState { nonce += 1 return plaintext } catch { - SecureLogger.log("Decrypt failed: \(error) for nonce \(decryptionNonce)") + SecureLogger.debug("Decrypt failed: \(error) for nonce \(decryptionNonce)") // Log authentication failures with nonce info - SecureLogger.log("Decryption failed at nonce \(decryptionNonce)", category: SecureLogger.encryption, level: .error) + SecureLogger.error("Decryption failed at nonce \(decryptionNonce)", category: .encryption) throw error } } @@ -661,7 +660,7 @@ final class NoiseHandshakeState { do { remoteEphemeralPublic = try NoiseHandshakeState.validatePublicKey(ephemeralData) } catch { - SecureLogger.log("Invalid ephemeral public key received", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Invalid ephemeral public key received", category: .security) throw NoiseError.invalidMessage } symmetricState.mixHash(ephemeralData) @@ -678,7 +677,7 @@ final class NoiseHandshakeState { let decrypted = try symmetricState.decryptAndHash(staticData) remoteStaticPublic = try NoiseHandshakeState.validatePublicKey(decrypted) } catch { - SecureLogger.logSecurityEvent(.authenticationFailed(peerID: "Unknown - handshake"), level: .error) + SecureLogger.error(.authenticationFailed(peerID: "Unknown - handshake")) throw NoiseError.authenticationFailure } @@ -877,7 +876,7 @@ extension NoiseHandshakeState { // Check against known bad points if lowOrderPoints.contains(keyData) { - SecureLogger.log("Low-order point detected", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Low-order point detected", category: .security) throw NoiseError.invalidPublicKey } @@ -887,7 +886,7 @@ extension NoiseHandshakeState { return publicKey } catch { // If CryptoKit rejects it, it's invalid - SecureLogger.log("CryptoKit validation failed", category: SecureLogger.security, level: .warning) + SecureLogger.warning("CryptoKit validation failed", category: .security) throw NoiseError.invalidPublicKey } } diff --git a/bitchat/Noise/NoiseSecurityConsiderations.swift b/bitchat/Noise/NoiseSecurityConsiderations.swift index e4e0afc0..67ea3639 100644 --- a/bitchat/Noise/NoiseSecurityConsiderations.swift +++ b/bitchat/Noise/NoiseSecurityConsiderations.swift @@ -153,7 +153,7 @@ final class NoiseRateLimiter { // Check global rate limit first globalHandshakeTimestamps = globalHandshakeTimestamps.filter { $0 > oneMinuteAgo } if globalHandshakeTimestamps.count >= NoiseSecurityConstants.maxGlobalHandshakesPerMinute { - SecureLogger.log("Global handshake rate limit exceeded: \(globalHandshakeTimestamps.count)/\(NoiseSecurityConstants.maxGlobalHandshakesPerMinute) per minute", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Global handshake rate limit exceeded: \(globalHandshakeTimestamps.count)/\(NoiseSecurityConstants.maxGlobalHandshakesPerMinute) per minute", category: .security) return false } @@ -162,7 +162,7 @@ final class NoiseRateLimiter { timestamps = timestamps.filter { $0 > oneMinuteAgo } if timestamps.count >= NoiseSecurityConstants.maxHandshakesPerMinute { - SecureLogger.log("Per-peer handshake rate limit exceeded for \(peerID): \(timestamps.count)/\(NoiseSecurityConstants.maxHandshakesPerMinute) per minute", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Per-peer handshake rate limit exceeded for \(peerID): \(timestamps.count)/\(NoiseSecurityConstants.maxHandshakesPerMinute) per minute", category: .security) return false } @@ -182,7 +182,7 @@ final class NoiseRateLimiter { // Check global rate limit first globalMessageTimestamps = globalMessageTimestamps.filter { $0 > oneSecondAgo } if globalMessageTimestamps.count >= NoiseSecurityConstants.maxGlobalMessagesPerSecond { - SecureLogger.log("Global message rate limit exceeded: \(globalMessageTimestamps.count)/\(NoiseSecurityConstants.maxGlobalMessagesPerSecond) per second", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Global message rate limit exceeded: \(globalMessageTimestamps.count)/\(NoiseSecurityConstants.maxGlobalMessagesPerSecond) per second", category: .security) return false } @@ -191,7 +191,7 @@ final class NoiseRateLimiter { timestamps = timestamps.filter { $0 > oneSecondAgo } if timestamps.count >= NoiseSecurityConstants.maxMessagesPerSecond { - SecureLogger.log("Per-peer message rate limit exceeded for \(peerID): \(timestamps.count)/\(NoiseSecurityConstants.maxMessagesPerSecond) per second", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Per-peer message rate limit exceeded for \(peerID): \(timestamps.count)/\(NoiseSecurityConstants.maxMessagesPerSecond) per second", category: .security) return false } diff --git a/bitchat/Noise/NoiseSession.swift b/bitchat/Noise/NoiseSession.swift index c16b1017..3e654c65 100644 --- a/bitchat/Noise/NoiseSession.swift +++ b/bitchat/Noise/NoiseSession.swift @@ -8,7 +8,6 @@ import Foundation import CryptoKit -import os.log // MARK: - Noise Session State @@ -92,7 +91,7 @@ class NoiseSession { func processHandshakeMessage(_ message: Data) throws -> Data? { return try sessionQueue.sync(flags: .barrier) { - SecureLogger.log("NoiseSession[\(peerID)]: Processing handshake message, current state: \(state), role: \(role)", category: SecureLogger.noise, level: .debug) + SecureLogger.debug("NoiseSession[\(peerID)]: Processing handshake message, current state: \(state), role: \(role)") // Initialize handshake state if needed (for responders) if state == .uninitialized && role == .responder { @@ -103,7 +102,7 @@ class NoiseSession { remoteStaticKey: nil ) state = .handshaking - SecureLogger.log("NoiseSession[\(peerID)]: Initialized handshake state for responder", category: SecureLogger.noise, level: .debug) + SecureLogger.debug("NoiseSession[\(peerID)]: Initialized handshake state for responder") } guard case .handshaking = state, let handshake = handshakeState else { @@ -112,7 +111,7 @@ class NoiseSession { // Process incoming message _ = try handshake.readMessage(message) - SecureLogger.log("NoiseSession[\(peerID)]: Read handshake message, checking if complete", category: SecureLogger.noise, level: .debug) + SecureLogger.debug("NoiseSession[\(peerID)]: Read handshake message, checking if complete") // Check if handshake is complete if handshake.isHandshakeComplete() { @@ -130,15 +129,15 @@ class NoiseSession { state = .established handshakeState = nil // Clear handshake state - SecureLogger.log("NoiseSession[\(peerID)]: Handshake complete (no response needed), transitioning to established", category: SecureLogger.noise, level: .debug) - SecureLogger.logSecurityEvent(.handshakeCompleted(peerID: peerID)) + SecureLogger.debug("NoiseSession[\(peerID)]: Handshake complete (no response needed), transitioning to established") + SecureLogger.info(.handshakeCompleted(peerID: peerID)) return nil } else { // Generate response let response = try handshake.writeMessage() sentHandshakeMessages.append(response) - SecureLogger.log("NoiseSession[\(peerID)]: Generated handshake response of size \(response.count)", category: SecureLogger.noise, level: .debug) + SecureLogger.debug("NoiseSession[\(peerID)]: Generated handshake response of size \(response.count)") // Check if handshake is complete after writing if handshake.isHandshakeComplete() { @@ -156,8 +155,8 @@ class NoiseSession { state = .established handshakeState = nil // Clear handshake state - SecureLogger.log("NoiseSession[\(peerID)]: Handshake complete after writing response, transitioning to established", category: SecureLogger.noise, level: .debug) - SecureLogger.logSecurityEvent(.handshakeCompleted(peerID: peerID)) + SecureLogger.debug("NoiseSession[\(peerID)]: Handshake complete after writing response, transitioning to established") + SecureLogger.info(.handshakeCompleted(peerID: peerID)) } return response @@ -242,7 +241,7 @@ class NoiseSession { handshakeHash = nil if wasEstablished { - SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID)) + SecureLogger.info(.sessionExpired(peerID: peerID)) } } } @@ -287,7 +286,7 @@ final class NoiseSessionManager { managerQueue.sync(flags: .barrier) { if let session = sessions[peerID] { if session.isEstablished() { - SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID)) + SecureLogger.info(.sessionExpired(peerID: peerID)) } // Clear sensitive data before removing session.reset() @@ -331,7 +330,7 @@ final class NoiseSessionManager { } catch { // Clean up failed session _ = sessions.removeValue(forKey: peerID) - SecureLogger.logSecurityEvent(.handshakeFailed(peerID: peerID, error: error.localizedDescription), level: .error) + SecureLogger.error(.handshakeFailed(peerID: peerID, error: error.localizedDescription)) throw error } } @@ -348,8 +347,7 @@ final class NoiseSessionManager { // for a good reason (e.g., decryption failure, restart, etc.) // We should accept the new handshake to re-establish encryption if existing.isEstablished() { - SecureLogger.log("Accepting handshake from \(peerID) despite existing session - peer likely cleared their session", - category: SecureLogger.session, level: .info) + SecureLogger.info("Accepting handshake from \(peerID) despite existing session - peer likely cleared their session", category: .session) _ = sessions.removeValue(forKey: peerID) shouldCreateNew = true } else { @@ -404,7 +402,7 @@ final class NoiseSessionManager { self?.onSessionFailed?(peerID, error) } - SecureLogger.logSecurityEvent(.handshakeFailed(peerID: peerID, error: error.localizedDescription), level: .error) + SecureLogger.error(.handshakeFailed(peerID: peerID, error: error.localizedDescription)) throw error } } diff --git a/bitchat/Nostr/GeoRelayDirectory.swift b/bitchat/Nostr/GeoRelayDirectory.swift index eaf1efe9..fc6c1be6 100644 --- a/bitchat/Nostr/GeoRelayDirectory.swift +++ b/bitchat/Nostr/GeoRelayDirectory.swift @@ -54,7 +54,7 @@ final class GeoRelayDirectory { Task.detached { let ready = await TorManager.shared.awaitReady() if !ready { - SecureLogger.log("GeoRelayDirectory: Tor not ready; skipping remote fetch (fail-closed)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoRelayDirectory: Tor not ready; skipping remote fetch (fail-closed)", category: .session) return } let task = TorURLSession.shared.session.dataTask(with: req) { [weak self] data, _, error in @@ -66,12 +66,12 @@ final class GeoRelayDirectory { self.entries = parsed self.persistCache(text) UserDefaults.standard.set(Date(), forKey: self.lastFetchKey) - SecureLogger.log("GeoRelayDirectory: refreshed \(parsed.count) relays from remote", category: SecureLogger.session, level: .info) + SecureLogger.info("GeoRelayDirectory: refreshed \(parsed.count) relays from remote", category: .session) } return } } - SecureLogger.log("GeoRelayDirectory: remote fetch failed; keeping local entries", category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoRelayDirectory: remote fetch failed; keeping local entries", category: .session) } task.resume() } @@ -82,7 +82,7 @@ final class GeoRelayDirectory { do { try text.data(using: .utf8)?.write(to: url, options: .atomic) } catch { - SecureLogger.log("GeoRelayDirectory: failed to write cache: \(error)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoRelayDirectory: failed to write cache: \(error)", category: .session) } } @@ -113,7 +113,7 @@ final class GeoRelayDirectory { let text = String(data: data, encoding: .utf8) { return Self.parseCSV(text) } - SecureLogger.log("GeoRelayDirectory: no local CSV found; entries empty", category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoRelayDirectory: no local CSV found; entries empty", category: .session) return [] } diff --git a/bitchat/Nostr/NostrProtocol.swift b/bitchat/Nostr/NostrProtocol.swift index c2491194..4919b183 100644 --- a/bitchat/Nostr/NostrProtocol.swift +++ b/bitchat/Nostr/NostrProtocol.swift @@ -78,8 +78,7 @@ struct NostrProtocol { ) // Successfully unwrapped gift wrap } catch { - SecureLogger.log("❌ Failed to unwrap gift wrap: \(error)", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to unwrap gift wrap: \(error)", category: .session) throw error } @@ -92,8 +91,7 @@ struct NostrProtocol { ) // Successfully opened seal } catch { - SecureLogger.log("❌ Failed to open seal: \(error)", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to open seal: \(error)", category: .session) throw error } diff --git a/bitchat/Nostr/NostrRelayManager.swift b/bitchat/Nostr/NostrRelayManager.swift index 99b363f8..04a06deb 100644 --- a/bitchat/Nostr/NostrRelayManager.swift +++ b/bitchat/Nostr/NostrRelayManager.swift @@ -82,10 +82,10 @@ final class NostrRelayManager: ObservableObject { let ready = await TorManager.shared.awaitReady() await MainActor.run { if !ready { - SecureLogger.log("❌ Tor not ready; aborting relay connections (fail-closed)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Tor not ready; aborting relay connections (fail-closed)", category: .session) return } - SecureLogger.log("🌐 Connecting to \(self.relays.count) Nostr relays (via Tor)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🌐 Connecting to \(self.relays.count) Nostr relays (via Tor)", category: .session) for relay in self.relays { self.connectToRelay(relay.url) } @@ -231,12 +231,11 @@ final class NostrRelayManager: ObservableObject { do { let message = try encoder.encode(req) guard let messageString = String(data: message, encoding: .utf8) else { - SecureLogger.log("❌ Failed to encode subscription request", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to encode subscription request", category: .session) return } - // SecureLogger.log("📋 Subscription filter JSON: \(messageString.prefix(200))...", - // category: SecureLogger.session, level: .debug) + // SecureLogger.debug("📋 Subscription filter JSON: \(messageString.prefix(200))...", category: .session) // Target specific relays if provided; else default. Filter permanently failed relays. let baseUrls = relayUrls ?? Self.defaultRelays @@ -251,8 +250,7 @@ final class NostrRelayManager: ObservableObject { map[id] = messageString self.pendingSubscriptions[url] = map } - SecureLogger.log("📋 Queued subscription id=\(id) for \(urls.count) relay(s)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📋 Queued subscription id=\(id) for \(urls.count) relay(s)", category: .session) // Ensure we actually have sockets opening to these relays so queued REQs can flush ensureConnections(to: urls) // If some targets are already connected, flush immediately for them @@ -262,8 +260,7 @@ final class NostrRelayManager: ObservableObject { } } } catch { - SecureLogger.log("❌ Failed to encode subscription request: \(error)", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to encode subscription request: \(error)", category: .session) } } @@ -295,7 +292,7 @@ final class NostrRelayManager: ObservableObject { private func connectToRelay(_ urlString: String) { guard let url = URL(string: urlString) else { - SecureLogger.log("Invalid relay URL: \(urlString)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("Invalid relay URL: \(urlString)", category: .session) return } @@ -321,7 +318,7 @@ final class NostrRelayManager: ObservableObject { let ready = await TorManager.shared.awaitReady() await MainActor.run { if ready { self.connectToRelay(urlString) } - else { SecureLogger.log("❌ Tor not ready; skipping connection to \(urlString)", category: SecureLogger.session, level: .error) } + else { SecureLogger.error("❌ Tor not ready; skipping connection to \(urlString)", category: .session) } } } return @@ -340,14 +337,12 @@ final class NostrRelayManager: ObservableObject { task.sendPing { [weak self] error in DispatchQueue.main.async { if error == nil { - SecureLogger.log("✅ Connected to Nostr relay: \(urlString)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Connected to Nostr relay: \(urlString)", category: .session) self?.updateRelayStatus(urlString, isConnected: true) // Flush any pending subscriptions for this relay self?.flushPendingSubscriptions(for: urlString) } else { - SecureLogger.log("❌ Failed to connect to Nostr relay \(urlString): \(error?.localizedDescription ?? "Unknown error")", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to connect to Nostr relay \(urlString): \(error?.localizedDescription ?? "Unknown error")", category: .session) self?.updateRelayStatus(urlString, isConnected: false, error: error) // Trigger disconnection handler for proper backoff self?.handleDisconnection(relayUrl: urlString, error: error ?? NSError(domain: "NostrRelay", code: -1, userInfo: nil)) @@ -364,8 +359,7 @@ final class NostrRelayManager: ObservableObject { if self.subscriptions[relayUrl]?.contains(id) == true { continue } connection.send(.string(messageString)) { error in if let error = error { - SecureLogger.log("❌ Failed to send pending subscription to \(relayUrl): \(error)", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to send pending subscription to \(relayUrl): \(error)", category: .session) } else { Task { @MainActor in var subs = self.subscriptions[relayUrl] ?? Set() @@ -414,8 +408,7 @@ final class NostrRelayManager: ObservableObject { switch parsed { case .event(let subId, let event): if event.kind != 1059 { - SecureLogger.log("📥 Event kind=\(event.kind) id=\(event.id.prefix(16))… relay=\(relayUrl)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📥 Event kind=\(event.kind) id=\(event.id.prefix(16))… relay=\(relayUrl)", category: .session) } if let index = self.relays.firstIndex(where: { $0.url == relayUrl }) { self.relays[index].messagesReceived += 1 @@ -423,8 +416,7 @@ final class NostrRelayManager: ObservableObject { if let handler = self.messageHandlers[subId] { handler(event) } else { - SecureLogger.log("⚠️ No handler for subscription \(subId)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ No handler for subscription \(subId)", category: .session) } case .eose: // No-op for now @@ -432,12 +424,14 @@ final class NostrRelayManager: ObservableObject { case .ok(let eventId, let success, let reason): if success { _ = Self.pendingGiftWrapIDs.remove(eventId) - SecureLogger.log("✅ Accepted id=\(eventId.prefix(16))… relay=\(relayUrl)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Accepted id=\(eventId.prefix(16))… relay=\(relayUrl)", category: .session) } else { let isGiftWrap = Self.pendingGiftWrapIDs.remove(eventId) != nil - SecureLogger.log("📮 Rejected id=\(eventId.prefix(16))… reason=\(reason)", - category: SecureLogger.session, level: isGiftWrap ? .warning : .error) + if isGiftWrap { + SecureLogger.warning("📮 Rejected id=\(eventId.prefix(16))… reason=\(reason)", category: .session) + } else { + SecureLogger.error("📮 Rejected id=\(eventId.prefix(16))… reason=\(reason)", category: .session) + } } case .notice: break @@ -451,17 +445,14 @@ final class NostrRelayManager: ObservableObject { let data = try encoder.encode(req) let message = String(data: data, encoding: .utf8) ?? "" - SecureLogger.log("📤 Send kind=\(event.kind) id=\(event.id.prefix(16))… relay=\(relayUrl)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Send kind=\(event.kind) id=\(event.id.prefix(16))… relay=\(relayUrl)", category: .session) connection.send(.string(message)) { [weak self] error in DispatchQueue.main.async { if let error = error { - SecureLogger.log("❌ Failed to send event to \(relayUrl): \(error)", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to send event to \(relayUrl): \(error)", category: .session) } else { - // SecureLogger.log("✅ Event sent to relay: \(relayUrl)", - // category: SecureLogger.session, level: .debug) + // SecureLogger.debug("✅ Event sent to relay: \(relayUrl)", category: .session) // Update relay stats if let index = self?.relays.firstIndex(where: { $0.url == relayUrl }) { self?.relays[index].messagesSent += 1 @@ -470,7 +461,7 @@ final class NostrRelayManager: ObservableObject { } } } catch { - SecureLogger.log("Failed to encode event: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to encode event: \(error)", category: .session) } } @@ -509,7 +500,7 @@ final class NostrRelayManager: ObservableObject { errorDescription.contains("dns") || (ns.domain == NSURLErrorDomain && ns.code == NSURLErrorBadServerResponse) { if relays.first(where: { $0.url == relayUrl })?.lastError == nil { - SecureLogger.log("Nostr relay permanent failure for \(relayUrl) - not retrying (code=\(ns.code))", category: SecureLogger.session, level: .warning) + SecureLogger.warning("Nostr relay permanent failure for \(relayUrl) - not retrying (code=\(ns.code))", category: .session) } if let index = relays.firstIndex(where: { $0.url == relayUrl }) { relays[index].lastError = error @@ -527,8 +518,7 @@ final class NostrRelayManager: ObservableObject { // Stop attempting after max attempts if relays[index].reconnectAttempts >= maxReconnectAttempts { - SecureLogger.log("Max reconnection attempts (\(maxReconnectAttempts)) reached for \(relayUrl)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("Max reconnection attempts (\(maxReconnectAttempts)) reached for \(relayUrl)", category: .session) return } diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 3564beef..6d44a94c 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -230,8 +230,7 @@ final class BLEService: NSObject { let newSize = data.count // If single chunk exceeds cap, drop it immediately if newSize > capBytes { - SecureLogger.log("⚠️ Dropping oversized write chunk (\(newSize)B) for peripheral \(uuid)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Dropping oversized write chunk (\(newSize)B) for peripheral \(uuid)", category: .session) } else { // Append and trim from the front to respect cap var total = queue.reduce(0) { $0 + $1.count } @@ -244,8 +243,7 @@ final class BLEService: NSObject { removedBytes += removed.count total -= removed.count } - SecureLogger.log("📉 Trimmed pending write buffer for \(uuid) by \(removedBytes)B to \(total)B", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("📉 Trimmed pending write buffer for \(uuid) by \(removedBytes)B to \(total)B", category: .session) } self.pendingPeripheralWrites[uuid] = queue.isEmpty ? nil : queue } @@ -341,8 +339,7 @@ final class BLEService: NSObject { // Set up Noise session establishment callback // This ensures we send pending messages only when session is truly established noiseService.onPeerAuthenticated = { [weak self] peerID, fingerprint in - SecureLogger.log("🔐 Noise session authenticated with \(peerID), fingerprint: \(fingerprint.prefix(16))...", - category: SecureLogger.noise, level: .debug) + SecureLogger.debug("🔐 Noise session authenticated with \(peerID), fingerprint: \(fingerprint.prefix(16))...") // Send any messages that were queued during handshake self?.messageQueue.async { [weak self] in self?.sendPendingMessagesAfterHandshake(for: peerID) @@ -587,8 +584,7 @@ final class BLEService: NSObject { } func sendFavoriteNotification(to peerID: String, isFavorite: Bool) { - SecureLogger.log("🔔 sendFavoriteNotification called - peerID: \(peerID), isFavorite: \(isFavorite)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔔 sendFavoriteNotification called - peerID: \(peerID), isFavorite: \(isFavorite)", category: .session) // Include Nostr public key in the notification var content = isFavorite ? "[FAVORITED]" : "[UNFAVORITED]" @@ -596,12 +592,10 @@ final class BLEService: NSObject { // Add our Nostr public key if available if let myNostrIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() { content += ":" + myNostrIdentity.npub - SecureLogger.log("📝 Sending favorite notification with Nostr npub: \(myNostrIdentity.npub)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📝 Sending favorite notification with Nostr npub: \(myNostrIdentity.npub)", category: .session) } - SecureLogger.log("📤 Sending favorite notification to \(peerID): \(content)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Sending favorite notification to \(peerID): \(content)", category: .session) sendPrivateMessage(content, to: peerID, messageID: UUID().uuidString) } @@ -611,8 +605,7 @@ final class BLEService: NSObject { payload.append(contentsOf: receipt.originalMessageID.utf8) if noiseService.hasEstablishedSession(with: peerID) { - SecureLogger.log("📤 Sending READ receipt for message \(receipt.originalMessageID) to \(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Sending READ receipt for message \(receipt.originalMessageID) to \(peerID)", category: .session) do { let encrypted = try noiseService.encrypt(payload, for: peerID) let packet = BitchatPacket( @@ -630,7 +623,7 @@ final class BLEService: NSObject { messageQueue.async { [weak self] in self?.broadcastPacket(packet) } } } catch { - SecureLogger.log("Failed to send read receipt: \(error)", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to send read receipt: \(error)") } } else { // Queue for after handshake and initiate if needed @@ -639,8 +632,7 @@ final class BLEService: NSObject { self.pendingNoisePayloadsAfterHandshake[peerID, default: []].append(payload) } if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID) } - SecureLogger.log("🕒 Queued READ receipt for \(peerID) until handshake completes", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🕒 Queued READ receipt for \(peerID) until handshake completes", category: .session) } } @@ -682,7 +674,7 @@ final class BLEService: NSObject { messageQueue.async { [weak self] in self?.broadcastPacket(packet) } } } catch { - SecureLogger.log("Failed to send verification payload: \(error)", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to send verification payload: \(error)") } } @@ -748,7 +740,7 @@ final class BLEService: NSObject { guard let self = self else { return } guard content.count <= self.maxMessageLength else { - SecureLogger.log("Message too long: \(content.count) chars", category: SecureLogger.session, level: .error) + SecureLogger.error("Message too long: \(content.count) chars", category: .session) return } @@ -771,7 +763,7 @@ final class BLEService: NSObject { ttl: self.messageTTL ) guard let signedPacket = self.noiseService.signPacket(basePacket) else { - SecureLogger.log("❌ Failed to sign public message", category: SecureLogger.security, level: .error) + SecureLogger.error("❌ Failed to sign public message", category: .security) return } // Pre-mark our own broadcast as processed to avoid handling relayed self copy @@ -789,7 +781,7 @@ final class BLEService: NSObject { // MARK: - Private Message Handling private func sendPrivateMessage(_ content: String, to recipientID: String, messageID: String) { - SecureLogger.log("📨 Sending PM to \(recipientID): \(content.prefix(30))...", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📨 Sending PM to \(recipientID): \(content.prefix(30))...", category: .session) // Check if we have an established Noise session if noiseService.hasEstablishedSession(with: recipientID) { @@ -798,7 +790,7 @@ final class BLEService: NSObject { // Create TLV-encoded private message let privateMessage = PrivateMessagePacket(messageID: messageID, content: content) guard let tlvData = privateMessage.encode() else { - SecureLogger.log("Failed to encode private message with TLV", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to encode private message with TLV") return } @@ -847,11 +839,11 @@ final class BLEService: NSObject { self?.delegate?.didUpdateMessageDeliveryStatus(messageID, status: .sent) } } catch { - SecureLogger.log("Failed to encrypt message: \(error)", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to encrypt message: \(error)") } } else { // Queue message for sending after handshake completes - SecureLogger.log("🤝 No session with \(recipientID), initiating handshake and queueing message", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🤝 No session with \(recipientID), initiating handshake and queueing message", category: .session) // Queue the message (especially important for favorite notifications) collectionsQueue.sync(flags: .barrier) { @@ -896,7 +888,7 @@ final class BLEService: NSObject { } } } catch { - SecureLogger.log("Failed to initiate handshake: \(error)", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to initiate handshake: \(error)") } } @@ -910,8 +902,7 @@ final class BLEService: NSObject { guard let messages = pendingMessages, !messages.isEmpty else { return } - SecureLogger.log("📤 Sending \(messages.count) pending messages after handshake to \(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Sending \(messages.count) pending messages after handshake to \(peerID)", category: .session) // Send each pending message directly (we know session is established) for (content, messageID) in messages { @@ -919,7 +910,7 @@ final class BLEService: NSObject { // Use the same TLV format as normal sends to keep receiver decoding consistent let privateMessage = PrivateMessagePacket(messageID: messageID, content: content) guard let tlvData = privateMessage.encode() else { - SecureLogger.log("Failed to encode pending private message TLV", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to encode pending private message TLV") continue } @@ -946,11 +937,9 @@ final class BLEService: NSObject { self?.delegate?.didUpdateMessageDeliveryStatus(messageID, status: .sent) } - SecureLogger.log("✅ Sent pending message \(messageID) to \(peerID) after handshake", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Sent pending message \(messageID) to \(peerID) after handshake", category: .session) } catch { - SecureLogger.log("Failed to send pending message after handshake: \(error)", - category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to send pending message after handshake: \(error)") // Notify delegate of failure notifyUI { [weak self] in @@ -966,7 +955,7 @@ final class BLEService: NSObject { // Encode once using a small per-type padding policy, then delegate by type let padForBLE = padPolicy(for: packet.type) guard let data = packet.toBinaryData(padding: padForBLE) else { - SecureLogger.log("❌ Failed to convert packet to binary data", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to convert packet to binary data", category: .session) return } if packet.type == MessageType.noiseEncrypted.rawValue { @@ -1037,7 +1026,7 @@ final class BLEService: NSObject { guard let self = self else { return } if self.pendingNotifications.count < TransportConfig.blePendingNotificationsCapCount { self.pendingNotifications.append((data: data, centrals: [central])) - SecureLogger.log("📋 Queued encrypted packet for retry (notification queue full)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📋 Queued encrypted packet for retry (notification queue full)", category: .session) } } } @@ -1150,7 +1139,7 @@ final class BLEService: NSObject { if byMsg[msgID] == nil { byMsg[msgID] = (packet: packet, enqueuedAt: Date()) self.pendingDirectedRelays[recipientPeerID] = byMsg - SecureLogger.log("🧳 Spooling directed packet for \(recipientPeerID) mid=\(msgID.prefix(8))…", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🧳 Spooling directed packet for \(recipientPeerID) mid=\(msgID.prefix(8))…", category: .session) } } } @@ -1322,7 +1311,7 @@ final class BLEService: NSObject { if let originalPacket = BinaryProtocol.decode(reassembled) { handleReceivedPacket(originalPacket, from: peerID) } else { - SecureLogger.log("❌ Failed to decode reassembled packet (type=\(originalType), total=\(total))", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to decode reassembled packet (type=\(originalType), total=\(total))", category: .session) } // Cleanup @@ -1342,8 +1331,7 @@ final class BLEService: NSObject { // Only log non-announce packets to reduce noise if packet.type != MessageType.announce.rawValue { // Log packet details for debugging - SecureLogger.log("📦 Handling packet type \(packet.type) from \(senderID), messageID: \(messageID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📦 Handling packet type \(packet.type) from \(senderID), messageID: \(messageID)", category: .session) } // Efficient deduplication @@ -1352,8 +1340,7 @@ final class BLEService: NSObject { // Announce packets (type 1) are sent every 10 seconds for peer discovery // It's normal to see these as duplicates - don't log them to reduce noise if packet.type != MessageType.announce.rawValue { - SecureLogger.log("⚠️ Duplicate packet ignored: \(messageID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("⚠️ Duplicate packet ignored: \(messageID)", category: .session) } // In sparse graphs (<=2 neighbors), keep the pending relay to ensure bridging. // In denser graphs, cancel the pending relay to reduce redundant floods. @@ -1406,7 +1393,7 @@ final class BLEService: NSObject { handleLeave(packet, from: senderID) default: - SecureLogger.log("⚠️ Unknown message type: \(packet.type)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Unknown message type: \(packet.type)", category: .session) break } @@ -1446,7 +1433,7 @@ final class BLEService: NSObject { private func handleAnnounce(_ packet: BitchatPacket, from peerID: String) { guard let announcement = AnnouncementPacket.decode(from: packet.payload) else { - SecureLogger.log("❌ Failed to decode announce packet from \(peerID)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to decode announce packet from \(peerID)", category: .session) return } @@ -1454,7 +1441,7 @@ final class BLEService: NSObject { // This helps detect relayed or spoofed announces. Only warn in release; assert in debug. let derivedFromKey = PeerIDUtils.derivePeerID(fromPublicKey: announcement.noisePublicKey) if derivedFromKey != peerID { - SecureLogger.log("⚠️ Announce sender mismatch: derived \(derivedFromKey.prefix(8))… vs packet \(peerID.prefix(8))…", category: SecureLogger.security, level: .warning) + SecureLogger.warning("⚠️ Announce sender mismatch: derived \(derivedFromKey.prefix(8))… vs packet \(peerID.prefix(8))…", category: .security) } @@ -1471,11 +1458,11 @@ final class BLEService: NSObject { if packet.signature != nil { verifiedAnnounce = noiseService.verifyPacketSignature(packet, publicKey: announcement.signingPublicKey) if !verifiedAnnounce { - SecureLogger.log("⚠️ Signature verification for announce failed \(peerID.prefix(8))", category: SecureLogger.security, level: .warning) + SecureLogger.warning("⚠️ Signature verification for announce failed \(peerID.prefix(8))", category: .security) } } if let existingKey = existingPeerForVerify?.noisePublicKey, existingKey != announcement.noisePublicKey { - SecureLogger.log("⚠️ Announce key mismatch for \(peerID.prefix(8))… — keeping unverified", category: SecureLogger.security, level: .warning) + SecureLogger.warning("⚠️ Announce key mismatch for \(peerID.prefix(8))… — keeping unverified", category: .security) verifiedAnnounce = false } @@ -1508,7 +1495,7 @@ final class BLEService: NSObject { // Require verified announce; ignore otherwise (no backward compatibility) if !verified { - SecureLogger.log("❌ Ignoring unverified announce from \(peerID.prefix(8))…", category: SecureLogger.security, level: .warning) + SecureLogger.warning("❌ Ignoring unverified announce from \(peerID.prefix(8))…", category: .security) return } @@ -1541,17 +1528,17 @@ final class BLEService: NSObject { if isDirectAnnounce || hasPeripheralConnection || hasCentralSubscription { let now = Date() if existingPeer == nil { - SecureLogger.log("🆕 New peer: \(announcement.nickname)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🆕 New peer: \(announcement.nickname)", category: .session) } else if wasDisconnected { // Debounce 'reconnected' logs within short window if let last = lastReconnectLogAt[peerID], now.timeIntervalSince(last) < TransportConfig.bleReconnectLogDebounceSeconds { // Skip duplicate log } else { - SecureLogger.log("🔄 Peer \(announcement.nickname) reconnected", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔄 Peer \(announcement.nickname) reconnected", category: .session) lastReconnectLogAt[peerID] = now } } else if existingPeer?.nickname != announcement.nickname { - SecureLogger.log("🔄 Peer \(peerID) changed nickname: \(existingPeer?.nickname ?? "Unknown") -> \(announcement.nickname)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔄 Peer \(peerID) changed nickname: \(existingPeer?.nickname ?? "Unknown") -> \(announcement.nickname)", category: .session) } } } @@ -1661,12 +1648,12 @@ final class BLEService: NSObject { } guard accepted else { - SecureLogger.log("🚫 Dropping public message from unverified or unknown peer \(peerID.prefix(8))…", category: SecureLogger.security, level: .warning) + SecureLogger.warning("🚫 Dropping public message from unverified or unknown peer \(peerID.prefix(8))…", category: .security) return } guard let content = String(data: packet.payload, encoding: .utf8) else { - SecureLogger.log("❌ Failed to decode message payload as UTF-8", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to decode message payload as UTF-8", category: .session) return } // Determine if we have a direct link to the sender @@ -1678,7 +1665,7 @@ final class BLEService: NSObject { } let pathTag = hasDirectLink ? "direct" : "mesh" - SecureLogger.log("💬 [\(senderNickname)] TTL:\(packet.ttl) (\(pathTag)): \(String(content.prefix(50)))\(content.count > 50 ? "..." : "")", category: SecureLogger.session, level: .debug) + SecureLogger.debug("💬 [\(senderNickname)] TTL:\(packet.ttl) (\(pathTag)): \(String(content.prefix(50)))\(content.count > 50 ? "..." : "")", category: .session) let ts = Date(timeIntervalSince1970: Double(packet.timestamp) / 1000) notifyUI { [weak self] in @@ -1710,7 +1697,7 @@ final class BLEService: NSObject { // Session establishment will trigger onPeerAuthenticated callback // which will send any pending messages at the right time } catch { - SecureLogger.log("Failed to process handshake: \(error)", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to process handshake: \(error)") // Try initiating a new handshake if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID) @@ -1720,17 +1707,16 @@ final class BLEService: NSObject { } private func handleNoiseEncrypted(_ packet: BitchatPacket, from peerID: String) { - SecureLogger.log("🔐 handleNoiseEncrypted called for packet from \(peerID)", - category: SecureLogger.noise, level: .debug) + SecureLogger.debug("🔐 handleNoiseEncrypted called for packet from \(peerID)") guard let recipientID = packet.recipientID else { - SecureLogger.log("⚠️ Encrypted message has no recipient ID", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Encrypted message has no recipient ID", category: .session) return } let recipientHex = recipientID.hexEncodedString() if recipientHex != myPeerID { - SecureLogger.log("🔐 Encrypted message not for me (for \(recipientHex), I am \(myPeerID))", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔐 Encrypted message not for me (for \(recipientHex), I am \(myPeerID))", category: .session) return } @@ -1772,19 +1758,17 @@ final class BLEService: NSObject { self?.delegate?.didReceiveNoisePayload(from: peerID, type: .verifyResponse, payload: Data(payloadData), timestamp: ts) } default: - SecureLogger.log("⚠️ Unknown noise payload type: \(payloadType)", category: SecureLogger.noise, level: .warning) + SecureLogger.warning("⚠️ Unknown noise payload type: \(payloadType)") } } catch NoiseEncryptionError.sessionNotEstablished { // We received an encrypted message before establishing a session with this peer. // Trigger a handshake so future messages can be decrypted. - SecureLogger.log("🔑 Encrypted message from \(peerID) without session; initiating handshake", - category: SecureLogger.noise, level: .debug) + SecureLogger.debug("🔑 Encrypted message from \(peerID) without session; initiating handshake") if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID) } } catch { - SecureLogger.log("❌ Failed to decrypt message from \(peerID): \(error)", - category: SecureLogger.noise, level: .error) + SecureLogger.error("❌ Failed to decrypt message from \(peerID): \(error)") } } @@ -1808,7 +1792,7 @@ final class BLEService: NSObject { // MARK: - Helper Functions private func sendLeave() { - SecureLogger.log("👋 Sending leave announcement", category: SecureLogger.session, level: .debug) + SecureLogger.debug("👋 Sending leave announcement", category: .session) let packet = BitchatPacket( type: MessageType.leave.rawValue, ttl: messageTTL, @@ -1845,7 +1829,7 @@ final class BLEService: NSObject { ) guard let payload = announcement.encode() else { - SecureLogger.log("❌ Failed to encode announce packet", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to encode announce packet", category: .session) return } @@ -1862,7 +1846,7 @@ final class BLEService: NSObject { // Sign the packet using the noise private key guard let signedPacket = noiseService.signPacket(packet) else { - SecureLogger.log("❌ Failed to sign announce packet", category: SecureLogger.security, level: .error) + SecureLogger.error("❌ Failed to sign announce packet", category: .security) return } @@ -1895,7 +1879,7 @@ final class BLEService: NSObject { ) broadcastPacket(packet) } catch { - SecureLogger.log("Failed to send delivery ACK: \(error)", category: SecureLogger.noise, level: .error) + SecureLogger.error("Failed to send delivery ACK: \(error)") } } else { // Queue for after handshake and initiate if needed @@ -1904,8 +1888,7 @@ final class BLEService: NSObject { self.pendingNoisePayloadsAfterHandshake[peerID, default: []].append(payload) } if !noiseService.hasSession(with: peerID) { initiateNoiseHandshake(with: peerID) } - SecureLogger.log("🕒 Queued DELIVERED ack for \(peerID) until handshake completes", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🕒 Queued DELIVERED ack for \(peerID) until handshake completes", category: .session) } } @@ -1916,8 +1899,7 @@ final class BLEService: NSObject { return list } guard !payloads.isEmpty else { return } - SecureLogger.log("📤 Sending \(payloads.count) pending noise payloads to \(peerID) after handshake", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Sending \(payloads.count) pending noise payloads to \(peerID) after handshake", category: .session) for payload in payloads { do { let encrypted = try noiseService.encrypt(payload, for: peerID) @@ -1932,8 +1914,7 @@ final class BLEService: NSObject { ) broadcastPacket(packet) } catch { - SecureLogger.log("❌ Failed to send pending noise payload to \(peerID): \(error)", - category: SecureLogger.noise, level: .error) + SecureLogger.error("❌ Failed to send pending noise payload to \(peerID): \(error)") } } } @@ -2103,8 +2084,7 @@ final class BLEService: NSObject { // Cleanup: remove peers that are not connected and past reachability retention if !peer.isConnected { if age > retention { - SecureLogger.log("🗑️ Removing stale peer after reachability window: \(peerID) (\(peer.nickname))", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🗑️ Removing stale peer after reachability window: \(peerID) (\(peer.nickname))", category: .session) peers.removeValue(forKey: peerID) removedOfflineCount += 1 } @@ -2395,8 +2375,7 @@ extension BLEService: CBCentralManagerDelegate { peripheral.delegate = self // Connect to the peripheral with options for faster connection - SecureLogger.log("📱 Connect: \(advertisedName) [RSSI:\(rssiValue)]", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📱 Connect: \(advertisedName) [RSSI:\(rssiValue)]", category: .session) // Use connection options for faster reconnection let options: [String: Any] = [ @@ -2415,8 +2394,7 @@ extension BLEService: CBCentralManagerDelegate { state.isConnecting && !state.isConnected else { return } // Connection timed out - cancel it - SecureLogger.log("⏱️ Timeout: \(advertisedName)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("⏱️ Timeout: \(advertisedName)", category: .session) central.cancelPeripheralConnection(peripheral) self.peripherals[peripheralID] = nil self.recentConnectTimeouts[peripheralID] = Date() @@ -2449,7 +2427,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph failureCounts[peripheralID] = 0 recentConnectTimeouts.removeValue(forKey: peripheralID) - SecureLogger.log("✅ Connected: \(peripheral.name ?? "Unknown") [\(peripheralID)]", category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Connected: \(peripheral.name ?? "Unknown") [\(peripheralID)]", category: .session) // Discover services peripheral.discoverServices([BLEService.serviceUUID]) @@ -2461,8 +2439,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph // Find the peer ID if we have it let peerID = peripherals[peripheralID]?.peerID - SecureLogger.log("📱 Disconnect: \(peerID ?? peripheralID)\(error != nil ? " (\(error!.localizedDescription))" : "")", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📱 Disconnect: \(peerID ?? peripheralID)\(error != nil ? " (\(error!.localizedDescription))" : "")", category: .session) // If disconnect carried an error (often timeout), apply short backoff to avoid thrash if error != nil { @@ -2517,7 +2494,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph // Clean up the references peripherals.removeValue(forKey: peripheralID) - SecureLogger.log("❌ Failed to connect to peripheral: \(peripheral.name ?? "Unknown") [\(peripheralID)] - Error: \(error?.localizedDescription ?? "Unknown")", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to connect to peripheral: \(peripheral.name ?? "Unknown") [\(peripheralID)] - Error: \(error?.localizedDescription ?? "Unknown")", category: .session) failureCounts[peripheralID, default: 0] += 1 // Try next candidate bleQueue.async { [weak self] in self?.tryConnectFromQueue() } @@ -2590,7 +2567,7 @@ extension BLEService { ] central.connect(peripheral, options: options) lastGlobalConnectAttempt = Date() - SecureLogger.log("⏩ Queue connect: \(candidate.name) [RSSI:\(candidate.rssi)]", category: SecureLogger.session, level: .debug) + SecureLogger.debug("⏩ Queue connect: \(candidate.name) [RSSI:\(candidate.rssi)]", category: .session) } } @@ -2635,7 +2612,7 @@ extension BLEService { extension BLEService: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let error = error { - SecureLogger.log("❌ Error discovering services for \(peripheral.name ?? "Unknown"): \(error.localizedDescription)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Error discovering services for \(peripheral.name ?? "Unknown"): \(error.localizedDescription)", category: .session) // Retry service discovery after a delay DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { guard peripheral.state == .connected else { return } @@ -2645,7 +2622,7 @@ extension BLEService: CBPeripheralDelegate { } guard let services = peripheral.services else { - SecureLogger.log("⚠️ No services discovered for \(peripheral.name ?? "Unknown")", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ No services discovered for \(peripheral.name ?? "Unknown")", category: .session) return } @@ -2661,12 +2638,12 @@ extension BLEService: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let error = error { - SecureLogger.log("❌ Error discovering characteristics for \(peripheral.name ?? "Unknown"): \(error.localizedDescription)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Error discovering characteristics for \(peripheral.name ?? "Unknown"): \(error.localizedDescription)", category: .session) return } guard let characteristic = service.characteristics?.first(where: { $0.uuid == BLEService.characteristicUUID }) else { - SecureLogger.log("⚠️ No matching characteristic found for \(peripheral.name ?? "Unknown")", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ No matching characteristic found for \(peripheral.name ?? "Unknown")", category: .session) return } @@ -2683,7 +2660,7 @@ extension BLEService: CBPeripheralDelegate { // Verify characteristic supports reliable writes if !characteristic.properties.contains(.write) { - SecureLogger.log("⚠️ Characteristic doesn't support reliable writes (withResponse)!", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Characteristic doesn't support reliable writes (withResponse)!", category: .session) } // Store characteristic in our consolidated structure @@ -2696,7 +2673,7 @@ extension BLEService: CBPeripheralDelegate { // Subscribe for notifications if characteristic.properties.contains(.notify) { peripheral.setNotifyValue(true, for: characteristic) - SecureLogger.log("🔔 Subscribed to notifications from \(peripheral.name ?? "Unknown")", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔔 Subscribed to notifications from \(peripheral.name ?? "Unknown")", category: .session) // Send announce after subscription is confirmed (force send for new connection) messageQueue.asyncAfter(deadline: .now() + TransportConfig.blePostSubscribeAnnounceDelaySeconds) { [weak self] in @@ -2707,18 +2684,18 @@ extension BLEService: CBPeripheralDelegate { self?.rebroadcastRecentAnnounces() } } else { - SecureLogger.log("⚠️ Characteristic does not support notifications", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Characteristic does not support notifications", category: .session) } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { - SecureLogger.log("❌ Error receiving notification: \(error.localizedDescription)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Error receiving notification: \(error.localizedDescription)", category: .session) return } guard let data = characteristic.value else { - SecureLogger.log("⚠️ No data in notification", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ No data in notification", category: .session) return } @@ -2728,8 +2705,7 @@ extension BLEService: CBPeripheralDelegate { guard let packet = BinaryProtocol.decode(data) else { // Avoid dumping entire payload; log size and short prefix for diagnostics let prefix = data.prefix(16).map { String(format: "%02x", $0) }.joined(separator: " ") - SecureLogger.log("❌ Failed to decode notification packet (len=\(data.count), prefix=\(prefix))", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to decode notification packet (len=\(data.count), prefix=\(prefix))", category: .session) return } @@ -2737,7 +2713,7 @@ extension BLEService: CBPeripheralDelegate { let senderID = packet.senderID.hexEncodedString() // Only log non-announce packets if packet.type != MessageType.announce.rawValue { - SecureLogger.log("📦 Decoded notification packet type: \(packet.type) from sender: \(senderID)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📦 Decoded notification packet type: \(packet.type) from sender: \(senderID)", category: .session) } let peripheralUUID = peripheral.identifier.uuidString @@ -2775,10 +2751,10 @@ extension BLEService: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { - SecureLogger.log("❌ Write failed to \(peripheral.name ?? peripheral.identifier.uuidString): \(error.localizedDescription)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Write failed to \(peripheral.name ?? peripheral.identifier.uuidString): \(error.localizedDescription)", category: .session) // Don't retry - just log the error } else { - SecureLogger.log("✅ Write confirmed to \(peripheral.name ?? peripheral.identifier.uuidString)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Write confirmed to \(peripheral.name ?? peripheral.identifier.uuidString)", category: .session) } } @@ -2788,14 +2764,14 @@ extension BLEService: CBPeripheralDelegate { } func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { - SecureLogger.log("⚠️ Services modified for \(peripheral.name ?? peripheral.identifier.uuidString)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Services modified for \(peripheral.name ?? peripheral.identifier.uuidString)", category: .session) // Check if our service was invalidated (peer app quit) let hasOurService = peripheral.services?.contains { $0.uuid == BLEService.serviceUUID } ?? false if !hasOurService { // Service is gone - disconnect - SecureLogger.log("❌ BitChat service removed - disconnecting from \(peripheral.name ?? peripheral.identifier.uuidString)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("❌ BitChat service removed - disconnecting from \(peripheral.name ?? peripheral.identifier.uuidString)", category: .session) centralManager?.cancelPeripheralConnection(peripheral) } else { // Try to rediscover @@ -2805,9 +2781,9 @@ extension BLEService: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let error = error { - SecureLogger.log("❌ Error updating notification state: \(error.localizedDescription)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Error updating notification state: \(error.localizedDescription)", category: .session) } else { - SecureLogger.log("🔔 Notification state updated for \(peripheral.name ?? peripheral.identifier.uuidString): \(characteristic.isNotifying ? "ON" : "OFF")", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔔 Notification state updated for \(peripheral.name ?? peripheral.identifier.uuidString): \(characteristic.isNotifying ? "ON" : "OFF")", category: .session) // If notifications are now on, send an announce to ensure this peer knows about us if characteristic.isNotifying { @@ -2822,7 +2798,7 @@ extension BLEService: CBPeripheralDelegate { extension BLEService: CBPeripheralManagerDelegate { func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { - SecureLogger.log("📡 Peripheral manager state: \(peripheral.state.rawValue)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📡 Peripheral manager state: \(peripheral.state.rawValue)", category: .session) if peripheral.state == .poweredOn { // Remove all services first to ensure clean state @@ -2841,28 +2817,28 @@ extension BLEService: CBPeripheralManagerDelegate { service.characteristics = [characteristic!] // Add service (advertising will start in didAdd delegate) - SecureLogger.log("🔧 Adding BLE service...", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔧 Adding BLE service...", category: .session) peripheral.add(service) } } func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) { if let error = error { - SecureLogger.log("❌ Failed to add service: \(error.localizedDescription)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to add service: \(error.localizedDescription)", category: .session) return } - SecureLogger.log("✅ Service added successfully, starting advertising", category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Service added successfully, starting advertising", category: .session) // Start advertising after service is confirmed added let adData = buildAdvertisementData() peripheral.startAdvertising(adData) - SecureLogger.log("📡 Started advertising (LocalName: \((adData[CBAdvertisementDataLocalNameKey] as? String) != nil ? "on" : "off"), ID: \(myPeerID.prefix(8))…)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📡 Started advertising (LocalName: \((adData[CBAdvertisementDataLocalNameKey] as? String) != nil ? "on" : "off"), ID: \(myPeerID.prefix(8))…)", category: .session) } func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { - SecureLogger.log("📥 Central subscribed: \(central.identifier.uuidString)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📥 Central subscribed: \(central.identifier.uuidString)", category: .session) subscribedCentrals.append(central) // Send announce to the newly subscribed central after a small delay to avoid overwhelming messageQueue.asyncAfter(deadline: .now() + TransportConfig.blePostAnnounceDelaySeconds) { [weak self] in @@ -2875,12 +2851,12 @@ extension BLEService: CBPeripheralManagerDelegate { } func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { - SecureLogger.log("📤 Central unsubscribed: \(central.identifier.uuidString)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Central unsubscribed: \(central.identifier.uuidString)", category: .session) subscribedCentrals.removeAll { $0.identifier == central.identifier } // Ensure we're still advertising for other devices to find us if peripheral.isAdvertising == false { - SecureLogger.log("📡 Restarting advertising after central unsubscribed", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📡 Restarting advertising after central unsubscribed", category: .session) peripheral.startAdvertising(buildAdvertisementData()) } @@ -2914,7 +2890,7 @@ extension BLEService: CBPeripheralManagerDelegate { } func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) { - SecureLogger.log("📤 Peripheral manager ready to send more notifications", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Peripheral manager ready to send more notifications", category: .session) // Retry pending notifications now that queue has space collectionsQueue.async(flags: .barrier) { [weak self] in @@ -2933,12 +2909,10 @@ extension BLEService: CBPeripheralManagerDelegate { if !success { // Still full, re-queue self.pendingNotifications.append((data: data, centrals: centrals)) - SecureLogger.log("⚠️ Notification queue still full, re-queuing", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("⚠️ Notification queue still full, re-queuing", category: .session) break // Stop trying, wait for next ready callback } else { - SecureLogger.log("✅ Sent pending notification from retry queue", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Sent pending notification from retry queue", category: .session) } } else { // Broadcast to all @@ -2952,8 +2926,7 @@ extension BLEService: CBPeripheralManagerDelegate { } if !self.pendingNotifications.isEmpty { - SecureLogger.log("📋 Still have \(self.pendingNotifications.count) pending notifications", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📋 Still have \(self.pendingNotifications.count) pending notifications", category: .session) } } } @@ -2961,7 +2934,7 @@ extension BLEService: CBPeripheralManagerDelegate { func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { // Suppress logs for single write requests to reduce noise if requests.count > 1 { - SecureLogger.log("📥 Received \(requests.count) write requests from central", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📥 Received \(requests.count) write requests from central", category: .session) } // IMPORTANT: Respond immediately to prevent timeouts! @@ -3000,7 +2973,7 @@ extension BLEService: CBPeripheralManagerDelegate { if combined.count >= 2 { let peekType = combined[1] if peekType != MessageType.announce.rawValue { - SecureLogger.log("📥 Accumulated write from central \(centralUUID): size=\(combined.count) (+\(appendedBytes)) bytes (type=\(peekType)), offsets=\(offsets)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📥 Accumulated write from central \(centralUUID): size=\(combined.count) (+\(appendedBytes)) bytes (type=\(peekType)), offsets=\(offsets)", category: .session) } } @@ -3010,7 +2983,7 @@ extension BLEService: CBPeripheralManagerDelegate { pendingWriteBuffers.removeValue(forKey: centralUUID) let senderID = packet.senderID.hexEncodedString() if packet.type != MessageType.announce.rawValue { - SecureLogger.log("📦 Decoded (combined) packet type: \(packet.type) from sender: \(senderID)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📦 Decoded (combined) packet type: \(packet.type) from sender: \(senderID)", category: .session) } if !subscribedCentrals.contains(sorted[0].central) { subscribedCentrals.append(sorted[0].central) @@ -3035,12 +3008,12 @@ extension BLEService: CBPeripheralManagerDelegate { // If buffer grows suspiciously large, reset to avoid memory leak if combined.count > TransportConfig.blePendingWriteBufferCapBytes { // cap for safety pendingWriteBuffers.removeValue(forKey: centralUUID) - SecureLogger.log("⚠️ Dropping oversized pending write buffer (\(combined.count) bytes) for central \(centralUUID)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Dropping oversized pending write buffer (\(combined.count) bytes) for central \(centralUUID)", category: .session) } // If this was a single short write and still failed, log the raw chunk for debugging if !hasMultiple, let only = sorted.first, let raw = only.value { let prefix = raw.prefix(16).map { String(format: "%02x", $0) }.joined(separator: " ") - SecureLogger.log("❌ Failed to decode packet from central (len=\(raw.count), prefix=\(prefix))", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to decode packet from central (len=\(raw.count), prefix=\(prefix))", category: .session) } } } diff --git a/bitchat/Services/FavoritesPersistenceService.swift b/bitchat/Services/FavoritesPersistenceService.swift index 3537c35e..82c3ffb8 100644 --- a/bitchat/Services/FavoritesPersistenceService.swift +++ b/bitchat/Services/FavoritesPersistenceService.swift @@ -47,8 +47,7 @@ final class FavoritesPersistenceService: ObservableObject { peerNostrPublicKey: String? = nil, peerNickname: String ) { - SecureLogger.log("⭐️ Adding favorite: \(peerNickname) (\(peerNoisePublicKey.hexEncodedString()))", - category: SecureLogger.session, level: .info) + SecureLogger.info("⭐️ Adding favorite: \(peerNickname) (\(peerNoisePublicKey.hexEncodedString()))", category: .session) let existing = favorites[peerNoisePublicKey] @@ -64,8 +63,7 @@ final class FavoritesPersistenceService: ObservableObject { // Log if this creates a mutual favorite if relationship.isMutual { - SecureLogger.log("💕 Mutual favorite relationship established with \(peerNickname)!", - category: SecureLogger.session, level: .info) + SecureLogger.info("💕 Mutual favorite relationship established with \(peerNickname)!", category: .session) } favorites[peerNoisePublicKey] = relationship @@ -83,8 +81,7 @@ final class FavoritesPersistenceService: ObservableObject { func removeFavorite(peerNoisePublicKey: Data) { guard let existing = favorites[peerNoisePublicKey] else { return } - SecureLogger.log("⭐️ Removing favorite: \(existing.peerNickname) (\(peerNoisePublicKey.hexEncodedString()))", - category: SecureLogger.session, level: .info) + SecureLogger.info("⭐️ Removing favorite: \(existing.peerNickname) (\(peerNoisePublicKey.hexEncodedString()))", category: .session) // If they still favorite us, keep the record but mark us as not favoriting if existing.theyFavoritedUs { @@ -125,8 +122,7 @@ final class FavoritesPersistenceService: ObservableObject { let existing = favorites[peerNoisePublicKey] let displayName = peerNickname ?? existing?.peerNickname ?? "Unknown" - SecureLogger.log("📨 Received favorite notification: \(displayName) \(favorited ? "favorited" : "unfavorited") us", - category: SecureLogger.session, level: .info) + SecureLogger.info("📨 Received favorite notification: \(displayName) \(favorited ? "favorited" : "unfavorited") us", category: .session) let relationship = FavoriteRelationship( peerNoisePublicKey: peerNoisePublicKey, @@ -147,8 +143,7 @@ final class FavoritesPersistenceService: ObservableObject { // Check if this creates a mutual favorite if relationship.isMutual { - SecureLogger.log("💕 Mutual favorite relationship established with \(displayName)!", - category: SecureLogger.session, level: .info) + SecureLogger.info("💕 Mutual favorite relationship established with \(displayName)!", category: .session) } } @@ -240,15 +235,13 @@ final class FavoritesPersistenceService: ObservableObject { /// Update noise public key when peer reconnects with new ID func updateNoisePublicKey(from oldKey: Data, to newKey: Data, peerNickname: String) { guard let existing = favorites[oldKey] else { - SecureLogger.log("⚠️ Cannot update noise key - no favorite found for \(oldKey.hexEncodedString())", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Cannot update noise key - no favorite found for \(oldKey.hexEncodedString())", category: .session) return } // Check if we already have a favorite with the new key if favorites[newKey] != nil { - SecureLogger.log("⚠️ Favorite already exists with new key \(newKey.hexEncodedString()), removing old entry", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Favorite already exists with new key \(newKey.hexEncodedString()), removing old entry", category: .session) favorites.removeValue(forKey: oldKey) saveFavorites() return @@ -302,7 +295,7 @@ final class FavoritesPersistenceService: ObservableObject { /// Clear all favorites - used for panic mode func clearAllFavorites() { - SecureLogger.log("🧹 Clearing all favorites (panic mode)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("🧹 Clearing all favorites (panic mode)", category: .session) favorites.removeAll() saveFavorites() @@ -336,7 +329,7 @@ final class FavoritesPersistenceService: ObservableObject { // Successfully saved favorites } catch { - SecureLogger.log("Failed to save favorites: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to save favorites: \(error)", category: .session) } } @@ -354,14 +347,12 @@ final class FavoritesPersistenceService: ObservableObject { let decoder = JSONDecoder() let relationships = try decoder.decode([FavoriteRelationship].self, from: data) - SecureLogger.log("✅ Loaded \(relationships.count) favorite relationships", - category: SecureLogger.session, level: .info) + SecureLogger.info("✅ Loaded \(relationships.count) favorite relationships", category: .session) // Log Nostr public key info for relationship in relationships { if relationship.peerNostrPublicKey == nil { - SecureLogger.log("⚠️ No Nostr public key stored for '\(relationship.peerNickname)'", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ No Nostr public key stored for '\(relationship.peerNickname)'", category: .session) } } @@ -372,8 +363,7 @@ final class FavoritesPersistenceService: ObservableObject { for relationship in relationships { // Check for duplicates by public key (the actual unique identifier) if let existing = seenPublicKeys[relationship.peerNoisePublicKey] { - SecureLogger.log("⚠️ Duplicate favorite found for public key \(relationship.peerNoisePublicKey.hexEncodedString()) - nicknames: '\(existing.peerNickname)' vs '\(relationship.peerNickname)'", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Duplicate favorite found for public key \(relationship.peerNoisePublicKey.hexEncodedString()) - nicknames: '\(existing.peerNickname)' vs '\(relationship.peerNickname)'", category: .session) // Keep the most recent or most complete relationship if relationship.lastUpdated > existing.lastUpdated || @@ -414,7 +404,7 @@ final class FavoritesPersistenceService: ObservableObject { // Log loaded relationships // Loaded relationships successfully } catch { - SecureLogger.log("Failed to load favorites: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to load favorites: \(error)", category: .session) } } } diff --git a/bitchat/Services/KeychainManager.swift b/bitchat/Services/KeychainManager.swift index 5d19db41..d5c129e3 100644 --- a/bitchat/Services/KeychainManager.swift +++ b/bitchat/Services/KeychainManager.swift @@ -8,7 +8,6 @@ import Foundation import Security -import os.log final class KeychainManager { static let shared = KeychainManager() @@ -53,7 +52,7 @@ final class KeychainManager { func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool { let fullKey = "identity_\(key)" let result = saveData(keyData, forKey: fullKey) - SecureLogger.logKeyOperation("save", keyType: key, success: result) + SecureLogger.logKeyOperation(.save, keyType: key, success: result) return result } @@ -64,7 +63,7 @@ final class KeychainManager { func deleteIdentityKey(forKey key: String) -> Bool { let result = delete(forKey: "identity_\(key)") - SecureLogger.logKeyOperation("delete", keyType: key, success: result) + SecureLogger.logKeyOperation(.delete, keyType: key, success: result) return result } @@ -113,9 +112,9 @@ final class KeychainManager { if status == errSecSuccess { return true } if status == -34018 && !triedWithoutGroup { - SecureLogger.logError(NSError(domain: "Keychain", code: -34018), context: "Missing keychain entitlement", category: SecureLogger.keychain) + SecureLogger.error(NSError(domain: "Keychain", code: -34018), context: "Missing keychain entitlement", category: .keychain) } else if status != errSecDuplicateItem { - SecureLogger.logError(NSError(domain: "Keychain", code: Int(status)), context: "Error saving to keychain", category: SecureLogger.keychain) + SecureLogger.error(NSError(domain: "Keychain", code: Int(status)), context: "Error saving to keychain", category: .keychain) } return false } @@ -151,7 +150,7 @@ final class KeychainManager { if status == errSecSuccess { return result as? Data } if status == -34018 { - SecureLogger.logError(NSError(domain: "Keychain", code: -34018), context: "Missing keychain entitlement", category: SecureLogger.keychain) + SecureLogger.error(NSError(domain: "Keychain", code: -34018), context: "Missing keychain entitlement", category: .keychain) } return nil } @@ -198,7 +197,7 @@ final class KeychainManager { // Delete ALL keychain data for panic mode func deleteAllKeychainData() -> Bool { - SecureLogger.log("Panic mode - deleting all keychain data", category: SecureLogger.security, level: .warning) + SecureLogger.warning("Panic mode - deleting all keychain data", category: .security) var totalDeleted = 0 @@ -261,7 +260,7 @@ final class KeychainManager { let deleteStatus = SecItemDelete(deleteQuery as CFDictionary) if deleteStatus == errSecSuccess { totalDeleted += 1 - SecureLogger.log("Deleted keychain item: \(account) from \(service)", category: SecureLogger.keychain, level: .info) + SecureLogger.info("Deleted keychain item: \(account) from \(service)", category: .keychain) } } } @@ -303,7 +302,7 @@ final class KeychainManager { totalDeleted += 1 } - SecureLogger.log("Panic mode cleanup completed. Total items deleted: \(totalDeleted)", category: SecureLogger.keychain, level: .warning) + SecureLogger.warning("Panic mode cleanup completed. Total items deleted: \(totalDeleted)", category: .keychain) return totalDeleted > 0 } diff --git a/bitchat/Services/LocationChannelManager.swift b/bitchat/Services/LocationChannelManager.swift index 99ea62b7..4b311528 100644 --- a/bitchat/Services/LocationChannelManager.swift +++ b/bitchat/Services/LocationChannelManager.swift @@ -197,8 +197,7 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { // Surface as denied/restricted if relevant; otherwise keep previous state - SecureLogger.log("LocationChannelManager: location error: \(error.localizedDescription)", - category: SecureLogger.session, level: .error) + SecureLogger.error("LocationChannelManager: location error: \(error.localizedDescription)", category: .session) } // MARK: - Helpers diff --git a/bitchat/Services/MessageRouter.swift b/bitchat/Services/MessageRouter.swift index cc0472e5..2ea05140 100644 --- a/bitchat/Services/MessageRouter.swift +++ b/bitchat/Services/MessageRouter.swift @@ -39,40 +39,34 @@ final class MessageRouter { func sendPrivate(_ content: String, to peerID: String, recipientNickname: String, messageID: String) { let reachableMesh = mesh.isPeerReachable(peerID) if reachableMesh { - SecureLogger.log("Routing PM via mesh (reachable) to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Routing PM via mesh (reachable) to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session) // BLEService will initiate a handshake if needed and queue the message mesh.sendPrivateMessage(content, to: peerID, recipientNickname: recipientNickname, messageID: messageID) } else if canSendViaNostr(peerID: peerID) { - SecureLogger.log("Routing PM via Nostr to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Routing PM via Nostr to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session) nostr.sendPrivateMessage(content, to: peerID, recipientNickname: recipientNickname, messageID: messageID) } else { // Queue for later (when mesh connects or Nostr mapping appears) if outbox[peerID] == nil { outbox[peerID] = [] } outbox[peerID]?.append((content, recipientNickname, messageID)) - SecureLogger.log("Queued PM for \(peerID.prefix(8))… (no mesh, no Nostr mapping) id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Queued PM for \(peerID.prefix(8))… (no mesh, no Nostr mapping) id=\(messageID.prefix(8))…", category: .session) } } func sendReadReceipt(_ receipt: ReadReceipt, to peerID: String) { // Prefer mesh for reachable peers; BLE will queue if handshake is needed if mesh.isPeerReachable(peerID) { - SecureLogger.log("Routing READ ack via mesh (reachable) to \(peerID.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Routing READ ack via mesh (reachable) to \(peerID.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", category: .session) mesh.sendReadReceipt(receipt, to: peerID) } else { - SecureLogger.log("Routing READ ack via Nostr to \(peerID.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Routing READ ack via Nostr to \(peerID.prefix(8))… id=\(receipt.originalMessageID.prefix(8))…", category: .session) nostr.sendReadReceipt(receipt, to: peerID) } } func sendDeliveryAck(_ messageID: String, to peerID: String) { if mesh.isPeerReachable(peerID) { - SecureLogger.log("Routing DELIVERED ack via mesh (reachable) to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Routing DELIVERED ack via mesh (reachable) to \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session) mesh.sendDeliveryAck(for: messageID, to: peerID) } else { nostr.sendDeliveryAck(for: messageID, to: peerID) @@ -108,18 +102,15 @@ final class MessageRouter { func flushOutbox(for peerID: String) { guard let queued = outbox[peerID], !queued.isEmpty else { return } - SecureLogger.log("Flushing outbox for \(peerID.prefix(8))… count=\(queued.count)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Flushing outbox for \(peerID.prefix(8))… count=\(queued.count)", category: .session) var remaining: [(content: String, nickname: String, messageID: String)] = [] // Prefer mesh if connected; else try Nostr if mapping exists for (content, nickname, messageID) in queued { if mesh.isPeerReachable(peerID) { - SecureLogger.log("Outbox -> mesh for \(peerID.prefix(8))… id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Outbox -> mesh for \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session) mesh.sendPrivateMessage(content, to: peerID, recipientNickname: nickname, messageID: messageID) } else if canSendViaNostr(peerID: peerID) { - SecureLogger.log("Outbox -> Nostr for \(peerID.prefix(8))… id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("Outbox -> Nostr for \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session) nostr.sendPrivateMessage(content, to: peerID, recipientNickname: nickname, messageID: messageID) } else { // Keep unsent items queued diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index 797e853f..923575e5 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -85,7 +85,6 @@ import Foundation import CryptoKit -import os.log // MARK: - Encryption Status @@ -190,7 +189,7 @@ final class NoiseEncryptionService { if let identityData = KeychainManager.shared.getIdentityKey(forKey: "noiseStaticKey"), let key = try? Curve25519.KeyAgreement.PrivateKey(rawRepresentation: identityData) { loadedKey = key - SecureLogger.logKeyOperation("load", keyType: "noiseStaticKey", success: true) + SecureLogger.logKeyOperation(.load, keyType: "noiseStaticKey", success: true) } // If no identity exists, create new one else { @@ -199,7 +198,7 @@ final class NoiseEncryptionService { // Save to keychain let saved = KeychainManager.shared.saveIdentityKey(keyData, forKey: "noiseStaticKey") - SecureLogger.logKeyOperation("create", keyType: "noiseStaticKey", success: saved) + SecureLogger.logKeyOperation(.create, keyType: "noiseStaticKey", success: saved) } // Now assign the final value @@ -213,7 +212,7 @@ final class NoiseEncryptionService { if let signingData = KeychainManager.shared.getIdentityKey(forKey: "ed25519SigningKey"), let key = try? Curve25519.Signing.PrivateKey(rawRepresentation: signingData) { loadedSigningKey = key - SecureLogger.logKeyOperation("load", keyType: "ed25519SigningKey", success: true) + SecureLogger.logKeyOperation(.load, keyType: "ed25519SigningKey", success: true) } // If no signing key exists, create new one else { @@ -222,7 +221,7 @@ final class NoiseEncryptionService { // Save to keychain let saved = KeychainManager.shared.saveIdentityKey(keyData, forKey: "ed25519SigningKey") - SecureLogger.logKeyOperation("create", keyType: "ed25519SigningKey", success: saved) + SecureLogger.logKeyOperation(.create, keyType: "ed25519SigningKey", success: saved) } // Now assign the signing keys @@ -269,8 +268,8 @@ final class NoiseEncryptionService { // Clear from keychain let deletedStatic = KeychainManager.shared.deleteIdentityKey(forKey: "noiseStaticKey") let deletedSigning = KeychainManager.shared.deleteIdentityKey(forKey: "ed25519SigningKey") - SecureLogger.logKeyOperation("delete", keyType: "identity keys", success: deletedStatic && deletedSigning) - SecureLogger.log("Panic mode activated - identity cleared", category: SecureLogger.security, level: .warning) + SecureLogger.logKeyOperation(.delete, keyType: "identity keys", success: deletedStatic && deletedSigning) + SecureLogger.warning("Panic mode activated - identity cleared", category: .security) // Stop rekey timer stopRekeyTimer() } @@ -281,7 +280,7 @@ final class NoiseEncryptionService { let signature = try signingKey.signature(for: data) return signature } catch { - SecureLogger.logError(error, context: "Failed to sign data", category: SecureLogger.noise) + SecureLogger.error(error, context: "Failed to sign data") return nil } } @@ -292,7 +291,7 @@ final class NoiseEncryptionService { let signingPublicKey = try Curve25519.Signing.PublicKey(rawRepresentation: publicKey) return signingPublicKey.isValidSignature(signature, for: data) } catch { - SecureLogger.logError(error, context: "Failed to verify signature", category: SecureLogger.noise) + SecureLogger.error(error, context: "Failed to verify signature") return false } } @@ -392,17 +391,17 @@ final class NoiseEncryptionService { // Validate peer ID guard NoiseSecurityValidator.validatePeerID(peerID) else { - SecureLogger.logSecurityEvent(.authenticationFailed(peerID: peerID), level: .warning) + SecureLogger.warning(.authenticationFailed(peerID: peerID)) throw NoiseSecurityError.invalidPeerID } // Check rate limit guard rateLimiter.allowHandshake(from: peerID) else { - SecureLogger.logSecurityEvent(.authenticationFailed(peerID: "Rate limited: \(peerID)"), level: .warning) + SecureLogger.warning(.authenticationFailed(peerID: "Rate limited: \(peerID)")) throw NoiseSecurityError.rateLimitExceeded } - SecureLogger.logSecurityEvent(.handshakeStarted(peerID: peerID)) + SecureLogger.info(.handshakeStarted(peerID: peerID)) // Return raw handshake data without wrapper // The Noise protocol handles its own message format @@ -415,19 +414,19 @@ final class NoiseEncryptionService { // Validate peer ID guard NoiseSecurityValidator.validatePeerID(peerID) else { - SecureLogger.logSecurityEvent(.authenticationFailed(peerID: peerID), level: .warning) + SecureLogger.warning(.authenticationFailed(peerID: peerID)) throw NoiseSecurityError.invalidPeerID } // Validate message size guard NoiseSecurityValidator.validateHandshakeMessageSize(message) else { - SecureLogger.logSecurityEvent(.handshakeFailed(peerID: peerID, error: "Message too large"), level: .warning) + SecureLogger.warning(.handshakeFailed(peerID: peerID, error: "Message too large")) throw NoiseSecurityError.messageTooLarge } // Check rate limit guard rateLimiter.allowHandshake(from: peerID) else { - SecureLogger.logSecurityEvent(.authenticationFailed(peerID: "Rate limited: \(peerID)"), level: .warning) + SecureLogger.warning(.authenticationFailed(peerID: "Rate limited: \(peerID)")) throw NoiseSecurityError.rateLimitExceeded } @@ -521,7 +520,7 @@ final class NoiseEncryptionService { peerFingerprints.removeValue(forKey: peerID) } - SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID)) + SecureLogger.info(.sessionExpired(peerID: peerID)) } // MARK: - Private Helpers @@ -537,7 +536,7 @@ final class NoiseEncryptionService { } // Log security event - SecureLogger.logSecurityEvent(.handshakeCompleted(peerID: peerID)) + SecureLogger.info(.handshakeCompleted(peerID: peerID)) // Notify all handlers about authentication serviceQueue.async { [weak self] in @@ -573,12 +572,12 @@ final class NoiseEncryptionService { // Attempt to rekey the session do { try sessionManager.initiateRekey(for: peerID) - SecureLogger.log("Key rotation initiated for peer: \(peerID)", category: SecureLogger.security, level: .debug) + SecureLogger.debug("Key rotation initiated for peer: \(peerID)", category: .security) // Signal that handshake is needed onHandshakeRequired?(peerID) } catch { - SecureLogger.logError(error, context: "Failed to initiate rekey for peer: \(peerID)", category: SecureLogger.session) + SecureLogger.error(error, context: "Failed to initiate rekey for peer: \(peerID)", category: .session) } } } diff --git a/bitchat/Services/NostrTransport.swift b/bitchat/Services/NostrTransport.swift index c1fad64d..fc7640c8 100644 --- a/bitchat/Services/NostrTransport.swift +++ b/bitchat/Services/NostrTransport.swift @@ -51,31 +51,29 @@ final class NostrTransport: Transport { Task { @MainActor in guard let recipientNpub = resolveRecipientNpub(for: peerID) else { return } guard let senderIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { return } - SecureLogger.log("NostrTransport: preparing PM to \(recipientNpub.prefix(16))… for peerID \(peerID.prefix(8))… id=\(messageID.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: preparing PM to \(recipientNpub.prefix(16))… for peerID \(peerID.prefix(8))… id=\(messageID.prefix(8))…", category: .session) // Convert recipient npub -> hex (x-only) let recipientHex: String do { let (hrp, data) = try Bech32.decode(recipientNpub) guard hrp == "npub" else { - SecureLogger.log("NostrTransport: recipient key not npub (hrp=\(hrp))", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: recipient key not npub (hrp=\(hrp))", category: .session) return } recipientHex = data.hexEncodedString() } catch { - SecureLogger.log("NostrTransport: failed to decode npub -> hex: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to decode npub -> hex: \(error)", category: .session) return } guard let embedded = NostrEmbeddedBitChat.encodePMForNostr(content: content, messageID: messageID, recipientPeerID: peerID, senderPeerID: senderPeerID) else { - SecureLogger.log("NostrTransport: failed to embed PM packet", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to embed PM packet", category: .session) return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: senderIdentity) else { - SecureLogger.log("NostrTransport: failed to build Nostr event for PM", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to build Nostr event for PM", category: .session) return } - SecureLogger.log("NostrTransport: sending PM giftWrap id=\(event.id.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: sending PM giftWrap id=\(event.id.prefix(16))…", category: .session) NostrRelayManager.shared.sendEvent(event) } } @@ -99,8 +97,7 @@ final class NostrTransport: Transport { Task { @MainActor in guard let recipientNpub = resolveRecipientNpub(for: item.peerID) else { scheduleNextReadAck(); return } guard let senderIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { scheduleNextReadAck(); return } - SecureLogger.log("NostrTransport: preparing READ ack for id=\(item.receipt.originalMessageID.prefix(8))… to \(recipientNpub.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: preparing READ ack for id=\(item.receipt.originalMessageID.prefix(8))… to \(recipientNpub.prefix(16))…", category: .session) // Convert recipient npub -> hex let recipientHex: String do { @@ -109,15 +106,14 @@ final class NostrTransport: Transport { recipientHex = data.hexEncodedString() } catch { scheduleNextReadAck(); return } guard let ack = NostrEmbeddedBitChat.encodeAckForNostr(type: .readReceipt, messageID: item.receipt.originalMessageID, recipientPeerID: item.peerID, senderPeerID: senderPeerID) else { - SecureLogger.log("NostrTransport: failed to embed READ ack", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to embed READ ack", category: .session) scheduleNextReadAck(); return } guard let event = try? NostrProtocol.createPrivateMessage(content: ack, recipientPubkey: recipientHex, senderIdentity: senderIdentity) else { - SecureLogger.log("NostrTransport: failed to build Nostr event for READ ack", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to build Nostr event for READ ack", category: .session) scheduleNextReadAck(); return } - SecureLogger.log("NostrTransport: sending READ ack giftWrap id=\(event.id.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: sending READ ack giftWrap id=\(event.id.prefix(16))…", category: .session) NostrRelayManager.shared.sendEvent(event) scheduleNextReadAck() } @@ -136,8 +132,7 @@ final class NostrTransport: Transport { guard let recipientNpub = resolveRecipientNpub(for: peerID) else { return } guard let senderIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { return } let content = isFavorite ? "[FAVORITED]:\(senderIdentity.npub)" : "[UNFAVORITED]:\(senderIdentity.npub)" - SecureLogger.log("NostrTransport: preparing FAVORITE(\(isFavorite)) to \(recipientNpub.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: preparing FAVORITE(\(isFavorite)) to \(recipientNpub.prefix(16))…", category: .session) // Convert recipient npub -> hex let recipientHex: String do { @@ -146,15 +141,14 @@ final class NostrTransport: Transport { recipientHex = data.hexEncodedString() } catch { return } guard let embedded = NostrEmbeddedBitChat.encodePMForNostr(content: content, messageID: UUID().uuidString, recipientPeerID: peerID, senderPeerID: senderPeerID) else { - SecureLogger.log("NostrTransport: failed to embed favorite notification", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to embed favorite notification", category: .session) return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: senderIdentity) else { - SecureLogger.log("NostrTransport: failed to build Nostr event for favorite notification", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to build Nostr event for favorite notification", category: .session) return } - SecureLogger.log("NostrTransport: sending favorite giftWrap id=\(event.id.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: sending favorite giftWrap id=\(event.id.prefix(16))…", category: .session) NostrRelayManager.shared.sendEvent(event) } } @@ -180,8 +174,7 @@ final class NostrTransport: Transport { Task { @MainActor in guard let recipientNpub = resolveRecipientNpub(for: peerID) else { return } guard let senderIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { return } - SecureLogger.log("NostrTransport: preparing DELIVERED ack for id=\(messageID.prefix(8))… to \(recipientNpub.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: preparing DELIVERED ack for id=\(messageID.prefix(8))… to \(recipientNpub.prefix(16))…", category: .session) let recipientHex: String do { let (hrp, data) = try Bech32.decode(recipientNpub) @@ -189,15 +182,14 @@ final class NostrTransport: Transport { recipientHex = data.hexEncodedString() } catch { return } guard let ack = NostrEmbeddedBitChat.encodeAckForNostr(type: .delivered, messageID: messageID, recipientPeerID: peerID, senderPeerID: senderPeerID) else { - SecureLogger.log("NostrTransport: failed to embed DELIVERED ack", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to embed DELIVERED ack", category: .session) return } guard let event = try? NostrProtocol.createPrivateMessage(content: ack, recipientPubkey: recipientHex, senderIdentity: senderIdentity) else { - SecureLogger.log("NostrTransport: failed to build Nostr event for DELIVERED ack", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to build Nostr event for DELIVERED ack", category: .session) return } - SecureLogger.log("NostrTransport: sending DELIVERED ack giftWrap id=\(event.id.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: sending DELIVERED ack giftWrap id=\(event.id.prefix(16))…", category: .session) NostrRelayManager.shared.sendEvent(event) } } @@ -205,8 +197,7 @@ final class NostrTransport: Transport { // MARK: - Geohash ACK helpers func sendDeliveryAckGeohash(for messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) { Task { @MainActor in - SecureLogger.log("GeoDM: send DELIVERED -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: send DELIVERED -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", category: .session) guard let embedded = NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .delivered, messageID: messageID, senderPeerID: senderPeerID) else { return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: identity) else { return } NostrRelayManager.registerPendingGiftWrap(id: event.id) @@ -216,8 +207,7 @@ final class NostrTransport: Transport { func sendReadReceiptGeohash(_ messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) { Task { @MainActor in - SecureLogger.log("GeoDM: send READ -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: send READ -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", category: .session) guard let embedded = NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .readReceipt, messageID: messageID, senderPeerID: senderPeerID) else { return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: identity) else { return } NostrRelayManager.registerPendingGiftWrap(id: event.id) @@ -229,19 +219,17 @@ final class NostrTransport: Transport { func sendPrivateMessageGeohash(content: String, toRecipientHex recipientHex: String, from identity: NostrIdentity, messageID: String) { Task { @MainActor in guard !recipientHex.isEmpty else { return } - SecureLogger.log("GeoDM: send PM -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: send PM -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", category: .session) // Build embedded BitChat packet without recipient peer ID guard let embedded = NostrEmbeddedBitChat.encodePMForNostrNoRecipient(content: content, messageID: messageID, senderPeerID: senderPeerID) else { - SecureLogger.log("NostrTransport: failed to embed geohash PM packet", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to embed geohash PM packet", category: .session) return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: identity) else { - SecureLogger.log("NostrTransport: failed to build Nostr event for geohash PM", category: SecureLogger.session, level: .error) + SecureLogger.error("NostrTransport: failed to build Nostr event for geohash PM", category: .session) return } - SecureLogger.log("NostrTransport: sending geohash PM giftWrap id=\(event.id.prefix(16))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("NostrTransport: sending geohash PM giftWrap id=\(event.id.prefix(16))…", category: .session) NostrRelayManager.registerPendingGiftWrap(id: event.id) NostrRelayManager.shared.sendEvent(event) } diff --git a/bitchat/Services/PrivateChatManager.swift b/bitchat/Services/PrivateChatManager.swift index 41e600fb..05389c57 100644 --- a/bitchat/Services/PrivateChatManager.swift +++ b/bitchat/Services/PrivateChatManager.swift @@ -228,8 +228,7 @@ final class PrivateChatManager: ObservableObject { // Route via MessageRouter to avoid handshakeRequired spam when session isn't established if let router = messageRouter { - SecureLogger.log("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.prefix(8))… via router", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("PrivateChatManager: sending READ ack for \(message.id.prefix(8))… to \(senderPeerID.prefix(8))… via router", category: .session) Task { @MainActor in router.sendReadReceipt(receipt, to: senderPeerID) } diff --git a/bitchat/Services/Tor/TorManager.swift b/bitchat/Services/Tor/TorManager.swift index 8189514c..da07937b 100644 --- a/bitchat/Services/Tor/TorManager.swift +++ b/bitchat/Services/Tor/TorManager.swift @@ -189,7 +189,7 @@ final class TorManager: ObservableObject { var started = false // If already running (per C glue), treat as started if tor_host_is_running() != 0 { - SecureLogger.log("TorManager: embed reports already running", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: embed reports already running", category: .session) return true } dir.withCString { dptr in @@ -198,9 +198,9 @@ final class TorManager: ObservableObject { let rc = tor_host_start(dptr, sptr, cptr, 1) started = (rc == 0) if rc != 0 { - SecureLogger.log("TorManager: tor_host_start failed rc=\(rc)", category: SecureLogger.session, level: .error) + SecureLogger.error("TorManager: tor_host_start failed rc=\(rc)", category: .session) } else { - SecureLogger.log("TorManager: tor_host_start OK (\(socks), control \(control))", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: tor_host_start OK (\(socks), control \(control))", category: .session) } } } @@ -215,10 +215,10 @@ final class TorManager: ObservableObject { await MainActor.run { self.socksReady = ready if ready { - SecureLogger.log("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort) [embed]", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort) [embed]", category: .session) } else { self.lastError = NSError(domain: "TorManager", code: -14, userInfo: [NSLocalizedDescriptionKey: "Tor SOCKS not reachable after embed start"]) - SecureLogger.log("TorManager: SOCKS not reachable (timeout) [embed]", category: SecureLogger.session, level: .error) + SecureLogger.error("TorManager: SOCKS not reachable (timeout) [embed]", category: .session) } } } @@ -282,13 +282,13 @@ final class TorManager: ObservableObject { /// Returns true if the attempt started and port probing was scheduled. private func startTorViaDlopen() -> Bool { guard let fwURL = frameworkBinaryURL() else { - SecureLogger.log("TorManager: no embedded tor framework found", category: SecureLogger.session, level: .warning) + SecureLogger.warning("TorManager: no embedded tor framework found", category: .session) return false } // Load the library let mode = RTLD_NOW | RTLD_LOCAL - SecureLogger.log("TorManager: dlopen(\(fwURL.lastPathComponent))…", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: dlopen(\(fwURL.lastPathComponent))…", category: .session) guard let handle = dlopen(fwURL.path, mode) else { let err = String(cString: dlerror()) self.lastError = NSError(domain: "TorManager", code: -10, userInfo: [NSLocalizedDescriptionKey: "dlopen failed: \(err)"]) @@ -314,7 +314,7 @@ final class TorManager: ObservableObject { argv.append(contentsOf: ["-f", torrc]) } // Run Tor on a background thread to avoid blocking the main actor - SecureLogger.log("TorManager: launching tor_main with torrc", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: launching tor_main with torrc", category: .session) let argc = Int32(argv.count) DispatchQueue.global(qos: .utility).async { // Build stable C argv in this thread @@ -339,9 +339,9 @@ final class TorManager: ObservableObject { self.socksReady = ready if !ready { self.lastError = NSError(domain: "TorManager", code: -12, userInfo: [NSLocalizedDescriptionKey: "Tor SOCKS not reachable after dlopen start"]) - SecureLogger.log("TorManager: SOCKS not reachable (timeout)", category: SecureLogger.session, level: .error) + SecureLogger.error("TorManager: SOCKS not reachable (timeout)", category: .session) } else { - SecureLogger.log("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort)", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort)", category: .session) } // isStarting will be cleared when bootstrap reaches 100% } @@ -385,7 +385,7 @@ final class TorManager: ObservableObject { var argv: [String] = ["tor"] if let torrc = torrcURL()?.path { argv.append(contentsOf: ["-f", torrc]) } - SecureLogger.log("TorManager: starting tor_main (static)", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: starting tor_main (static)", category: .session) let argc = Int32(argv.count) DispatchQueue.global(qos: .utility).async { // Build stable C argv in this thread @@ -409,10 +409,10 @@ final class TorManager: ObservableObject { await MainActor.run { self.socksReady = ready if ready { - SecureLogger.log("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort)", category: SecureLogger.session, level: .info) + SecureLogger.info("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort)", category: .session) } else { self.lastError = NSError(domain: "TorManager", code: -13, userInfo: [NSLocalizedDescriptionKey: "Tor SOCKS not reachable after static start"]) - SecureLogger.log("TorManager: SOCKS not reachable (timeout)", category: SecureLogger.session, level: .error) + SecureLogger.error("TorManager: SOCKS not reachable (timeout)", category: .session) } // isStarting will be cleared when bootstrap reaches 100% } diff --git a/bitchat/Services/UnifiedPeerService.swift b/bitchat/Services/UnifiedPeerService.swift index 87813d90..cdef5858 100644 --- a/bitchat/Services/UnifiedPeerService.swift +++ b/bitchat/Services/UnifiedPeerService.swift @@ -203,11 +203,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { if let favorite = favoriteByNickname, let noiseKey = peerInfo.noisePublicKey { - SecureLogger.log( - "🔄 Found favorite for '\(peerInfo.nickname)' by nickname, updating noise key", - category: SecureLogger.session, - level: .debug - ) + SecureLogger.debug("🔄 Found favorite for '\(peerInfo.nickname)' by nickname, updating noise key", category: .session) // Update the favorite's key in persistence favoritesService.updateNoisePublicKey( @@ -282,8 +278,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { /// Toggle favorite status func toggleFavorite(_ peerID: String) { guard let peer = getPeer(by: peerID) else { - SecureLogger.log("⚠️ Cannot toggle favorite - peer not found: \(peerID)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Cannot toggle favorite - peer not found: \(peerID)", category: .session) return } @@ -293,15 +288,13 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { var actualNickname = peer.nickname // Debug logging to understand the issue - SecureLogger.log("🔍 Toggle favorite - peer.nickname: '\(peer.nickname)', peer.displayName: '\(peer.displayName)', peerID: \(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔍 Toggle favorite - peer.nickname: '\(peer.nickname)', peer.displayName: '\(peer.displayName)', peerID: \(peerID)", category: .session) if actualNickname.isEmpty { // Try to get from mesh service's current peer list if let meshPeerNickname = meshService.peerNickname(peerID: peerID) { actualNickname = meshPeerNickname - SecureLogger.log("🔍 Got nickname from mesh service: '\(actualNickname)'", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔍 Got nickname from mesh service: '\(actualNickname)'", category: .session) } } @@ -328,8 +321,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { } // Log the final nickname being saved - SecureLogger.log("⭐️ Toggled favorite for '\(finalNickname)' (peerID: \(peerID), was: \(wasFavorite), now: \(!wasFavorite))", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("⭐️ Toggled favorite for '\(finalNickname)' (peerID: \(peerID), was: \(wasFavorite), now: \(!wasFavorite))", category: .session) // Send favorite notification to the peer via router (mesh or Nostr) if let router = messageRouter { diff --git a/bitchat/Utils/OSLog+Categories.swift b/bitchat/Utils/OSLog+Categories.swift new file mode 100644 index 00000000..7172bf82 --- /dev/null +++ b/bitchat/Utils/OSLog+Categories.swift @@ -0,0 +1,20 @@ +// +// OSLog+Categories.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import os.log + +extension OSLog { + private static let subsystem = "chat.bitchat" + + static let noise = OSLog(subsystem: subsystem, category: "noise") + static let encryption = OSLog(subsystem: subsystem, category: "encryption") + static let keychain = OSLog(subsystem: subsystem, category: "keychain") + static let session = OSLog(subsystem: subsystem, category: "session") + static let security = OSLog(subsystem: subsystem, category: "security") + static let handshake = OSLog(subsystem: subsystem, category: "handshake") +} diff --git a/bitchat/Utils/SecureLogger.swift b/bitchat/Utils/SecureLogger.swift index 41f0f478..3846a397 100644 --- a/bitchat/Utils/SecureLogger.swift +++ b/bitchat/Utils/SecureLogger.swift @@ -13,17 +13,6 @@ import os.log /// Provides safe logging that filters sensitive data and security events final class SecureLogger { - // MARK: - Log Categories - - private static let subsystem = "chat.bitchat" - - static let noise = OSLog(subsystem: subsystem, category: "noise") - static let encryption = OSLog(subsystem: subsystem, category: "encryption") - static let keychain = OSLog(subsystem: subsystem, category: "keychain") - static let session = OSLog(subsystem: subsystem, category: "session") - static let security = OSLog(subsystem: subsystem, category: "security") - static let handshake = OSLog(subsystem: subsystem, category: "handshake") - // MARK: - Timestamp Formatter private static let timestampFormatter: DateFormatter = { @@ -124,24 +113,90 @@ final class SecureLogger { // MARK: - Public Logging Methods - /// Log a security event - static func logSecurityEvent(_ event: SecurityEvent, level: LogLevel = .info, - file: String = #file, line: Int = #line, function: String = #function) { - guard shouldLog(level) else { return } - let location = formatLocation(file: file, line: line, function: function) - let message = "\(location) \(event.message)" - - #if DEBUG - os_log("%{public}@", log: security, type: level.osLogType, message) - #else - // In release, use private logging to prevent sensitive data exposure - os_log("%{private}@", log: security, type: level.osLogType, message) - #endif + static func debug(_ message: @autoclosure () -> String, category: OSLog = .noise, + file: String = #file, line: Int = #line, function: String = #function) { + log(message(), category: category, level: .debug, file: file, line: line, function: function) } + static func info(_ message: @autoclosure () -> String, category: OSLog = .noise, + file: String = #file, line: Int = #line, function: String = #function) { + log(message(), category: category, level: .info, file: file, line: line, function: function) + } + + static func warning(_ message: @autoclosure () -> String, category: OSLog = .noise, + file: String = #file, line: Int = #line, function: String = #function) { + log(message(), category: category, level: .warning, file: file, line: line, function: function) + } + + static func error(_ message: @autoclosure () -> String, category: OSLog = .noise, + file: String = #file, line: Int = #line, function: String = #function) { + log(message(), category: category, level: .error, file: file, line: line, function: function) + } + + // MARK: Security Event Logging + + static func debug(_ event: SecurityEvent, file: String = #file, line: Int = #line, function: String = #function) { + logSecurityEvent(event, level: .debug, file: file, line: line, function: function) + } + + static func info(_ event: SecurityEvent, file: String = #file, line: Int = #line, function: String = #function) { + logSecurityEvent(event, level: .info, file: file, line: line, function: function) + } + + static func warning(_ event: SecurityEvent, file: String = #file, line: Int = #line, function: String = #function) { + logSecurityEvent(event, level: .warning, file: file, line: line, function: function) + } + + static func error(_ event: SecurityEvent, file: String = #file, line: Int = #line, function: String = #function) { + logSecurityEvent(event, level: .error, file: file, line: line, function: function) + } + + /// Log errors with context + static func error(_ error: Error, context: @autoclosure () -> String, category: OSLog = .noise, + file: String = #file, line: Int = #line, function: String = #function) { + let location = formatLocation(file: file, line: line, function: function) + let sanitized = sanitize(context()) + let errorDesc = sanitize(error.localizedDescription) + + #if DEBUG + os_log("%{public}@ Error in %{public}@: %{public}@", log: category, type: .error, location, sanitized, errorDesc) + #else + os_log("%{private}@ Error in %{private}@: %{private}@", log: category, type: .error, location, sanitized, errorDesc) + #endif + } +} + +// MARK: - Convenience Extensions + +extension SecureLogger { + + enum KeyOperation: String, CustomStringConvertible { + case load + case create + case generate + case delete + case save + + var description: String { rawValue } + } + + /// Log key management operations + static func logKeyOperation(_ operation: KeyOperation, keyType: String, success: Bool = true, + file: String = #file, line: Int = #line, function: String = #function) { + if success { + debug("Key operation '\(operation)' for \(keyType) succeeded", category: .keychain, file: file, line: line, function: function) + } else { + error("Key operation '\(operation)' for \(keyType) failed", category: .keychain, file: file, line: line, function: function) + } + } +} + +// MARK: - Private Helpers + +private extension SecureLogger { /// Log general messages with automatic sensitive data filtering - static func log(_ message: @autoclosure () -> String, category: OSLog = noise, level: LogLevel = .debug, - file: String = #file, line: Int = #line, function: String = #function) { + static func log(_ message: @autoclosure () -> String, category: OSLog, level: LogLevel, + file: String, line: Int, function: String) { guard shouldLog(level) else { return } let location = formatLocation(file: file, line: line, function: function) let sanitized = sanitize("\(location) \(message())") @@ -156,31 +211,30 @@ final class SecureLogger { #endif } - /// Log errors with context - static func logError(_ error: Error, context: @autoclosure () -> String, category: OSLog = noise, - file: String = #file, line: Int = #line, function: String = #function) { + /// Log a security event + static func logSecurityEvent(_ event: SecurityEvent, level: LogLevel = .info, + file: String, line: Int, function: String) { + guard shouldLog(level) else { return } let location = formatLocation(file: file, line: line, function: function) - let sanitized = sanitize(context()) - let errorDesc = sanitize(error.localizedDescription) + let message = "\(location) \(event.message)" #if DEBUG - os_log("%{public}@ Error in %{public}@: %{public}@", log: category, type: .error, location, sanitized, errorDesc) + os_log("%{public}@", log: .security, type: level.osLogType, message) #else - os_log("%{private}@ Error in %{private}@: %{private}@", log: category, type: .error, location, sanitized, errorDesc) + // In release, use private logging to prevent sensitive data exposure + os_log("%{private}@", log: .security, type: level.osLogType, message) #endif } - // MARK: - Private Helpers - /// Format location information for logging - private static func formatLocation(file: String, line: Int, function: String) -> String { + static func formatLocation(file: String, line: Int, function: String) -> String { let fileName = (file as NSString).lastPathComponent let timestamp = timestampFormatter.string(from: Date()) return "[\(timestamp)] [\(fileName):\(line) \(function)]" } /// Sanitize strings to remove potentially sensitive data - private static func sanitize(_ input: String) -> String { + static func sanitize(_ input: String) -> String { let key = input as NSString // Check cache first @@ -226,45 +280,12 @@ final class SecureLogger { } /// Sanitize individual values - private static func sanitize(_ value: T) -> String { + static func sanitize(_ value: T) -> String { let stringValue = String(describing: value) return sanitize(stringValue) } } -// MARK: - Convenience Extensions - -extension SecureLogger { - - /// Log handshake events - static func logHandshake(_ phase: String, peerID: String, success: Bool = true, - file: String = #file, line: Int = #line, function: String = #function) { - if success { - log("Handshake \(phase) with peer: \(peerID)", category: session, level: .info, - file: file, line: line, function: function) - } else { - log("Handshake \(phase) failed with peer: \(peerID)", category: session, level: .warning, - file: file, line: line, function: function) - } - } - - /// Log encryption operations - static func logEncryption(_ operation: String, success: Bool = true, - file: String = #file, line: Int = #line, function: String = #function) { - let level: LogLevel = success ? .debug : .error - log("Encryption operation '\(operation)' \(success ? "succeeded" : "failed")", - category: encryption, level: level, file: file, line: line, function: function) - } - - /// Log key management operations - static func logKeyOperation(_ operation: String, keyType: String, success: Bool = true, - file: String = #file, line: Int = #line, function: String = #function) { - let level: LogLevel = success ? .debug : .error - log("Key operation '\(operation)' for \(keyType) \(success ? "succeeded" : "failed")", - category: keychain, level: level, file: file, line: line, function: function) - } -} - // MARK: - Migration Helper /// Helper to migrate from print statements to SecureLogger @@ -273,6 +294,6 @@ func secureLog(_ items: Any..., separator: String = " ", terminator: String = "\ file: String = #file, line: Int = #line, function: String = #function) { #if DEBUG let message = items.map { String(describing: $0) }.joined(separator: separator) - SecureLogger.log(message, level: .debug, file: file, line: line, function: function) + SecureLogger.debug(message, file: file, line: line, function: function) #endif } diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index e774dd9a..ca66454c 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -462,8 +462,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let data = try? JSONEncoder().encode(Array(sentReadReceipts)) { UserDefaults.standard.set(data, forKey: "sentReadReceipts") } else { - SecureLogger.log("❌ Failed to encode read receipts for persistence", - category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to encode read receipts for persistence", category: .session) } } } @@ -562,11 +561,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Wait for Tor readiness before Nostr init let ready = await TorManager.shared.awaitReady(timeout: 60) guard ready else { - SecureLogger.log("Nostr init skipped: Tor not ready", category: SecureLogger.session, level: .error) + SecureLogger.error("Nostr init skipped: Tor not ready", category: .session) return } nostrRelayManager = NostrRelayManager.shared - SecureLogger.log("Initializing Nostr relay connections", category: SecureLogger.session, level: .debug) + SecureLogger.debug("Initializing Nostr relay connections", category: .session) // Connect is managed centrally on scene activation; avoid duplicate connects here // Attempt to flush any queued outbox after Nostr comes online @@ -596,8 +595,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if uniquePeers[peer.id] == nil { uniquePeers[peer.id] = peer } else { - SecureLogger.log("⚠️ Duplicate peer ID detected: \(peer.id) (\(peer.displayName))", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Duplicate peer ID detected: \(peer.id) (\(peer.displayName))", category: .session) } } self.peerIndex = uniquePeers @@ -1035,11 +1033,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let idx = self.privateChats[convKey]?.firstIndex(where: { $0.id == messageID }) { self.privateChats[convKey]?[idx].deliveryStatus = .delivered(to: self.displayNameForNostrPubkey(senderPubkey), at: Date()) self.objectWillChange.send() - SecureLogger.log("GeoDM: recv DELIVERED for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoDM: recv DELIVERED for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", category: .session) } else { - SecureLogger.log("GeoDM: delivered ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoDM: delivered ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", category: .session) } } case .readReceipt: @@ -1047,11 +1043,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let idx = self.privateChats[convKey]?.firstIndex(where: { $0.id == messageID }) { self.privateChats[convKey]?[idx].deliveryStatus = .read(by: self.displayNameForNostrPubkey(senderPubkey), at: Date()) self.objectWillChange.send() - SecureLogger.log("GeoDM: recv READ for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoDM: recv READ for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", category: .session) } else { - SecureLogger.log("GeoDM: read ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoDM: read ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", category: .session) } } case .verifyChallenge, .verifyResponse: @@ -1464,14 +1458,13 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { count: TransportConfig.nostrGeoRelayCount ) if targetRelays.isEmpty { - SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning) + SecureLogger.warning("Geo: no geohash relays available for \(ch.geohash); not sending", category: .session) } else { NostrRelayManager.shared.sendEvent(event, to: targetRelays) } // Track ourselves as active participant self.recordGeoParticipant(pubkeyHex: identity.publicKeyHex) - SecureLogger.log("GeoTeleport: sent geo message pub=\(identity.publicKeyHex.prefix(8))… teleported=\(LocationChannelManager.shared.teleported)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoTeleport: sent geo message pub=\(identity.publicKeyHex.prefix(8))… teleported=\(LocationChannelManager.shared.teleported)", category: .session) // If we tagged this as teleported, also mark our pubkey in teleportedGeo for UI // Only when not in our regional set (and regional list is known) let hasRegional = !LocationChannelManager.shared.availableChannels.isEmpty @@ -1479,11 +1472,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if LocationChannelManager.shared.teleported && hasRegional && !inRegional { let key = identity.publicKeyHex.lowercased() self.teleportedGeo = self.teleportedGeo.union([key]) - SecureLogger.log("GeoTeleport: mark self teleported key=\(key.prefix(8))… total=\(self.teleportedGeo.count)", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoTeleport: mark self teleported key=\(key.prefix(8))… total=\(self.teleportedGeo.count)", category: .session) } } catch { - SecureLogger.log("❌ Failed to send geohash message: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to send geohash message: \(error)", category: .session) self.addSystemMessage("failed to send to location channel") } } @@ -1510,7 +1502,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Debug: log if any empty messages are present let emptyMesh = messages.filter { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count if emptyMesh > 0 { - SecureLogger.log("RenderGuard: mesh timeline contains \(emptyMesh) empty messages", category: SecureLogger.session, level: .debug) + SecureLogger.debug("RenderGuard: mesh timeline contains \(emptyMesh) empty messages", category: .session) } stopGeoParticipantsTimer() geohashPeople = [] @@ -1537,7 +1529,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Debug: log if any empty messages are present post-sanitize let emptyGeo = messages.filter { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count if emptyGeo > 0 { - SecureLogger.log("RenderGuard: geohash \(ch.geohash) timeline has \(emptyGeo) empty messages after sanitize", category: SecureLogger.session, level: .debug) + SecureLogger.debug("RenderGuard: geohash \(ch.geohash) timeline has \(emptyGeo) empty messages after sanitize", category: .session) } } // If switching to a location channel, flush any pending geohash-only system messages @@ -1569,8 +1561,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let key = id.publicKeyHex.lowercased() if LocationChannelManager.shared.teleported && hasRegional && !inRegional { teleportedGeo = teleportedGeo.union([key]) - SecureLogger.log("GeoTeleport: channel switch mark self teleported key=\(key.prefix(8))… total=\(teleportedGeo.count)", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoTeleport: channel switch mark self teleported key=\(key.prefix(8))… total=\(teleportedGeo.count)", category: .session) } else { teleportedGeo.remove(key) } @@ -1593,8 +1584,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { self.recordProcessedEvent(event.id) // Log incoming tags for diagnostics let tagSummary = event.tags.map { "[" + $0.joined(separator: ",") + "]" }.joined(separator: ",") - SecureLogger.log("GeoTeleport: recv pub=\(event.pubkey.prefix(8))… tags=\(tagSummary)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoTeleport: recv pub=\(event.pubkey.prefix(8))… tags=\(tagSummary)", category: .session) // Track teleport tag for participants – only our format ["t", "teleport"] let hasTeleportTag: Bool = event.tags.contains(where: { tag in tag.count >= 2 && tag[0].lowercased() == "t" && tag[1].lowercased() == "teleport" @@ -1611,8 +1601,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if !isSelf { Task { @MainActor in self.teleportedGeo = self.teleportedGeo.union([key]) - SecureLogger.log("GeoTeleport: mark peer teleported key=\(key.prefix(8))… total=\(self.teleportedGeo.count)", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoTeleport: mark peer teleported key=\(key.prefix(8))… total=\(self.teleportedGeo.count)", category: .session) } } } @@ -1676,8 +1665,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // pared back logging: subscribe debug only // Log GeoDM subscribe only when Tor is ready to avoid early noise if TorManager.shared.isReady { - SecureLogger.log("GeoDM: subscribing DMs pub=\(id.publicKeyHex.prefix(8))… sub=\(dmSub)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: subscribing DMs pub=\(id.publicKeyHex.prefix(8))… sub=\(dmSub)", category: .session) } let dmFilter = NostrFilter.giftWrapsFor(pubkey: id.publicKeyHex, since: Date().addingTimeInterval(-TransportConfig.nostrDMSubscribeLookbackSeconds)) NostrRelayManager.shared.subscribe(filter: dmFilter, id: dmSub) { [weak self] giftWrap in @@ -1687,12 +1675,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { self.recordProcessedEvent(giftWrap.id) // Decrypt with per-geohash identity guard let (content, senderPubkey, rumorTs) = try? NostrProtocol.decryptPrivateMessage(giftWrap: giftWrap, recipientIdentity: id) else { - SecureLogger.log("GeoDM: failed decrypt giftWrap id=\(giftWrap.id.prefix(8))…", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoDM: failed decrypt giftWrap id=\(giftWrap.id.prefix(8))…", category: .session) return } - SecureLogger.log("GeoDM: decrypted gift-wrap id=\(giftWrap.id.prefix(16))... from=\(senderPubkey.prefix(8))...", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: decrypted gift-wrap id=\(giftWrap.id.prefix(16))... from=\(senderPubkey.prefix(8))...", category: .session) guard content.hasPrefix("bitchat1:") else { return } guard let packetData = Self.base64URLDecode(String(content.dropFirst("bitchat1:".count))), let packet = BitchatPacket.from(packetData) else { return } @@ -1705,8 +1691,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { case .privateMessage: guard let pm = PrivateMessagePacket.decode(from: noisePayload.data) else { return } let messageId = pm.messageID - SecureLogger.log("GeoDM: recv PM <- sender=\(senderPubkey.prefix(8))… mid=\(messageId.prefix(8))…", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoDM: recv PM <- sender=\(senderPubkey.prefix(8))… mid=\(messageId.prefix(8))…", category: .session) // Send delivery ACK immediately (even if duplicate), once per messageID if !self.sentGeoDeliveryAcks.contains(messageId) { let nostrTransport = NostrTransport() @@ -1769,11 +1754,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let idx = self.privateChats[convKey]?.firstIndex(where: { $0.id == messageID }) { self.privateChats[convKey]?[idx].deliveryStatus = .delivered(to: self.displayNameForNostrPubkey(senderPubkey), at: Date()) self.objectWillChange.send() - SecureLogger.log("GeoDM: recv DELIVERED for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoDM: recv DELIVERED for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", category: .session) } else { - SecureLogger.log("GeoDM: delivered ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoDM: delivered ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", category: .session) } } case .readReceipt: @@ -1781,11 +1764,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let idx = self.privateChats[convKey]?.firstIndex(where: { $0.id == messageID }) { self.privateChats[convKey]?[idx].deliveryStatus = .read(by: self.displayNameForNostrPubkey(senderPubkey), at: Date()) self.objectWillChange.send() - SecureLogger.log("GeoDM: recv READ for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", - category: SecureLogger.session, level: .info) + SecureLogger.info("GeoDM: recv READ for mid=\(messageID.prefix(8))… from=\(senderPubkey.prefix(8))…", category: .session) } else { - SecureLogger.log("GeoDM: read ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("GeoDM: read ack for unknown mid=\(messageID.prefix(8))… conv=\(convKey)", category: .session) } } default: @@ -2152,8 +2133,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } return } - SecureLogger.log("GeoDM: local send mid=\(messageID.prefix(8))… to=\(recipientHex.prefix(8))… conv=\(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: local send mid=\(messageID.prefix(8))… to=\(recipientHex.prefix(8))… conv=\(peerID)", category: .session) let nostrTransport = NostrTransport() nostrTransport.senderPeerID = meshService.myPeerID nostrTransport.sendPrivateMessageGeohash(content: content, toRecipientHex: recipientHex, from: id, messageID: messageID) @@ -2473,16 +2453,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // If any temp peer ID had unread messages, mark the consolidated peer as unread if hadUnreadTemp { unreadPrivateMessages.insert(peerID) - SecureLogger.log("📬 Transferred unread status from temp peer IDs to \(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📬 Transferred unread status from temp peer IDs to \(peerID)", category: .session) } if consolidatedCount > 0 { // Sort by timestamp privateChats[peerID]?.sort { $0.timestamp < $1.timestamp } - SecureLogger.log("📥 Consolidated \(consolidatedCount) Nostr messages from temporary peer IDs to \(peerNickname)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📥 Consolidated \(consolidatedCount) Nostr messages from temporary peer IDs to \(peerNickname)", category: .session) } } @@ -2496,8 +2474,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { break } } else { - SecureLogger.log("GeoDM: skipping mesh handshake for virtual peerID=\(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: skipping mesh handshake for virtual peerID=\(peerID)", category: .session) } // Delegate to private chat manager but add already-acked messages first @@ -2577,8 +2554,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @objc private func handleNostrReadReceipt(_ notification: Notification) { guard let receipt = notification.userInfo?["receipt"] as? ReadReceipt else { return } - SecureLogger.log("📖 Handling read receipt for message \(receipt.originalMessageID) from Nostr", - category: SecureLogger.session, level: .info) + SecureLogger.info("📖 Handling read receipt for message \(receipt.originalMessageID) from Nostr", category: .session) // Process the read receipt through the same flow as Bluetooth read receipts didReceiveReadReceipt(receipt) @@ -2603,8 +2579,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // If we have a private chat open with the old peer ID, update it to the new one if selectedPrivateChatPeer == oldPeerID { - SecureLogger.log("📱 Updating private chat peer ID due to key change: \(oldPeerID) -> \(newPeerID)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📱 Updating private chat peer ID due to key change: \(oldPeerID) -> \(newPeerID)", category: .session) // Transfer private chat messages to new peer ID if let messages = privateChats[oldPeerID] { @@ -2635,8 +2610,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } else { // Even if the chat isn't open, migrate any existing private chat data if let messages = privateChats[oldPeerID] { - SecureLogger.log("📱 Migrating private chat messages from \(oldPeerID) to \(newPeerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📱 Migrating private chat messages from \(oldPeerID) to \(newPeerID)", category: .session) var chats = privateChats chats[newPeerID] = messages chats.removeValue(forKey: oldPeerID) @@ -2742,7 +2716,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { messageRouter.sendPrivate(screenshotMessage, to: peerID, recipientNickname: peerNickname, messageID: UUID().uuidString) default: // Don't send screenshot notification if no session exists - SecureLogger.log("Skipping screenshot notification to \(peerID) - no established session", category: SecureLogger.security, level: .debug) + SecureLogger.debug("Skipping screenshot notification to \(peerID) - no established session", category: .security) } } @@ -2783,14 +2757,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { ) let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5) if targetRelays.isEmpty { - SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning) + SecureLogger.warning("Geo: no geohash relays available for \(ch.geohash); not sending", category: .session) } else { NostrRelayManager.shared.sendEvent(event, to: targetRelays) } // Track ourselves as active participant self.recordGeoParticipant(pubkeyHex: identity.publicKeyHex) } catch { - SecureLogger.log("❌ Failed to send geohash screenshot message: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to send geohash screenshot message: \(error)", category: .session) self.addSystemMessage("failed to send to location channel") } } @@ -2848,8 +2822,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Search for the current peer ID with the same nickname for (currentPeerID, currentNickname) in meshService.getPeerNicknames() { if currentNickname == peerNickname { - SecureLogger.log("📖 Resolved updated peer ID for read receipt: \(peerID) -> \(currentPeerID)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📖 Resolved updated peer ID for read receipt: \(peerID) -> \(currentPeerID)", category: .session) actualPeerID = currentPeerID break } @@ -2877,8 +2850,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let messages = privateChats[peerID] ?? [] for message in messages where message.senderPeerID == peerID && !message.isRelay { if !sentReadReceipts.contains(message.id) { - SecureLogger.log("GeoDM: sending READ for mid=\(message.id.prefix(8))… to=\(recipientHex.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("GeoDM: sending READ for mid=\(message.id.prefix(8))… to=\(recipientHex.prefix(8))…", category: .session) let nostrTransport = NostrTransport() nostrTransport.senderPeerID = meshService.myPeerID nostrTransport.sendReadReceiptGeohash(message.id, toRecipientHex: recipientHex, from: id) @@ -4322,14 +4294,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { verifiedFingerprints = SecureIdentityStateManager.shared.getVerifiedFingerprints() // Log snapshot for debugging persistence let sample = Array(verifiedFingerprints.prefix(TransportConfig.uiFingerprintSampleCount)).map { $0.prefix(8) }.joined(separator: ", ") - SecureLogger.log("🔐 Verified loaded: \(verifiedFingerprints.count) [\(sample)]", category: SecureLogger.security, level: .info) + SecureLogger.info("🔐 Verified loaded: \(verifiedFingerprints.count) [\(sample)]", category: .security) // Also log any offline favorites and whether we consider them verified let offlineFavorites = unifiedPeerService.favorites.filter { !$0.isConnected } for fav in offlineFavorites { let fp = unifiedPeerService.getFingerprint(for: fav.id) let isVer = fp.flatMap { verifiedFingerprints.contains($0) } ?? false let fpShort = fp?.prefix(8) ?? "nil" - SecureLogger.log("⭐️ Favorite offline: \(fav.nickname) fp=\(fpShort) verified=\(isVer)", category: SecureLogger.security, level: .info) + SecureLogger.info("⭐️ Favorite offline: \(fav.nickname) fp=\(fpShort) verified=\(isVer)", category: .security) } // Invalidate cached encryption statuses so offline favorites can show verified badges immediately invalidateEncryptionCache() @@ -4345,7 +4317,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { DispatchQueue.main.async { guard let self = self else { return } - SecureLogger.log("🔐 Authenticated: \(peerID)", category: SecureLogger.security, level: .debug) + SecureLogger.debug("🔐 Authenticated: \(peerID)", category: .security) // Update encryption status if self.verifiedFingerprints.contains(fingerprint) { @@ -4364,8 +4336,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let keyData = self.meshService.getNoiseService().getPeerPublicKeyData(peerID) { let stable = keyData.hexEncodedString() self.shortIDToNoiseKey[peerID] = stable - SecureLogger.log("🗺️ Mapped short peerID to Noise key for header continuity: \(peerID) -> \(stable.prefix(8))…", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🗺️ Mapped short peerID to Noise key for header continuity: \(peerID) -> \(stable.prefix(8))…", category: .session) } // If a QR verification is pending but not sent yet, send it now that session is authenticated @@ -4373,7 +4344,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { self.meshService.sendVerifyChallenge(to: peerID, noiseKeyHex: pending.noiseKeyHex, nonceA: pending.nonceA) pending.sent = true self.pendingQRVerifications[peerID] = pending - SecureLogger.log("📤 Sent deferred verify challenge to \(peerID) after handshake", category: SecureLogger.security, level: .debug) + SecureLogger.debug("📤 Sent deferred verify challenge to \(peerID) after handshake", category: .security) } // Schedule UI update @@ -4519,7 +4490,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { pendingQRVerifications.removeValue(forKey: peerID) if let fp = getFingerprint(for: peerID) { let short = fp.prefix(8) - SecureLogger.log("🔐 Marking verified fingerprint: \(short)", category: SecureLogger.security, level: .info) + SecureLogger.info("🔐 Marking verified fingerprint: \(short)", category: .security) SecureIdentityStateManager.shared.setVerified(fingerprint: fp, verified: true) SecureIdentityStateManager.shared.forceSave() verifiedFingerprints.insert(fp) @@ -4605,7 +4576,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // MARK: - Peer Connection Events func didConnectToPeer(_ peerID: String) { - SecureLogger.log("🤝 Peer connected: \(peerID)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("🤝 Peer connected: \(peerID)", category: .session) // Handle all main actor work async Task { @MainActor in @@ -4622,8 +4593,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Resend favorite notification with our Nostr key after a short delay try? await Task.sleep(nanoseconds: TransportConfig.uiAsyncMediumSleepNs) // 0.5 seconds meshService.sendFavoriteNotification(to: peerID, isFavorite: true) - SecureLogger.log("📤 Resent favorite notification to reconnected peer \(peerID)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Resent favorite notification to reconnected peer \(peerID)", category: .session) } // Force UI refresh @@ -4643,7 +4613,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } func didDisconnectFromPeer(_ peerID: String) { - SecureLogger.log("👋 Peer disconnected: \(peerID)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("👋 Peer disconnected: \(peerID)", category: .session) // Remove ephemeral session from identity manager SecureIdentityStateManager.shared.removeEphemeralSession(peerID: peerID) @@ -4738,8 +4708,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { self.lastNetworkNotificationTime = Date() self.recentlySeenPeers = currentPeerSet NotificationService.shared.sendNetworkAvailableNotification(peerCount: meshPeers.count) - SecureLogger.log("👥 Sent bitchatters nearby notification for \(meshPeers.count) mesh peers", - category: SecureLogger.session, level: .info) + SecureLogger.info("👥 Sent bitchatters nearby notification for \(meshPeers.count) mesh peers", category: .session) } } else { // No peers — immediately reset to allow next rising-edge to notify @@ -4749,7 +4718,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { self.networkResetTimer?.invalidate() self.networkResetTimer = nil } - SecureLogger.log("⏳ Mesh empty — reset network notification state", category: SecureLogger.session, level: .debug) + SecureLogger.debug("⏳ Mesh empty — reset network notification state", category: .session) } // Register ephemeral sessions for all connected peers @@ -4811,8 +4780,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } if !idsToRemove.isEmpty { - SecureLogger.log("🧹 Cleaned up \(idsToRemove.count) stale unread peer IDs", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🧹 Cleaned up \(idsToRemove.count) stale unread peer IDs", category: .session) } } @@ -4842,8 +4810,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let removedCount = oldCount - sentReadReceipts.count if removedCount > 0 { - SecureLogger.log("🧹 Cleaned up \(removedCount) old read receipts", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🧹 Cleaned up \(removedCount) old read receipts", category: .session) } } @@ -4891,7 +4858,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { nostrPublicKey = data.hexEncodedString() } } catch { - SecureLogger.log("Failed to decode Nostr npub: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to decode Nostr npub: \(error)", category: .session) } } @@ -5032,7 +4999,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { NostrRelayManager.shared.sendEvent(event, to: targetRelays) } } catch { - SecureLogger.log("❌ Failed to send geohash raw message: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("❌ Failed to send geohash raw message: \(error)", category: .session) } } return @@ -5048,12 +5015,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor private func setupNostrMessageHandling() { guard let currentIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { - SecureLogger.log("⚠️ No Nostr identity available for message handling", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ No Nostr identity available for message handling", category: .session) return } - SecureLogger.log("🔑 Setting up Nostr subscription for pubkey: \(currentIdentity.publicKeyHex.prefix(16))...", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("🔑 Setting up Nostr subscription for pubkey: \(currentIdentity.publicKeyHex.prefix(16))...", category: .session) // Subscribe to Nostr messages let filter = NostrFilter.giftWrapsFor( @@ -5093,19 +5059,19 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Expect embedded BitChat packet content guard content.hasPrefix("bitchat1:") else { - SecureLogger.log("Ignoring non-embedded Nostr DM content", category: SecureLogger.session, level: .debug) + SecureLogger.debug("Ignoring non-embedded Nostr DM content", category: .session) return } guard let packetData = Self.base64URLDecode(String(content.dropFirst("bitchat1:".count))), let packet = BitchatPacket.from(packetData) else { - SecureLogger.log("Failed to decode embedded BitChat packet from Nostr DM", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to decode embedded BitChat packet from Nostr DM", category: .session) return } // Only process typed noiseEncrypted envelope for private messages/receipts guard packet.type == MessageType.noiseEncrypted.rawValue else { - SecureLogger.log("Unsupported embedded packet type: \(packet.type)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("Unsupported embedded packet type: \(packet.type)", category: .session) return } @@ -5119,7 +5085,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Parse plaintext typed payload guard let noisePayload = NoisePayload.decode(packet.payload) else { - SecureLogger.log("Failed to parse embedded NoisePayload", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to parse embedded NoisePayload", category: .session) return } @@ -5215,14 +5181,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Send delivery ack via Nostr embedded if !wasReadBefore { if let key = actualSenderNoiseKey { - SecureLogger.log("Sending DELIVERED ack for \(messageId.prefix(8))… via router", category: SecureLogger.session, level: .debug) + SecureLogger.debug("Sending DELIVERED ack for \(messageId.prefix(8))… via router", category: .session) messageRouter.sendDeliveryAck(messageId, to: key.hexEncodedString()) } else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() { // Fallback: no Noise mapping yet — send directly to sender's Nostr pubkey let nt = NostrTransport() nt.senderPeerID = meshService.myPeerID nt.sendDeliveryAckGeohash(for: messageId, toRecipientHex: senderPubkey, from: id) - SecureLogger.log("Sent DELIVERED ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(messageId.prefix(8))…", category: SecureLogger.session, level: .debug) + SecureLogger.debug("Sent DELIVERED ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(messageId.prefix(8))…", category: .session) } } @@ -5237,7 +5203,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if !sentReadReceipts.contains(messageId) { if let key = actualSenderNoiseKey { let receipt = ReadReceipt(originalMessageID: messageId, readerID: meshService.myPeerID, readerNickname: nickname) - SecureLogger.log("Viewing chat; sending READ ack for \(messageId.prefix(8))… via router", category: SecureLogger.session, level: .debug) + SecureLogger.debug("Viewing chat; sending READ ack for \(messageId.prefix(8))… via router", category: .session) messageRouter.sendReadReceipt(receipt, to: key.hexEncodedString()) sentReadReceipts.insert(messageId) } else if let id = try? NostrIdentityBridge.getCurrentNostrIdentity() { @@ -5245,7 +5211,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { nt.senderPeerID = meshService.myPeerID nt.sendReadReceiptGeohash(messageId, toRecipientHex: senderPubkey, from: id) sentReadReceipts.insert(messageId) - SecureLogger.log("Viewing chat; sent READ ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(messageId.prefix(8))…", category: SecureLogger.session, level: .debug) + SecureLogger.debug("Viewing chat; sent READ ack directly to Nostr pub=\(senderPubkey.prefix(8))… for mid=\(messageId.prefix(8))…", category: .session) } } } else { @@ -5290,7 +5256,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } } catch { - SecureLogger.log("Failed to decrypt Nostr message: \(error)", category: SecureLogger.session, level: .error) + SecureLogger.error("Failed to decrypt Nostr message: \(error)", category: .session) } } @@ -5311,7 +5277,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Parse ACK format: "ACK:TYPE:MESSAGE_ID" let parts = content.split(separator: ":", maxSplits: 2) guard parts.count >= 3 else { - SecureLogger.log("⚠️ Invalid ACK format: \(content)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Invalid ACK format: \(content)", category: .session) return } @@ -5326,8 +5292,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } processedNostrAcks.insert(ackKey) - SecureLogger.log("📨 Received \(ackType) ACK for message \(messageId.prefix(16))... from \(senderPubkey.prefix(16))...", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("📨 Received \(ackType) ACK for message \(messageId.prefix(16))... from \(senderPubkey.prefix(16))...", category: .session) // Verify the sender has a valid Noise key guard findNoiseKey(for: senderPubkey) != nil else { @@ -5346,12 +5311,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { case "READ": privateChats[chatPeerID]?[index].deliveryStatus = .read(by: "recipient", at: Date()) default: - SecureLogger.log("⚠️ Unknown ACK type: \(ackType)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Unknown ACK type: \(ackType)", category: .session) } messageFound = true - SecureLogger.log("✅ Updated message \(messageId.prefix(16))... status to \(ackType) in chat \(chatPeerID.prefix(16))...", - category: SecureLogger.session, level: .info) + SecureLogger.info("✅ Updated message \(messageId.prefix(16))... status to \(ackType) in chat \(chatPeerID.prefix(16))...", category: .session) // Don't break - continue to update in all chats where this message exists } } @@ -5359,8 +5323,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if messageFound { objectWillChange.send() } else { - SecureLogger.log("⚠️ Could not find message \(messageId) to update status from ACK", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Could not find message \(messageId) to update status from ACK", category: .session) } } @@ -5386,8 +5349,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { var nostrPubkey: String? = nil if parts.count > 1 { nostrPubkey = String(parts[1]) - SecureLogger.log("📝 Received Nostr npub in favorite notification: \(nostrPubkey ?? "none")", - category: SecureLogger.session, level: .info) + SecureLogger.info("📝 Received Nostr npub in favorite notification: \(nostrPubkey ?? "none")", category: .session) } // Get the noise public key for this peer @@ -5405,8 +5367,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } guard let finalNoiseKey = noiseKey else { - SecureLogger.log("⚠️ Cannot get Noise key for peer \(peerID)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Cannot get Noise key for peer \(peerID)", category: .session) return } @@ -5420,8 +5381,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // If they favorited us and provided their Nostr key, ensure it's stored if isFavorite && nostrPubkey != nil { - SecureLogger.log("💾 Storing Nostr key association for \(senderNickname): \(nostrPubkey!.prefix(16))...", - category: SecureLogger.session, level: .info) + SecureLogger.info("💾 Storing Nostr key association for \(senderNickname): \(nostrPubkey!.prefix(16))...", category: .session) } // Show system message @@ -5532,8 +5492,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Not notifying for old message } - SecureLogger.log("📬 Stored Nostr message from unknown sender \(finalSenderNickname) in temporary peer \(tempPeerID)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📬 Stored Nostr message from unknown sender \(finalSenderNickname) in temporary peer \(tempPeerID)", category: .session) } @MainActor @@ -5545,16 +5504,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } else { // Try to convert hex to npub guard let pubkeyData = Data(hexString: nostrPubkey) else { - SecureLogger.log("⚠️ Invalid hex public key format: \(nostrPubkey.prefix(16))...", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Invalid hex public key format: \(nostrPubkey.prefix(16))...", category: .session) return nil } do { npubToMatch = try Bech32.encode(hrp: "npub", data: pubkeyData) } catch { - SecureLogger.log("⚠️ Failed to convert hex to npub: \(error)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Failed to convert hex to npub: \(error)", category: .session) return nil } } @@ -5564,22 +5521,19 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let storedNostrKey = relationship.peerNostrPublicKey { // Compare npub format if storedNostrKey == npubToMatch { - // SecureLogger.log("✅ Found Noise key for Nostr sender (npub match)", - // category: SecureLogger.session, level: .debug) + // SecureLogger.debug("✅ Found Noise key for Nostr sender (npub match)", category: .session) return noiseKey } // Also try hex comparison if stored value is hex if !storedNostrKey.hasPrefix("npub") && storedNostrKey == nostrPubkey { - SecureLogger.log("✅ Found Noise key for Nostr sender (hex match)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("✅ Found Noise key for Nostr sender (hex match)", category: .session) return noiseKey } } } - SecureLogger.log("⚠️ No matching Noise key found for Nostr pubkey: \(nostrPubkey.prefix(16))... (tried npub: \(npubToMatch.prefix(16))...)", - category: SecureLogger.session, level: .debug) + SecureLogger.debug("⚠️ No matching Noise key found for Nostr pubkey: \(nostrPubkey.prefix(16))... (tried npub: \(npubToMatch.prefix(16))...)", category: .session) return nil } @@ -5607,13 +5561,13 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Try mesh first for connected peers if meshService.isPeerConnected(peerID) { messageRouter.sendFavoriteNotification(to: peerID, isFavorite: isFavorite) - SecureLogger.log("📤 Sent favorite notification via BLE to \(peerID)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📤 Sent favorite notification via BLE to \(peerID)", category: .session) } else if let key = noiseKey { // Send via Nostr for offline peers (using router) let recipientPeerID = key.hexEncodedString() messageRouter.sendFavoriteNotification(to: recipientPeerID, isFavorite: isFavorite) } else { - SecureLogger.log("⚠️ Cannot send favorite notification - peer not connected and no Nostr pubkey", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Cannot send favorite notification - peer not connected and no Nostr pubkey", category: .session) } } @@ -5703,12 +5657,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { oldPeerIDsToRemove.append(oldPeerID) } else { // Keep old messages in original location but don't show in UI - SecureLogger.log("📦 Partially migrating \(recentMessages.count) of \(messages.count) messages from \(oldPeerID)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📦 Partially migrating \(recentMessages.count) of \(messages.count) messages from \(oldPeerID)", category: .session) } - SecureLogger.log("📦 Migrating \(recentMessages.count) recent messages from old peer ID \(oldPeerID) to \(peerID) (fingerprint match)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📦 Migrating \(recentMessages.count) recent messages from old peer ID \(oldPeerID) to \(peerID) (fingerprint match)", category: .session) } else if currentFingerprint == nil || oldFingerprint == nil { // Check if this chat contains messages with this sender by nickname let isRelevantChat = recentMessages.contains { msg in @@ -5724,8 +5676,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { oldPeerIDsToRemove.append(oldPeerID) } - SecureLogger.log("📦 Migrating \(recentMessages.count) recent messages from old peer ID \(oldPeerID) to \(peerID) (nickname match)", - category: SecureLogger.session, level: .warning) + SecureLogger.warning("📦 Migrating \(recentMessages.count) recent messages from old peer ID \(oldPeerID) to \(peerID) (nickname match)", category: .session) } } } @@ -5764,8 +5715,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Update selectedPrivateChatPeer if it was pointing to an old ID if needsSelectedUpdate { selectedPrivateChatPeer = peerID - SecureLogger.log("📱 Updated selectedPrivateChatPeer from old ID to \(peerID) during migration", - category: SecureLogger.session, level: .info) + SecureLogger.info("📱 Updated selectedPrivateChatPeer from old ID to \(peerID) during migration", category: .session) } } } @@ -5774,11 +5724,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { /// Handle incoming private message @MainActor private func handlePrivateMessage(_ message: BitchatMessage) { - SecureLogger.log("📥 handlePrivateMessage called for message from \(message.sender)", category: SecureLogger.session, level: .debug) + SecureLogger.debug("📥 handlePrivateMessage called for message from \(message.sender)", category: .session) let senderPeerID = message.senderPeerID ?? getPeerIDForNickname(message.sender) guard let peerID = senderPeerID else { - SecureLogger.log("⚠️ Could not get peer ID for sender \(message.sender)", category: SecureLogger.session, level: .warning) + SecureLogger.warning("⚠️ Could not get peer ID for sender \(message.sender)", category: .session) return } @@ -5818,8 +5768,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Clean up the stable key storage to avoid duplication privateChats.removeValue(forKey: stableKeyHex) - SecureLogger.log("📥 Consolidated \(nostrMessages.count) Nostr messages from stable key to ephemeral peer \(peerID)", - category: SecureLogger.session, level: .info) + SecureLogger.info("📥 Consolidated \(nostrMessages.count) Nostr messages from stable key to ephemeral peer \(peerID)", category: .session) } } } @@ -6086,8 +6035,7 @@ private func checkForMentions(_ message: BitchatMessage) { let isMentioned = (message.mentions?.contains { myTokens.contains($0) } ?? false) if isMentioned && message.sender != nickname { - SecureLogger.log("🔔 Mention from \(message.sender)", - category: SecureLogger.session, level: .info) + SecureLogger.info("🔔 Mention from \(message.sender)", category: .session) NotificationService.shared.sendMentionNotification(from: message.sender, message: message.content) } }