From 2bd2751a6a092069e48d4eb96b55b867eba15c4f Mon Sep 17 00:00:00 2001 From: CC Date: Wed, 10 Jun 2026 13:57:03 +0200 Subject: [PATCH] cleanup logging --- .../android/mesh/BluetoothMeshService.kt | 72 +------ .../mesh/BluetoothPacketBroadcaster.kt | 65 +----- .../bitchat/android/mesh/MessageHandler.kt | 47 +---- .../bitchat/android/mesh/PacketProcessor.kt | 62 +----- .../android/mesh/PacketRelayManager.kt | 27 +-- .../bitchat/android/mesh/SecurityManager.kt | 140 +++++++++---- .../android/noise/NoiseChannelEncryption.kt | 4 - .../android/noise/NoiseEncryptionService.kt | 14 +- .../com/bitchat/android/noise/NoiseSession.kt | 84 +------- .../android/noise/NoiseSessionManager.kt | 23 +-- .../protocol/HandshakeState.java | 30 --- .../protocol/SymmetricState.java | 48 ----- .../android/ui/debug/DebugSettingsManager.kt | 187 +++++++++--------- .../android/mesh/SecurityManagerTest.kt | 76 +++++++ 14 files changed, 300 insertions(+), 579 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index 474c04a5..629af1c3 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -78,7 +78,6 @@ class BluetoothMeshService(private val context: Context) { VerificationService.configure(encryptionService) setupDelegates() messageHandler.packetProcessor = packetProcessor - //startPeriodicDebugLogging() // Initialize sync manager (needs serviceScope) gossipSyncManager = GossipSyncManager( @@ -117,29 +116,6 @@ class BluetoothMeshService(private val context: Context) { peerManager.isPeerDirectlyConnected = { peerID -> connectionManager.addressPeerMap.containsValue(peerID) } - - Log.d(TAG, "Delegates set up; GossipSyncManager initialized") - } - - /** - * Start periodic debug logging every 10 seconds - */ - private fun startPeriodicDebugLogging() { - serviceScope.launch { - Log.d(TAG, "Starting periodic debug logging loop") - while (isActive) { - try { - delay(10000) // 10 seconds - if (isActive) { // Double-check before logging - val debugInfo = getDebugStatus() - Log.d(TAG, "=== PERIODIC DEBUG STATUS ===\n$debugInfo\n=== END DEBUG STATUS ===") - } - } catch (e: Exception) { - Log.e(TAG, "Error in periodic debug logging: ${e.message}") - } - } - Log.d(TAG, "Periodic debug logging loop ended (isActive=$isActive)") - } } /** @@ -147,7 +123,6 @@ class BluetoothMeshService(private val context: Context) { */ private fun sendPeriodicBroadcastAnnounce() { serviceScope.launch { - Log.d(TAG, "Starting periodic announce loop") while (isActive) { try { delay(30000) // 30 seconds @@ -156,7 +131,6 @@ class BluetoothMeshService(private val context: Context) { Log.e(TAG, "Error in periodic broadcast announce: ${e.message}") } } - Log.d(TAG, "Periodic announce loop ended (isActive=$isActive)") } } @@ -164,7 +138,6 @@ class BluetoothMeshService(private val context: Context) { * Setup delegate connections between components */ private fun setupDelegates() { - Log.d(TAG, "Setting up component delegates") // Provide nickname resolver to BLE broadcaster and debug manager try { val resolver: (String) -> String? = { pid -> peerManager.getPeerNickname(pid) } @@ -187,7 +160,6 @@ class BluetoothMeshService(private val context: Context) { // Also drop any Noise session state for this peer when they go offline try { encryptionService.removePeer(peerID) - Log.d(TAG, "Removed Noise session for offline peer $peerID") } catch (e: Exception) { Log.w(TAG, "Failed to remove Noise session for $peerID: ${e.message}") } @@ -199,7 +171,6 @@ class BluetoothMeshService(private val context: Context) { override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) { // Send announcement and cached messages after key exchange serviceScope.launch { - Log.d(TAG, "Key exchange completed with $peerID; sending follow-ups") delay(100) sendAnnouncementToPeer(peerID) @@ -222,7 +193,6 @@ class BluetoothMeshService(private val context: Context) { // Sign the handshake response val signedPacket = signPacketBeforeBroadcast(responsePacket) connectionManager.broadcastPacket(RoutedPacket(signedPacket)) - Log.d(TAG, "Sent Noise handshake response to $peerID (${response.size} bytes)") } override fun getPeerInfo(peerID: String): PeerInfo? { @@ -336,7 +306,6 @@ class BluetoothMeshService(private val context: Context) { // Sign the handshake packet before broadcasting val signedPacket = signPacketBeforeBroadcast(packet) connectionManager.broadcastPacket(RoutedPacket(signedPacket)) - Log.d(TAG, "Initiated Noise handshake with $peerID (${handshakeData.size} bytes)") } else { Log.w(TAG, "Failed to generate Noise handshake data for $peerID") } @@ -358,12 +327,11 @@ class BluetoothMeshService(private val context: Context) { override fun updatePeerIDBinding(newPeerID: String, nickname: String, publicKey: ByteArray, previousPeerID: String?) { - Log.d(TAG, "Updating peer ID binding: $newPeerID (was: $previousPeerID) with nickname: $nickname and public key: ${publicKey.toHexString().take(16)}...") // Update peer mapping in the PeerManager for peer ID rotation support peerManager.addOrUpdatePeer(newPeerID, nickname) // Store fingerprint for the peer via centralized fingerprint manager - val fingerprint = peerManager.storeFingerprintForPeer(newPeerID, publicKey) + peerManager.storeFingerprintForPeer(newPeerID, publicKey) // Index existing Nostr mapping by the new peerID if we have it try { @@ -376,8 +344,6 @@ class BluetoothMeshService(private val context: Context) { previousPeerID?.let { oldPeerID -> peerManager.removePeer(oldPeerID) } - - Log.d(TAG, "Updated peer ID binding: $newPeerID (was: $previousPeerID), fingerprint: ${fingerprint.take(16)}...") } // Message operations @@ -488,7 +454,6 @@ class BluetoothMeshService(private val context: Context) { if (isDirect) { // Bind or rebind this device address to the announcing peer connectionManager.addressPeerMap[deviceAddress] = pid - Log.d(TAG, "Mapped device $deviceAddress to peer $pid (TTL=${routed.packet.ttl})") // Mark as directly connected - refresh UI state try { peerManager.refreshPeerList() } catch (_: Exception) { } @@ -572,7 +537,6 @@ class BluetoothMeshService(private val context: Context) { override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) { // Send initial announcements after services are ready serviceScope.launch { - Log.d(TAG, "Device connected: ${device.address}; scheduling announce") delay(200) sendBroadcastAnnounce() } @@ -587,7 +551,6 @@ class BluetoothMeshService(private val context: Context) { } override fun onDeviceDisconnected(device: android.bluetooth.BluetoothDevice) { - Log.d(TAG, "Device disconnected: ${device.address}") val addr = device.address // Remove mapping and, if that was the last direct path for the peer, clear direct flag val peer = connectionManager.addressPeerMap[addr] @@ -638,10 +601,8 @@ class BluetoothMeshService(private val context: Context) { // Start periodic announcements for peer discovery and connectivity sendPeriodicBroadcastAnnounce() - Log.d(TAG, "Started periodic broadcast announcements (every 30 seconds)") // Start periodic syncs gossipSyncManager.start() - Log.d(TAG, "GossipSyncManager started") } else { Log.e(TAG, "Failed to start Bluetooth services") } @@ -663,14 +624,11 @@ class BluetoothMeshService(private val context: Context) { sendLeaveAnnouncement() serviceScope.launch { - Log.d(TAG, "Stopping subcomponents and cancelling scope...") delay(200) // Give leave message time to send // Stop all components gossipSyncManager.stop() - Log.d(TAG, "GossipSyncManager stopped") connectionManager.stopServices() - Log.d(TAG, "BluetoothConnectionManager stop requested") peerManager.shutdown() fragmentManager.shutdown() securityManager.shutdown() @@ -691,9 +649,6 @@ class BluetoothMeshService(private val context: Context) { */ fun isReusable(): Boolean { val reusable = !terminated && serviceScope.isActive && connectionManager.isReusable() - if (!reusable) { - Log.d(TAG, "isReusable=false (terminated=$terminated, scopeActive=${serviceScope.isActive}, connReusable=${connectionManager.isReusable()})") - } return reusable } @@ -728,13 +683,11 @@ class BluetoothMeshService(private val context: Context) { */ fun sendFileBroadcast(file: com.bitchat.android.model.BitchatFilePacket) { try { - Log.d(TAG, "📤 sendFileBroadcast: name=${file.fileName}, size=${file.fileSize}") val payload = file.encode() if (payload == null) { Log.e(TAG, "❌ Failed to encode file packet in sendFileBroadcast") return } - Log.d(TAG, "📦 Encoded payload: ${payload.size} bytes") serviceScope.launch { val packet = BitchatPacket( version = 2u, // FILE_TRANSFER uses v2 for 4-byte payload length to support large files @@ -763,8 +716,6 @@ class BluetoothMeshService(private val context: Context) { */ fun sendFilePrivate(recipientPeerID: String, file: com.bitchat.android.model.BitchatFilePacket) { try { - Log.d(TAG, "📤 sendFilePrivate (ENCRYPTED): to=$recipientPeerID, name=${file.fileName}, size=${file.fileSize}") - serviceScope.launch { // Check if we have an established Noise session if (encryptionService.hasEstablishedSession(recipientPeerID)) { @@ -775,7 +726,6 @@ class BluetoothMeshService(private val context: Context) { Log.e(TAG, "❌ Failed to encode file packet for private send") return@launch } - Log.d(TAG, "📦 Encoded file TLV: ${filePayload.size} bytes") // Create NoisePayload wrapper (type byte + file TLV data) - same as iOS val noisePayload = com.bitchat.android.model.NoisePayload( @@ -789,7 +739,6 @@ class BluetoothMeshService(private val context: Context) { Log.e(TAG, "❌ Failed to encrypt file for $recipientPeerID") return@launch } - Log.d(TAG, "🔐 Encrypted file payload: ${encrypted.size} bytes") // Create NOISE_ENCRYPTED packet (not FILE_TRANSFER!) val packet = BitchatPacket( @@ -808,7 +757,6 @@ class BluetoothMeshService(private val context: Context) { // Use a stable transferId based on the unencrypted file TLV payload for progress tracking val transferId = sha256Hex(filePayload) connectionManager.broadcastPacket(RoutedPacket(signed, transferId = transferId)) - Log.d(TAG, "✅ Sent encrypted file to $recipientPeerID") } catch (e: Exception) { Log.e(TAG, "❌ Failed to encrypt file for $recipientPeerID: ${e.message}", e) @@ -846,9 +794,7 @@ class BluetoothMeshService(private val context: Context) { serviceScope.launch { val finalMessageID = messageID ?: java.util.UUID.randomUUID().toString() - - Log.d(TAG, "📨 Sending PM to $recipientPeerID: ${content.take(30)}...") - + // Check if we have an established Noise session if (encryptionService.hasEstablishedSession(recipientPeerID)) { try { @@ -888,7 +834,6 @@ class BluetoothMeshService(private val context: Context) { // Sign the packet before broadcasting val signedPacket = signPacketBeforeBroadcast(packet) connectionManager.broadcastPacket(RoutedPacket(signedPacket)) - Log.d(TAG, "📤 Sent encrypted private message to $recipientPeerID (${encrypted.size} bytes)") // FIXED: Don't send didReceiveMessage for our own sent messages // This was causing self-notifications - iOS doesn't do this @@ -899,7 +844,6 @@ class BluetoothMeshService(private val context: Context) { } } else { // Fire and forget - initiate handshake but don't queue exactly like iOS - Log.d(TAG, "🤝 No session with $recipientPeerID, initiating handshake") messageHandler.delegate?.initiateNoiseHandshake(recipientPeerID) // FIXED: Don't send didReceiveMessage for our own sent messages @@ -914,8 +858,6 @@ class BluetoothMeshService(private val context: Context) { */ fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) { serviceScope.launch { - Log.d(TAG, "📖 Sending read receipt for message $messageID to $recipientPeerID") - // Route geohash read receipts via MessageRouter instead of here val geo = runCatching { com.bitchat.android.services.MessageRouter.tryGetInstance() }.getOrNull() val isGeoAlias = try { @@ -931,7 +873,6 @@ class BluetoothMeshService(private val context: Context) { // Avoid duplicate read receipts: check persistent store first val seenStore = try { com.bitchat.android.services.SeenMessageStore.getInstance(context.applicationContext) } catch (_: Exception) { null } if (seenStore?.hasRead(messageID) == true) { - Log.d(TAG, "Skipping read receipt for $messageID - already marked read") return@launch } @@ -959,7 +900,6 @@ class BluetoothMeshService(private val context: Context) { // Sign the packet before broadcasting val signedPacket = signPacketBeforeBroadcast(packet) connectionManager.broadcastPacket(RoutedPacket(signedPacket)) - Log.d(TAG, "📤 Sent read receipt to $recipientPeerID for message $messageID") // Persist as read after successful send try { seenStore?.markRead(messageID) } catch (_: Exception) { } @@ -1007,7 +947,6 @@ class BluetoothMeshService(private val context: Context) { val signedPacket = signPacketBeforeBroadcast(packet) connectionManager.broadcastPacket(RoutedPacket(signedPacket)) - Log.d(TAG, "📤 Sent $label to $recipientPeerID (${payload.data.size} bytes)") } catch (e: Exception) { Log.e(TAG, "Failed to send $label to $recipientPeerID: ${e.message}") } @@ -1018,7 +957,6 @@ class BluetoothMeshService(private val context: Context) { * Send broadcast announce with TLV-encoded identity announcement - exactly like iOS */ fun sendBroadcastAnnounce() { - Log.d(TAG, "Sending broadcast announce") serviceScope.launch { val nickname = try { com.bitchat.android.services.NicknameProvider.getNickname(context, myPeerID) } catch (_: Exception) { myPeerID } @@ -1071,7 +1009,6 @@ class BluetoothMeshService(private val context: Context) { } ?: announcePacket connectionManager.broadcastPacket(RoutedPacket(signedPacket)) - Log.d(TAG, "Sent iOS-compatible signed TLV announce (${tlvPayload.size} bytes)") // Track announce for sync try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { } } @@ -1135,7 +1072,6 @@ class BluetoothMeshService(private val context: Context) { connectionManager.broadcastPacket(RoutedPacket(signedPacket)) peerManager.markPeerAsAnnouncedTo(peerID) - Log.d(TAG, "Sent iOS-compatible signed TLV peer announce to $peerID (${tlvPayload.size} bytes)") // Track announce for sync try { gossipSyncManager.onPublicPacketSeen(signedPacket) } catch (_: Exception) { } @@ -1347,7 +1283,6 @@ class BluetoothMeshService(private val context: Context) { // Exclude first (sender) and last (recipient); only intermediates val intermediates = path.subList(1, path.size - 1) val hopsBytes = intermediates.map { hexStringToByteArray(it) } - Log.d(TAG, "✅ Signed packet type ${packet.type} (route ${hopsBytes.size} hops: $intermediates)") // Attach route and upgrade to v2 (required for HAS_ROUTE flag) packet.copy(route = hopsBytes, version = 2u) } else packet.copy(route = null) @@ -1364,7 +1299,6 @@ class BluetoothMeshService(private val context: Context) { // Sign the packet data using our signing key val signature = encryptionService.signData(packetDataForSigning) if (signature != null) { - Log.d(TAG, "✅ Signed packet type ${packet.type} (signature ${signature.size} bytes)") withRoute.copy(signature = signature) } else { Log.w(TAG, "Failed to sign packet type ${packet.type}, sending unsigned") @@ -1393,7 +1327,6 @@ class BluetoothMeshService(private val context: Context) { securityManager.clearAllData() peerManager.clearAllPeers() peerManager.clearAllFingerprints() - Log.d(TAG, "✅ Cleared all mesh service internal data") } catch (e: Exception) { Log.e(TAG, "❌ Error clearing mesh service internal data: ${e.message}") } @@ -1407,7 +1340,6 @@ class BluetoothMeshService(private val context: Context) { try { // Clear encryption service persistent identity (includes Ed25519 signing keys) encryptionService.clearPersistentIdentity() - Log.d(TAG, "✅ Cleared all encryption data") } catch (e: Exception) { Log.e(TAG, "❌ Error clearing encryption data: ${e.message}") } diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt index 37af030d..59b6ef45 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt @@ -58,9 +58,6 @@ class BluetoothPacketBroadcaster( nicknameResolver = resolver } - /** - * Debug logging helper - can be easily removed/disabled for production - */ private fun logPacketRelay( typeName: String, senderPeerID: String, @@ -74,10 +71,13 @@ class BluetoothPacketBroadcaster( routeInfo: String? = null ) { try { - val fromNick = incomingPeer?.let { nicknameResolver?.invoke(it) } val toNick = toPeer?.let { nicknameResolver?.invoke(it) } val manager = com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() - // Always log outgoing for the actual transmission target + val origin = senderNick?.takeIf { it.isNotBlank() }?.let { "$it ($senderPeerID)" } ?: senderPeerID + val incoming = incomingAddr?.let { "in=$it" } + val relayInfo = listOfNotNull(routeInfo, "origin=$origin", incoming) + .joinToString(" ") + .takeIf { it.isNotBlank() } manager.logOutgoing( packetType = typeName, toPeerID = toPeer, @@ -85,23 +85,8 @@ class BluetoothPacketBroadcaster( toDeviceAddress = toDeviceAddress, previousHopPeerID = incomingPeer, packetVersion = packetVersion, - routeInfo = routeInfo - ) - // Keep the verbose relay message for human readability - manager.logPacketRelayDetailed( - packetType = typeName, - senderPeerID = senderPeerID, - senderNickname = senderNick, - fromPeerID = incomingPeer, - fromNickname = fromNick, - fromDeviceAddress = incomingAddr, - toPeerID = toPeer, - toNickname = toNick, - toDeviceAddress = toDeviceAddress, - ttl = ttl, - isRelay = true, - packetVersion = packetVersion, - routeInfo = routeInfo + routeInfo = relayInfo, + ttl = ttl ) } catch (_: Exception) { // Silently ignore debug logging failures @@ -124,13 +109,8 @@ class BluetoothPacketBroadcaster( private val broadcasterActor = broadcasterScope.actor( capacity = Channel.UNLIMITED ) { - Log.d(TAG, "🎭 Created packet broadcaster actor") - try { - for (request in channel) { - broadcastSinglePacketInternal(request.routed, request.gattServer, request.characteristic) - } - } finally { - Log.d(TAG, "🎭 Packet broadcaster actor terminated") + for (request in channel) { + broadcastSinglePacketInternal(request.routed, request.gattServer, request.characteristic) } } @@ -141,9 +121,6 @@ class BluetoothPacketBroadcaster( ) { val packet = routed.packet val isFile = packet.type == MessageType.FILE_TRANSFER.value - if (isFile) { - Log.d(TAG, "📤 Broadcasting FILE_TRANSFER: ${packet.payload.size} bytes") - } // Prefer caller-provided transferId (e.g., for encrypted media), else derive for FILE_TRANSFER val transferId = routed.transferId ?: (if (isFile) sha256Hex(packet.payload) else null) // Check if we need to fragment @@ -158,10 +135,6 @@ class BluetoothPacketBroadcaster( return } if (fragments.size > 1) { - if (isFile) { - Log.d(TAG, "🔀 File needs ${fragments.size} fragments") - } - Log.d(TAG, "Fragmenting packet into ${fragments.size} fragments") if (transferId != null) { TransferProgressManager.start(transferId, fragments.size) } @@ -219,9 +192,6 @@ class BluetoothPacketBroadcaster( val packet = routed.packet val data = packet.toBinaryData() ?: return false val isFile = packet.type == MessageType.FILE_TRANSFER.value - if (isFile) { - Log.d(TAG, "📤 Broadcasting FILE_TRANSFER: ${packet.payload.size} bytes") - } // Prefer caller-provided transferId (e.g., for encrypted media), else derive for FILE_TRANSFER val transferId = routed.transferId ?: (if (isFile) sha256Hex(packet.payload) else null) if (transferId != null) { @@ -355,7 +325,6 @@ class BluetoothPacketBroadcaster( // If we are the sender and a source route is defined, we must send ONLY to the first hop. if (packet.senderID.toHexString() == myPeerID && !packet.route.isNullOrEmpty()) { val firstHop = packet.route!![0].toHexString() - Log.d(TAG, "Source Routing: Packet has explicit route, attempting to send to first hop: $firstHop") var sent = false @@ -364,7 +333,6 @@ class BluetoothPacketBroadcaster( .firstOrNull { connectionTracker.addressPeerMap[it.address] == firstHop } if (serverTarget != null) { - Log.d(TAG, "Source Routing: sending directly to first hop (server conn) $firstHop: ${serverTarget.address}") if (notifyDevice(serverTarget, data, gattServer, characteristic)) { val toPeer = connectionTracker.addressPeerMap[serverTarget.address] logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, toPeer, serverTarget.address, packet.ttl, packet.version, routeInfo) @@ -378,7 +346,6 @@ class BluetoothPacketBroadcaster( .firstOrNull { connectionTracker.addressPeerMap[it.device.address] == firstHop } if (clientTarget != null) { - Log.d(TAG, "Source Routing: sending directly to first hop (client conn) $firstHop: ${clientTarget.device.address}") if (writeToDeviceConn(clientTarget, data)) { val toPeer = connectionTracker.addressPeerMap[clientTarget.device.address] logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, toPeer, clientTarget.device.address, packet.ttl, packet.version, routeInfo) @@ -401,7 +368,6 @@ class BluetoothPacketBroadcaster( // If found, send directly if (targetDevice != null) { - Log.d(TAG, "Send packet type ${packet.type} directly to target device for recipient $recipientID: ${targetDevice.address}") if (notifyDevice(targetDevice, data, gattServer, characteristic)) { val toPeer = connectionTracker.addressPeerMap[targetDevice.address] logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, toPeer, targetDevice.address, packet.ttl, packet.version, routeInfo) @@ -415,7 +381,6 @@ class BluetoothPacketBroadcaster( // If found, send directly if (targetDeviceConn != null) { - Log.d(TAG, "Send packet type ${packet.type} directly to target client connection for recipient $recipientID: ${targetDeviceConn.device.address}") if (writeToDeviceConn(targetDeviceConn, data)) { val toPeer = connectionTracker.addressPeerMap[targetDeviceConn.device.address] logPacketRelay(typeName, senderPeerID, senderNick, incomingPeer, incomingAddr, toPeer, targetDeviceConn.device.address, packet.ttl, packet.version, routeInfo) @@ -427,19 +392,15 @@ class BluetoothPacketBroadcaster( // Else, continue with broadcasting to all devices val subscribedDevices = connectionTracker.getSubscribedDevices() val connectedDevices = connectionTracker.getConnectedDevices() - - Log.i(TAG, "Broadcasting packet v${packet.version} type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections") val senderID = packet.senderID.toHexString() // Send to server connections (devices connected to our GATT server) subscribedDevices.forEach { device -> if (device.address == routed.relayAddress) { - Log.d(TAG, "Skipping broadcast to client back to relayer: ${device.address}") return@forEach } if (connectionTracker.addressPeerMap[device.address] == senderID) { - Log.d(TAG, "Skipping broadcast to client back to sender: ${device.address}") return@forEach } val sent = notifyDevice(device, data, gattServer, characteristic) @@ -453,11 +414,9 @@ class BluetoothPacketBroadcaster( connectedDevices.values.forEach { deviceConn -> if (deviceConn.isClient && deviceConn.gatt != null && deviceConn.characteristic != null) { if (deviceConn.device.address == routed.relayAddress) { - Log.d(TAG, "Skipping broadcast to server back to relayer: ${deviceConn.device.address}") return@forEach } if (connectionTracker.addressPeerMap[deviceConn.device.address] == senderID) { - Log.d(TAG, "Skipping roadcast to server back to sender: ${deviceConn.device.address}") return@forEach } val sent = writeToDeviceConn(deviceConn, data) @@ -534,14 +493,10 @@ class BluetoothPacketBroadcaster( * Shutdown the broadcaster actor gracefully */ fun shutdown() { - Log.d(TAG, "Shutting down BluetoothPacketBroadcaster actor") - // Close the actor gracefully broadcasterActor.close() // Cancel the broadcaster scope broadcasterScope.cancel() - - Log.d(TAG, "BluetoothPacketBroadcaster shutdown complete") } -} +} diff --git a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt index ecac572b..d46cba7a 100644 --- a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt +++ b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt @@ -39,16 +39,13 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro suspend fun handleNoiseEncrypted(routed: RoutedPacket) { val packet = routed.packet val peerID = routed.peerID ?: "unknown" - - Log.d(TAG, "Processing Noise encrypted message from $peerID (${packet.payload.size} bytes)") - + // Skip our own messages if (peerID == myPeerID) return // Check if this message is for us val recipientID = packet.recipientID?.toHexString() if (recipientID != myPeerID) { - Log.d(TAG, "🔐 Encrypted message not for me (for $recipientID, I am $myPeerID)") return } @@ -72,15 +69,11 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro return } - Log.d(TAG, "🔓 Decrypted NoisePayload type ${noisePayload.type} from $peerID") - when (noisePayload.type) { com.bitchat.android.model.NoisePayloadType.PRIVATE_MESSAGE -> { // Decode TLV private message exactly like iOS val privateMessage = com.bitchat.android.model.PrivateMessagePacket.decode(noisePayload.data) if (privateMessage != null) { - Log.d(TAG, "🔓 Decrypted TLV PM from $peerID: ${privateMessage.content.take(30)}...") - // Handle favorite/unfavorite notifications embedded as PMs val pmContent = privateMessage.content if (pmContent.startsWith("[FAVORITED]") || pmContent.startsWith("[UNFAVORITED]")) { @@ -116,7 +109,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro // Handle encrypted file transfer; generate unique message ID val file = com.bitchat.android.model.BitchatFilePacket.decode(noisePayload.data) if (file != null) { - Log.d(TAG, "🔓 Decrypted encrypted file from $peerID: name='${file.fileName}', size=${file.fileSize}, mime='${file.mimeType}'") val uniqueMsgId = java.util.UUID.randomUUID().toString().uppercase() val savedPath = com.bitchat.android.features.file.FileUtils.saveIncomingFile(appContext, file) val message = BitchatMessage( @@ -130,8 +122,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro recipientNickname = delegate?.getMyNickname(), senderPeerID = peerID ) - - Log.d(TAG, "📄 Saved encrypted incoming file to $savedPath (msgId=$uniqueMsgId)") delegate?.onMessageReceived(message) // Send delivery ACK with generated message ID @@ -144,7 +134,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro com.bitchat.android.model.NoisePayloadType.DELIVERED -> { // Handle delivery ACK exactly like iOS val messageID = String(noisePayload.data, Charsets.UTF_8) - Log.d(TAG, "📬 Delivery ACK received from $peerID for message $messageID") // Simplified: Call delegate with messageID and peerID directly delegate?.onDeliveryAckReceived(messageID, peerID) @@ -153,17 +142,14 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro com.bitchat.android.model.NoisePayloadType.READ_RECEIPT -> { // Handle read receipt exactly like iOS val messageID = String(noisePayload.data, Charsets.UTF_8) - Log.d(TAG, "👁️ Read receipt received from $peerID for message $messageID") // Simplified: Call delegate with messageID and peerID directly delegate?.onReadReceiptReceived(messageID, peerID) } com.bitchat.android.model.NoisePayloadType.VERIFY_CHALLENGE -> { - Log.d(TAG, "🔐 Verify challenge received from $peerID (${noisePayload.data.size} bytes)") delegate?.onVerifyChallengeReceived(peerID, noisePayload.data, packet.timestamp.toLong()) } com.bitchat.android.model.NoisePayloadType.VERIFY_RESPONSE -> { - Log.d(TAG, "🔐 Verify response received from $peerID (${noisePayload.data.size} bytes)") delegate?.onVerifyResponseReceived(peerID, noisePayload.data, packet.timestamp.toLong()) } } @@ -204,7 +190,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro ) delegate?.sendPacket(packet) - Log.d(TAG, "📤 Sent delivery ACK to $senderPeerID for message $messageID") } catch (e: Exception) { Log.e(TAG, "Failed to send delivery ACK to $senderPeerID: ${e.message}") @@ -260,11 +245,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro return false } - // Successfully decoded TLV format exactly like iOS - Log.d(TAG, "✅ Verified announce from $peerID: nickname=${announcement.nickname}, " + - "noisePublicKey=${announcement.noisePublicKey.joinToString("") { "%02x".format(it) }.take(16)}..., " + - "signingPublicKey=${announcement.signingPublicKey.joinToString("") { "%02x".format(it) }.take(16)}...") - // Extract nickname and public keys from TLV data val nickname = announcement.nickname val noisePublicKey = announcement.noisePublicKey @@ -293,8 +273,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro com.bitchat.android.services.meshgraph.MeshGraphService.getInstance() .updateFromAnnouncement(peerID, nickname, neighborsOrNull, packet.timestamp) } catch (_: Exception) { } - - Log.d(TAG, "✅ Processed verified TLV announce: stored identity for $peerID") return isFirstAnnounce } @@ -305,16 +283,13 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro suspend fun handleNoiseHandshake(routed: RoutedPacket) { val packet = routed.packet val peerID = routed.peerID ?: "unknown" - - Log.d(TAG, "Processing Noise handshake from $peerID (${packet.payload.size} bytes)") - + // Skip our own handshake messages if (peerID == myPeerID) return // Check if handshake is addressed to us val recipientID = packet.recipientID?.toHexString() if (recipientID != myPeerID) { - Log.d(TAG, "Handshake not for me (for $recipientID, I am $myPeerID)") return } @@ -323,8 +298,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro val response = delegate?.processNoiseHandshakeMessage(packet.payload, peerID) if (response != null) { - Log.d(TAG, "Generated handshake response for $peerID (${response.size} bytes)") - // Send response using same packet type (simplified iOS approach) val responsePacket = BitchatPacket( version = 1u, @@ -338,13 +311,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro ) delegate?.sendPacket(responsePacket) - Log.d(TAG, "📤 Sent handshake response to $peerID") - } - - // Check if session is now established - val hasSession = delegate?.hasNoiseSession(peerID) ?: false - if (hasSession) { - Log.d(TAG, "✅ Noise session established with $peerID") } } catch (e: Exception) { @@ -361,7 +327,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro if (peerID == myPeerID) return val senderNickname = delegate?.getPeerNickname(peerID) if (senderNickname != null) { - Log.d(TAG, "Received message from $senderNickname") delegate?.updatePeerNickname(peerID, senderNickname) } @@ -396,9 +361,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro val isFileTransfer = com.bitchat.android.protocol.MessageType.fromValue(packet.type) == com.bitchat.android.protocol.MessageType.FILE_TRANSFER val file = com.bitchat.android.model.BitchatFilePacket.decode(packet.payload) if (file != null) { - if (isFileTransfer) { - Log.d(TAG, "📥 FILE_TRANSFER decode success (broadcast): name='${file.fileName}', size=${file.fileSize}, mime='${file.mimeType}', from=${peerID.take(8)}") - } val savedPath = com.bitchat.android.features.file.FileUtils.saveIncomingFile(appContext, file) val message = BitchatMessage( id = PacketIdUtil.computeIdHex(packet).uppercase(), @@ -408,7 +370,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro senderPeerID = peerID, timestamp = Date(packet.timestamp.toLong()) ) - Log.d(TAG, "📄 Saved incoming file to $savedPath") delegate?.onMessageReceived(message) return } else if (isFileTransfer) { @@ -444,9 +405,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro val isFileTransfer = com.bitchat.android.protocol.MessageType.fromValue(packet.type) == com.bitchat.android.protocol.MessageType.FILE_TRANSFER val file = com.bitchat.android.model.BitchatFilePacket.decode(packet.payload) if (file != null) { - if (isFileTransfer) { - Log.d(TAG, "📥 FILE_TRANSFER decode success (private): name='${file.fileName}', size=${file.fileSize}, mime='${file.mimeType}', from=${peerID.take(8)}") - } val savedPath = com.bitchat.android.features.file.FileUtils.saveIncomingFile(appContext, file) val message = BitchatMessage( id = java.util.UUID.randomUUID().toString().uppercase(), @@ -458,7 +416,6 @@ class MessageHandler(private val myPeerID: String, private val appContext: andro isPrivate = true, recipientNickname = delegate?.getMyNickname() ) - Log.d(TAG, "📄 Saved incoming file to $savedPath") delegate?.onMessageReceived(message) return } else if (isFileTransfer) { diff --git a/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt b/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt index fb47f40f..9434b7b0 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt @@ -16,8 +16,7 @@ import kotlinx.coroutines.channels.actor * from the same peer simultaneously, causing session management conflicts. */ class PacketProcessor(private val myPeerID: String) { - private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } } - + companion object { private const val TAG = "PacketProcessor" } @@ -43,18 +42,11 @@ class PacketProcessor(private val myPeerID: String) { private val peerActors = mutableMapOf>() @OptIn(ObsoleteCoroutinesApi::class) - private fun getOrCreateActorForPeer(peerID: String) = processorScope.actor( + private fun getOrCreateActorForPeer() = processorScope.actor( capacity = Channel.UNLIMITED ) { - Log.d(TAG, "🎭 Created packet actor for peer: ${formatPeerForLog(peerID)}") - try { - for (packet in channel) { - Log.d(TAG, "📦 Processing packet type ${packet.packet.type} from ${formatPeerForLog(peerID)} (serialized)") - handleReceivedPacket(packet) - Log.d(TAG, "Completed packet type ${packet.packet.type} from ${formatPeerForLog(peerID)}") - } - } finally { - Log.d(TAG, "🎭 Packet actor for ${formatPeerForLog(peerID)} terminated") + for (packet in channel) { + handleReceivedPacket(packet) } } @@ -71,7 +63,6 @@ class PacketProcessor(private val myPeerID: String) { * SURGICAL FIX: Route to per-peer actor for serialized processing */ fun processPacket(routed: RoutedPacket) { - Log.d(TAG, "processPacket ${routed.packet.type}") val peerID = routed.peerID if (peerID == null) { @@ -80,7 +71,7 @@ class PacketProcessor(private val myPeerID: String) { } // Get or create actor for this peer - val actor = actors.getOrPut(peerID) { getOrCreateActorForPeer(peerID) } + val actor = actors.getOrPut(peerID) { getOrCreateActorForPeer() } // Send packet to peer's dedicated actor for serialized processing processorScope.launch { @@ -125,22 +116,12 @@ class PacketProcessor(private val myPeerID: String) { // Basic validation and security checks if (!delegate?.validatePacketSecurity(packet, peerID)!!) { - Log.d(TAG, "Packet failed security validation from ${formatPeerForLog(peerID)}") return } var validPacket = true val messageType = MessageType.fromValue(packet.type) - Log.d(TAG, "Processing packet type ${messageType} from ${formatPeerForLog(peerID)}") - // Verbose logging to debug manager (and chat via ChatViewModel observer) - try { - val mt = messageType?.name ?: packet.type.toString() - val routeDevice = routed.relayAddress - val nick = delegate?.getPeerNickname(peerID) - debugManager?.logIncomingPacket(peerID, nick, mt, routeDevice) - } catch (_: Exception) { } - - + // Handle public packet types (no address check needed) when (messageType) { MessageType.ANNOUNCE -> handleAnnounce(routed) @@ -161,8 +142,6 @@ class PacketProcessor(private val myPeerID: String) { Log.w(TAG, "Unknown message type: ${packet.type}") } } - } else { - Log.d(TAG, "Private packet type ${messageType} not addressed to us (from: ${formatPeerForLog(peerID)} to ${packet.recipientID?.let { it.joinToString("") { b -> "%02x".format(b) } }}), skipping") } } } @@ -180,8 +159,6 @@ class PacketProcessor(private val myPeerID: String) { * Handle Noise handshake message - SIMPLIFIED iOS-compatible version */ private suspend fun handleNoiseHandshake(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing Noise handshake from ${formatPeerForLog(peerID)}") delegate?.handleNoiseHandshake(routed) } @@ -189,8 +166,6 @@ class PacketProcessor(private val myPeerID: String) { * Handle Noise encrypted transport message */ private suspend fun handleNoiseEncrypted(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing Noise encrypted message from ${formatPeerForLog(peerID)}") delegate?.handleNoiseEncrypted(routed) } @@ -198,8 +173,6 @@ class PacketProcessor(private val myPeerID: String) { * Handle announce message */ private suspend fun handleAnnounce(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing announce from ${formatPeerForLog(peerID)}") delegate?.handleAnnounce(routed) } @@ -207,8 +180,6 @@ class PacketProcessor(private val myPeerID: String) { * Handle regular message */ private suspend fun handleMessage(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing message from ${formatPeerForLog(peerID)}") delegate?.handleMessage(routed) } @@ -216,8 +187,6 @@ class PacketProcessor(private val myPeerID: String) { * Handle leave message */ private suspend fun handleLeave(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing leave from ${formatPeerForLog(peerID)}") delegate?.handleLeave(routed) } @@ -225,12 +194,8 @@ class PacketProcessor(private val myPeerID: String) { * Handle message fragments */ private suspend fun handleFragment(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing fragment from ${formatPeerForLog(peerID)}") - val reassembledPacket = delegate?.handleFragment(routed.packet) if (reassembledPacket != null) { - Log.d(TAG, "Fragment reassembled, processing complete message") handleReceivedPacket(RoutedPacket(reassembledPacket, routed.peerID, routed.relayAddress)) } @@ -241,20 +206,9 @@ class PacketProcessor(private val myPeerID: String) { * Handle REQUEST_SYNC packets (public, TTL=1) */ private suspend fun handleRequestSync(routed: RoutedPacket) { - val peerID = routed.peerID ?: "unknown" - Log.d(TAG, "Processing REQUEST_SYNC from ${formatPeerForLog(peerID)}") delegate?.handleRequestSync(routed) } - /** - * Handle delivery acknowledgment - */ -// private suspend fun handleDeliveryAck(routed: RoutedPacket) { -// val peerID = routed.peerID ?: "unknown" -// Log.d(TAG, "Processing delivery ACK from ${formatPeerForLog(peerID)}") -// delegate?.handleDeliveryAck(routed) -// } - /** * Get debug information */ @@ -278,8 +232,6 @@ class PacketProcessor(private val myPeerID: String) { * Shutdown the processor and all peer actors */ fun shutdown() { - Log.d(TAG, "Shutting down PacketProcessor and ${actors.size} peer actors") - // Close all peer actors gracefully actors.values.forEach { actor -> actor.close() @@ -291,8 +243,6 @@ class PacketProcessor(private val myPeerID: String) { // Cancel the main scope processorScope.cancel() - - Log.d(TAG, "PacketProcessor shutdown complete") } } diff --git a/app/src/main/java/com/bitchat/android/mesh/PacketRelayManager.kt b/app/src/main/java/com/bitchat/android/mesh/PacketRelayManager.kt index a82746c4..cee60a53 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PacketRelayManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PacketRelayManager.kt @@ -15,8 +15,7 @@ import kotlin.random.Random * All packets that aren't specifically addressed to us get processed here. */ class PacketRelayManager(private val myPeerID: String) { - private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } } - + companion object { private const val TAG = "PacketRelayManager" } @@ -40,30 +39,24 @@ class PacketRelayManager(private val myPeerID: String) { suspend fun handlePacketRelay(routed: RoutedPacket) { val packet = routed.packet val peerID = routed.peerID ?: "unknown" - - Log.d(TAG, "Evaluating relay for packet type ${packet.type} from ${peerID} (TTL: ${packet.ttl})") - + // Double-check this packet isn't addressed to us if (isPacketAddressedToMe(packet)) { - Log.d(TAG, "Packet addressed to us, skipping relay") return } // Skip our own packets if (peerID == myPeerID) { - Log.d(TAG, "Packet from ourselves, skipping relay") return } // Check TTL and decrement if (packet.ttl == 0u.toUByte()) { - Log.d(TAG, "TTL expired, not relaying packet") return } // Decrement TTL by 1 val relayPacket = packet.copy(ttl = (packet.ttl - 1u).toUByte()) - Log.d(TAG, "Decremented TTL from ${packet.ttl} to ${relayPacket.ttl}") // Source-based routing: if route is set and includes us, try targeted next-hop forwarding val route = relayPacket.route @@ -88,7 +81,6 @@ class PacketRelayManager(private val myPeerID: String) { if (nextHopIdHex != null) { val success = try { delegate?.sendToPeer(nextHopIdHex, RoutedPacket(relayPacket, peerID, routed.relayAddress)) } catch (_: Exception) { false } ?: false if (success) { - Log.i(TAG, "📦 Source-route relay: ${peerID.take(8)} -> ${nextHopIdHex.take(8)} (type ${'$'}{packet.type}, TTL ${'$'}{relayPacket.ttl})") return } else { Log.w(TAG, "Source-route next hop ${nextHopIdHex.take(8)} not directly connected; falling back to broadcast") @@ -98,11 +90,9 @@ class PacketRelayManager(private val myPeerID: String) { } // Apply relay logic based on packet type and debug switch - val shouldRelay = isRelayEnabled() && shouldRelayPacket(relayPacket, peerID) + val shouldRelay = isRelayEnabled() && shouldRelayPacket(relayPacket) if (shouldRelay) { relayPacket(RoutedPacket(relayPacket, peerID, routed.relayAddress)) - } else { - Log.d(TAG, "Relay decision: NOT relaying packet type ${packet.type}") } } @@ -131,10 +121,9 @@ class PacketRelayManager(private val myPeerID: String) { /** * Determine if we should relay this packet based on type and network conditions */ - private fun shouldRelayPacket(packet: BitchatPacket, fromPeerID: String): Boolean { + private fun shouldRelayPacket(packet: BitchatPacket): Boolean { // Always relay if TTL is high enough (indicates important message) if (packet.ttl >= 4u) { - Log.d(TAG, "High TTL (${packet.ttl}), relaying") return true } @@ -143,7 +132,6 @@ class PacketRelayManager(private val myPeerID: String) { // Small networks always relay to ensure connectivity if (networkSize <= 3) { - Log.d(TAG, "Small network (${networkSize} peers), relaying") return true } @@ -156,17 +144,13 @@ class PacketRelayManager(private val myPeerID: String) { else -> 0.4 // Lowest probability for very large networks } - val shouldRelay = Random.nextDouble() < relayProb - Log.d(TAG, "Network size: ${networkSize}, Relay probability: ${relayProb}, Decision: ${shouldRelay}") - - return shouldRelay + return Random.nextDouble() < relayProb } /** * Actually broadcast the packet for relay */ private fun relayPacket(routed: RoutedPacket) { - Log.d(TAG, "🔄 Relaying packet type ${routed.packet.type} with TTL ${routed.packet.ttl}") delegate?.broadcastPacket(routed) } @@ -186,7 +170,6 @@ class PacketRelayManager(private val myPeerID: String) { * Shutdown the relay manager */ fun shutdown() { - Log.d(TAG, "Shutting down PacketRelayManager") relayScope.cancel() } } diff --git a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt index 5beb3384..de220cb6 100644 --- a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt @@ -5,6 +5,7 @@ import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType import com.bitchat.android.model.RoutedPacket +import com.bitchat.android.ui.debug.DebugSettingsManager import com.bitchat.android.util.toHexString import kotlinx.coroutines.* import java.util.* @@ -23,6 +24,11 @@ class SecurityManager(private val encryptionService: EncryptionService, private private const val CLEANUP_INTERVAL = com.bitchat.android.util.AppConstants.Security.CLEANUP_INTERVAL_MS // 5 minutes private const val MAX_PROCESSED_MESSAGES = com.bitchat.android.util.AppConstants.Security.MAX_PROCESSED_MESSAGES private const val MAX_PROCESSED_KEY_EXCHANGES = com.bitchat.android.util.AppConstants.Security.MAX_PROCESSED_KEY_EXCHANGES + private val SIGNATURE_REQUIRED_TYPES = setOf( + MessageType.ANNOUNCE, + MessageType.MESSAGE, + MessageType.FILE_TRANSFER + ) } // Security tracking @@ -32,6 +38,14 @@ class SecurityManager(private val encryptionService: EncryptionService, private // Delegate for callbacks var delegate: SecurityManagerDelegate? = null + + private val debugManager by lazy { + try { + DebugSettingsManager.getInstance() + } catch (_: Exception) { + null + } + } // Coroutines private val managerScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -46,12 +60,18 @@ class SecurityManager(private val encryptionService: EncryptionService, private fun validatePacket(packet: BitchatPacket, peerID: String): Boolean { // Skip validation for our own packets if (peerID == myPeerID) { - Log.d(TAG, "Skipping validation for our own packet") return false } // Replay attack protection (same 5-minute window as iOS) val currentTime = System.currentTimeMillis() + val timestampResult = validateTimestamp(packet, currentTime) + if (!timestampResult.isValid) { + logValidationFailure(packet, peerID, timestampResult.reason, timestampResult.detail) + Log.w(TAG, "Dropping packet from $peerID: ${timestampResult.reason}") + return false + } + val messageType = MessageType.fromValue(packet.type) // Duplicate detection @@ -65,23 +85,24 @@ class SecurityManager(private val encryptionService: EncryptionService, private packet.ttl >= com.bitchat.android.util.AppConstants.MESSAGE_TTL_HOPS if (!isFreshAnnounce) { - Log.d(TAG, "Dropping duplicate packet: $messageID") + logValidationFailure(packet, peerID, "duplicate packet") return false } - Log.d(TAG, "Allowing duplicate ANNOUNCE from direct neighbor: $messageID") + } + + // Enforce mandatory signature verification + val signatureResult = verifyPacketSignature(packet, peerID) + if (!signatureResult.isValid) { + logValidationFailure(packet, peerID, signatureResult.reason, signatureResult.detail) + Log.w(TAG, "Dropping packet from $peerID: ${signatureResult.reason}") + return false } - // Add to processed messages + // Add to processed messages only after validation succeeds so invalid packets + // cannot poison duplicate detection for a later valid retry. processedMessages.add(messageID) messageTimestamps[messageID] = currentTime - // Enforce mandatory signature verification - if (!verifyPacketSignature(packet, peerID)) { - Log.w(TAG, "Dropping packet from $peerID due to signature verification failure") - return false - } - - Log.d(TAG, "Packet validation passed for $peerID, messageID: $messageID") return true } @@ -95,7 +116,6 @@ class SecurityManager(private val encryptionService: EncryptionService, private // Skip handshakes not addressed to us if (packet.recipientID?.toHexString() != myPeerID) { - Log.d(TAG, "Skipping handshake not addressed to us: $peerID") return false } @@ -106,7 +126,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private // drop the existing session so we can re-establish cleanly. var forcedRehandshake = false if (encryptionService.hasEstablishedSession(peerID)) { - Log.d(TAG, "Received new Noise handshake from $peerID with an existing session. Dropping old session to re-handshake.") + Log.i(TAG, "Re-establishing Noise session with $peerID") try { encryptionService.removePeer(peerID) forcedRehandshake = true @@ -124,10 +144,8 @@ class SecurityManager(private val encryptionService: EncryptionService, private val exchangeKey = "$peerID-${packet.payload.sliceArray(0 until minOf(16, packet.payload.size)).contentHashCode()}" if (!forcedRehandshake && processedKeyExchanges.contains(exchangeKey)) { - Log.d(TAG, "Already processed handshake: $exchangeKey") return false } - Log.d(TAG, "Processing Noise handshake from $peerID (${packet.payload.size} bytes)") processedKeyExchanges.add(exchangeKey) try { @@ -135,13 +153,11 @@ class SecurityManager(private val encryptionService: EncryptionService, private val response = encryptionService.processHandshakeMessage(packet.payload, peerID) if (response != null) { - Log.d(TAG, "Successfully processed Noise handshake from $peerID, sending response") // Send handshake response through delegate delegate?.sendHandshakeResponse(peerID, response) } // Check if session is now established (handshake complete) if (encryptionService.hasEstablishedSession(peerID)) { - Log.d(TAG, "✅ Noise handshake completed with $peerID") delegate?.onKeyExchangeCompleted(peerID, packet.payload) } return true @@ -235,32 +251,33 @@ class SecurityManager(private val encryptionService: EncryptionService, private * Verify packet signature using peer's signing public key * Returns true only if signature is present and valid */ - private fun verifyPacketSignature(packet: BitchatPacket, peerID: String): Boolean { + private fun verifyPacketSignature(packet: BitchatPacket, peerID: String): ValidationResult { try { - // only verify ANNOUNCE, MESSAGE, and FILE_TRANSFER - if (MessageType.fromValue(packet.type) !in setOf( - MessageType.ANNOUNCE, - MessageType.MESSAGE, - MessageType.FILE_TRANSFER - )) { - return true + val messageType = MessageType.fromValue(packet.type) + if (messageType !in SIGNATURE_REQUIRED_TYPES) { + return ValidationResult.valid() } // 1. Mandatory Signature Check if (packet.signature == null) { - Log.w(TAG, "❌ Signature check for $peerID: NO_SIGNATURE (packet type ${packet.type})") - return false + return ValidationResult.invalid( + reason = "missing signature", + detail = "${messageType?.name ?: packet.type.toString()} requires Ed25519 signature" + ) } // 2. Get Signing Public Key var signingPublicKey: ByteArray? = null - if (MessageType.fromValue(packet.type) == MessageType.ANNOUNCE) { + if (messageType == MessageType.ANNOUNCE) { // Special Case: ANNOUNCE packets carry their own signing key try { val announcement = com.bitchat.android.model.IdentityAnnouncement.decode(packet.payload) signingPublicKey = announcement?.signingPublicKey } catch (e: Exception) { - Log.w(TAG, "Failed to decode announcement for key extraction: ${e.message}") + return ValidationResult.invalid( + reason = "invalid announcement signature key", + detail = e.message + ) } } else { // Standard Case: Get key from known peer info @@ -271,15 +288,19 @@ class SecurityManager(private val encryptionService: EncryptionService, private if (signingPublicKey == null) { // If we don't have a key (and it's not an announce), we can't verify. // For security, we must reject packets from unknown peers unless it's an announce. - Log.w(TAG, "❌ Signature check for $peerID: NO_SIGNING_KEY_AVAILABLE (packet type ${packet.type})") - return false + return ValidationResult.invalid( + reason = "no signing key", + detail = "cannot verify ${messageType?.name ?: packet.type.toString()} from unknown peer" + ) } // 3. Get Canonical Data val packetDataForSigning = packet.toBinaryDataForSigning() if (packetDataForSigning == null) { - Log.w(TAG, "❌ Signature check for $peerID: ENCODING_ERROR (packet type ${packet.type})") - return false + return ValidationResult.invalid( + reason = "packet encoding error", + detail = "could not build canonical signing bytes" + ) } // 4. Verify Signature @@ -291,16 +312,54 @@ class SecurityManager(private val encryptionService: EncryptionService, private ) if (isSignatureValid) { - // Log.v(TAG, "✅ Signature verified for $peerID (type ${packet.type})") - return true + return ValidationResult.valid() } else { - Log.w(TAG, "❌ Signature INVALID for $peerID (type ${packet.type})") - return false + return ValidationResult.invalid( + reason = "invalid signature", + detail = "Ed25519 verification failed" + ) } } catch (e: Exception) { - Log.e(TAG, "❌ Signature verification error for $peerID: ${e.message}") - return false + return ValidationResult.invalid( + reason = "signature verification error", + detail = e.message + ) + } + } + + private fun validateTimestamp(packet: BitchatPacket, currentTime: Long): ValidationResult { + val packetTime = packet.timestamp.toLong() + if (packetTime <= 0L) { + return ValidationResult.invalid("invalid timestamp", "timestamp=${packet.timestamp}") + } + + val ageMs = currentTime - packetTime + return when { + ageMs > MESSAGE_TIMEOUT -> ValidationResult.invalid( + reason = "stale timestamp", + detail = "age=${ageMs / 1000}s exceeds ${MESSAGE_TIMEOUT / 1000}s" + ) + ageMs < -MESSAGE_TIMEOUT -> ValidationResult.invalid( + reason = "future timestamp", + detail = "clock skew=${(-ageMs) / 1000}s exceeds ${MESSAGE_TIMEOUT / 1000}s" + ) + else -> ValidationResult.valid() + } + } + + private fun logValidationFailure(packet: BitchatPacket, peerID: String, reason: String, detail: String? = null) { + debugManager?.logPacketValidationFailure(packet, peerID, reason, detail) + } + + private data class ValidationResult( + val isValid: Boolean, + val reason: String, + val detail: String? = null + ) { + companion object { + fun valid() = ValidationResult(true, "") + fun invalid(reason: String, detail: String? = null) = ValidationResult(false, reason, detail) } } @@ -380,9 +439,6 @@ class SecurityManager(private val encryptionService: EncryptionService, private processedKeyExchanges.removeAll(toRemove.toSet()) } - if (removedCount > 0) { - Log.d(TAG, "Cleaned up $removedCount old processed messages") - } } /** diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt index dce6dd93..cf8c3f80 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseChannelEncryption.kt @@ -49,8 +49,6 @@ class NoiseChannelEncryption { // Store key and password channelKeys[channel] = key channelPasswords[channel] = password - - Log.d(TAG, "Set password for channel $channel") } catch (e: Exception) { Log.e(TAG, "Failed to set password for channel $channel: ${e.message}") } @@ -62,7 +60,6 @@ class NoiseChannelEncryption { fun removeChannelPassword(channel: String) { channelKeys.remove(channel) channelPasswords.remove(channel) - Log.d(TAG, "Removed password for channel $channel") } /** @@ -271,6 +268,5 @@ class NoiseChannelEncryption { fun clear() { channelKeys.clear() channelPasswords.clear() - Log.d(TAG, "Cleared all channel encryption data") } } diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt b/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt index 58dc810e..8b8bf0cc 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseEncryptionService.kt @@ -87,7 +87,6 @@ class NoiseEncryptionService(private val context: Context) { if (loadedKeyPair != null) { staticIdentityPrivateKey = loadedKeyPair.first staticIdentityPublicKey = loadedKeyPair.second - Log.d(TAG, "Loaded existing static identity key: ${calculateFingerprint(staticIdentityPublicKey)}") } else { // Generate new identity key pair val keyPair = generateKeyPair() @@ -96,7 +95,6 @@ class NoiseEncryptionService(private val context: Context) { // Save to secure storage identityStateManager.saveStaticKey(staticIdentityPrivateKey, staticIdentityPublicKey) - Log.d(TAG, "Generated and saved new static identity key") } // Load or create Ed25519 signing key (persistent across sessions) @@ -104,7 +102,6 @@ class NoiseEncryptionService(private val context: Context) { if (loadedSigningKeyPair != null) { signingPrivateKey = loadedSigningKeyPair.first signingPublicKey = loadedSigningKeyPair.second - Log.d(TAG, "Loaded existing Ed25519 signing key") } else { // Generate new Ed25519 signing key pair val signingKeyPair = generateEd25519KeyPair() @@ -113,7 +110,6 @@ class NoiseEncryptionService(private val context: Context) { // Save to secure storage identityStateManager.saveSigningKey(signingPrivateKey, signingPublicKey) - Log.d(TAG, "Generated and saved new Ed25519 signing key") } } @@ -169,7 +165,7 @@ class NoiseEncryptionService(private val context: Context) { // 4. Re-initialize SessionManager with new keys initializeSessionManager() - Log.d(TAG, "✅ Identity cleared and keys rotated") + Log.i(TAG, "Identity cleared and keys rotated") } // MARK: - Handshake Management @@ -182,7 +178,6 @@ class NoiseEncryptionService(private val context: Context) { return try { sessionManager.initiateHandshake(peerID) } catch (e: Exception) { - Log.e(TAG, "Failed to initiate handshake with $peerID: ${e.message}") null } } @@ -195,7 +190,6 @@ class NoiseEncryptionService(private val context: Context) { return try { sessionManager.processHandshakeMessage(peerID, data) } catch (e: Exception) { - Log.e(TAG, "Failed to process handshake from $peerID: ${e.message}") null } } @@ -221,7 +215,7 @@ class NoiseEncryptionService(private val context: Context) { */ fun encrypt(data: ByteArray, peerID: String): ByteArray? { if (!hasEstablishedSession(peerID)) { - Log.w(TAG, "No established session with $peerID, handshake required. TODO: IMPLEMENT HANDSHAKE INIT") + Log.i(TAG, "No established session with $peerID; requesting Noise handshake") onHandshakeRequired?.invoke(peerID) return null } @@ -339,7 +333,7 @@ class NoiseEncryptionService(private val context: Context) { * Initiate rekey for a session (replaces old session with new handshake) */ fun initiateRekey(peerID: String): ByteArray? { - Log.d(TAG, "Initiating rekey for session with $peerID") + Log.i(TAG, "Initiating Noise rekey with $peerID") // Remove old session sessionManager.removeSession(peerID) @@ -385,8 +379,6 @@ class NoiseEncryptionService(private val context: Context) { // Calculate fingerprint for logging and callback val fingerprint = calculateFingerprint(remoteStaticKey) - Log.d(TAG, "Session established with $peerID, fingerprint: ${fingerprint.take(16)}...") - // Notify about authentication onPeerAuthenticated?.invoke(peerID, fingerprint) } diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt b/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt index dc4a3d50..6b712b56 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseSession.kt @@ -2,8 +2,6 @@ package com.bitchat.android.noise import android.util.Log import com.bitchat.android.noise.southernstorm.protocol.* -import com.bitchat.android.util.toHexString -import java.security.SecureRandom /** @@ -123,7 +121,6 @@ class NoiseSession( } // Extract ciphertext (remaining bytes) val ciphertext = combinedPayload.copyOfRange(NONCE_SIZE_BYTES, combinedPayload.size) - Log.d(TAG, "Extracted nonce: $extractedNonce, ciphertext size: ${ciphertext.size}") return Pair(extractedNonce, ciphertext) } catch (e: Exception) { @@ -200,7 +197,6 @@ class NoiseSession( try { // Validate static keys validateStaticKeys() - Log.d(TAG, "Created ${if (isInitiator) "initiator" else "responder"} session for $peerID") } catch (e: Exception) { state = NoiseSessionState.Failed(e) Log.e(TAG, "Failed to initialize Noise session: ${e.message}") @@ -225,8 +221,6 @@ class NoiseSession( if (localStaticPublicKey.all { it == 0.toByte() }) { throw IllegalArgumentException("Local static public key cannot be all zeros") } - - Log.d(TAG, "Static keys validated successfully - private: ${localStaticPrivateKey.size} bytes, public: ${localStaticPublicKey.size} bytes") } /** @@ -235,57 +229,23 @@ class NoiseSession( */ private fun initializeNoiseHandshake(role: Int) { try { - Log.d(TAG, "Creating HandshakeState with role: ${if (role == HandshakeState.INITIATOR) "INITIATOR" else "RESPONDER"}") - - // LOGGING: Track Android handshake initialization (matching iOS) - Log.d(TAG, "=== ANDROID NOISE SESSION - BEFORE HANDSHAKE INIT ===") - Log.d(TAG, "Creating NoiseHandshakeState for peer: $peerID") - Log.d(TAG, "Role: ${if (role == HandshakeState.INITIATOR) "INITIATOR" else "RESPONDER"}") - handshakeState = HandshakeState(PROTOCOL_NAME, role) - Log.d(TAG, "HandshakeState created successfully") - - Log.d(TAG, "=== ANDROID NOISE SESSION - AFTER HANDSHAKE INIT ===") - Log.d(TAG, "NoiseHandshakeState created and mixPreMessageKeys() completed") if (handshakeState?.needsLocalKeyPair() == true) { - Log.d(TAG, "Local static key pair is required for XX pattern") - val localKeyPair = handshakeState?.getLocalKeyPair() if (localKeyPair != null) { // FIXED: Use the provided persistent identity keys with our local fork // Our local fork properly supports setting pre-existing keys - Log.d(TAG, "Setting persistent static identity keys...") - localKeyPair.setPrivateKey(localStaticPrivateKey, 0) if (!localKeyPair.hasPrivateKey() || !localKeyPair.hasPublicKey()) { throw IllegalStateException("Failed to set static identity keys - local fork issue") } - - Log.d(TAG, "✓ Successfully set persistent static identity keys") - Log.d(TAG, "Algorithm: ${localKeyPair.dhName}") - Log.d(TAG, "Private key length: ${localKeyPair.privateKeyLength}") - Log.d(TAG, "Public key length: ${localKeyPair.publicKeyLength}") - - // Verify the keys were set correctly - val verifyPrivate = ByteArray(32) - val verifyPublic = ByteArray(32) - localKeyPair.getPrivateKey(verifyPrivate, 0) - localKeyPair.getPublicKey(verifyPublic, 0) - - Log.d(TAG, "Persistent identity public key: ${localStaticPublicKey.joinToString("") { "%02x".format(it) }}") - Log.d(TAG, "Set public key: ${verifyPublic.joinToString("") { "%02x".format(it) }}") - } else { throw IllegalStateException("HandshakeState returned null for local key pair") } - - } else { - Log.d(TAG, "Local static key pair not needed for this handshake pattern/role") } handshakeState?.start() - Log.d(TAG, "Handshake state started successfully with persistent identity keys") } catch (e: Exception) { Log.e(TAG, "Exception during handshake initialization: ${e.message}", e) @@ -303,8 +263,6 @@ class NoiseSession( */ @Synchronized fun startHandshake(): ByteArray { - Log.d(TAG, "Starting noise XX handshake with $peerID as INITIATOR") - if (!isInitiator) { throw IllegalStateException("Only initiator can start handshake") } @@ -329,11 +287,9 @@ class NoiseSession( Log.w(TAG, "Warning: XX message 1 size ${firstMessage.size} != expected $XX_MESSAGE_1_SIZE") } - Log.d(TAG, "Sending XX handshake message 1 to $peerID (${firstMessage.size} bytes) currentPattern: $currentPattern") return firstMessage } catch (e: Exception) { state = NoiseSessionState.Failed(e) - Log.e(TAG, "Failed to start handshake: ${e.message}") throw e } } @@ -344,14 +300,11 @@ class NoiseSession( */ @Synchronized fun processHandshakeMessage(message: ByteArray): ByteArray? { - Log.d(TAG, "Processing handshake message from $peerID (${message.size} bytes)") - try { // Initialize as responder if receiving first message if (state == NoiseSessionState.Uninitialized && !isInitiator) { initializeNoiseHandshake(HandshakeState.RESPONDER) state = NoiseSessionState.Handshaking - Log.d(TAG, "Initialized as RESPONDER for XX handshake with $peerID") } if (state != NoiseSessionState.Handshaking) { @@ -364,13 +317,11 @@ class NoiseSession( val payloadBuffer = ByteArray(XX_MESSAGE_2_SIZE + MAX_PAYLOAD_SIZE) // Buffer for any payload data // Read the incoming message - the Noise library will handle validation - val payloadLength = handshakeStateLocal.readMessage(message, 0, message.size, payloadBuffer, 0) + handshakeStateLocal.readMessage(message, 0, message.size, payloadBuffer, 0) currentPattern++ - Log.d(TAG, "Read handshake message, payload length: $payloadLength currentPattern: $currentPattern") // Check what action the handshake state wants us to take next val action = handshakeStateLocal.getAction() - Log.d(TAG, "Handshake action after processing message: $action") return when (action) { HandshakeState.WRITE_MESSAGE -> { @@ -380,7 +331,6 @@ class NoiseSession( currentPattern++ val response = responseBuffer.copyOf(responseLength) - Log.d(TAG, "Generated handshake response: ${response.size} bytes, action still: ${handshakeStateLocal.getAction()} currentPattern: $currentPattern") completeHandshake() response } @@ -388,7 +338,6 @@ class NoiseSession( HandshakeState.SPLIT -> { // Handshake complete, split into transport keys completeHandshake() - Log.d(TAG, "SPLIT ✅ XX handshake completed with $peerID") null } @@ -398,19 +347,16 @@ class NoiseSession( HandshakeState.READ_MESSAGE -> { // Noise library expects us to read another message - Log.d(TAG, "Handshake waiting for next message from $peerID") null } else -> { - Log.d(TAG, "Handshake action: $action - no immediate action needed") null } } } catch (e: Exception) { state = NoiseSessionState.Failed(e) - Log.e(TAG, "Handshake failed with $peerID: ${e.message}", e) throw e } } @@ -423,9 +369,6 @@ class NoiseSession( if (currentPattern < NOISE_XX_PATTERN_LENGTH) { return } - - Log.d(TAG, "Completing XX handshake with $peerID") - try { // Split handshake state into transport ciphers val cipherPair = handshakeState?.split() @@ -439,7 +382,6 @@ class NoiseSession( if (remoteDH != null) { remoteStaticPublicKey = ByteArray(32) remoteDH.getPublicKey(remoteStaticPublicKey!!, 0) - Log.d(TAG, "Remote static public key: ${remoteStaticPublicKey!!.joinToString("") { "%02x".format(it) }}") } } @@ -459,11 +401,8 @@ class NoiseSession( replayWindow = ByteArray(REPLAY_WINDOW_BYTES) state = NoiseSessionState.Established - Log.d(TAG, "Handshake completed with $peerID as isInitiator: $isInitiator - transport keys derived") - Log.d(TAG, "✅ XX handshake completed with $peerID") } catch (e: Exception) { state = NoiseSessionState.Failed(e) - Log.e(TAG, "Failed to complete handshake: ${e.message}") throw e } } @@ -526,17 +465,9 @@ class NoiseSession( Log.w(TAG, "High nonce value detected: $currentNonce - consider rekeying") } - Log.d(TAG, "✅ ANDROID ENCRYPT: ${data.size} → ${combinedPayload.size} bytes (nonce: $currentNonce, ciphertextLength+TAG: ${ciphertextLength}) for $peerID (msg #$messagesSent, role: ${if (isInitiator) "INITIATOR" else "RESPONDER"})") return combinedPayload } catch (e: Exception) { - Log.e(TAG, "Real encryption failed - exception: ${e.message}") - - // ENHANCED: Log cipher state for debugging - if (sendCipher != null) { - Log.e(TAG, "Send cipher state: ${sendCipher!!.javaClass.simpleName}") - } - throw SessionError.EncryptionFailed } } @@ -596,19 +527,9 @@ class NoiseSession( } val result = plaintext.copyOf(plaintextLength) - Log.d(TAG, "✅ ANDROID DECRYPT: ${combinedPayload.size} → ${result.size} bytes from $peerID (nonce: $extractedNonce, highest: $highestReceivedNonce, role: ${if (isInitiator) "INITIATOR" else "RESPONDER"})") return result } catch (e: Exception) { - Log.e(TAG, "Decryption failed - exception: ${e.message}") - - // ENHANCED: Log cipher state and session details for debugging - if (receiveCipher != null) { - Log.e(TAG, "Receive cipher state: ${receiveCipher!!.javaClass.simpleName}") - } - Log.e(TAG, "Session state: $state, highest received nonce: $highestReceivedNonce") - Log.e(TAG, "Input data size: ${combinedPayload.size} bytes") - throw SessionError.DecryptionFailed } } @@ -709,9 +630,6 @@ class NoiseSession( if (state !is NoiseSessionState.Failed) { state = NoiseSessionState.Failed(Exception("Session destroyed")) } - - Log.d(TAG, "Session destroyed for $peerID") - } catch (e: Exception) { Log.w(TAG, "Error during session cleanup: ${e.message}") } diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt b/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt index 2d9e06d8..c16246c5 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt @@ -28,7 +28,6 @@ class NoiseSessionManager( */ fun addSession(peerID: String, session: NoiseSession) { sessions[peerID] = session - Log.d(TAG, "Added new session for $peerID") } /** @@ -45,14 +44,13 @@ class NoiseSessionManager( fun removeSession(peerID: String) { sessions[peerID]?.destroy() sessions.remove(peerID) - Log.d(TAG, "Removed session for $peerID") } /** * SIMPLIFIED: Initiate handshake - no tie breaker, just start */ fun initiateHandshake(peerID: String): ByteArray { - Log.d(TAG, "initiateHandshake($peerID)") + Log.i(TAG, "Starting Noise handshake with $peerID") // Remove any existing session first removeSession(peerID) @@ -64,15 +62,13 @@ class NoiseSessionManager( localStaticPrivateKey = localStaticPrivateKey, localStaticPublicKey = localStaticPublicKey ) - Log.d(TAG, "Storing new INITIATOR session for $peerID") addSession(peerID, session) try { - val handshakeData = session.startHandshake() - Log.d(TAG, "Started handshake with $peerID as INITIATOR") - return handshakeData + return session.startHandshake() } catch (e: Exception) { sessions.remove(peerID) + Log.e(TAG, "Failed to start Noise handshake with $peerID: ${e.message}") throw e } } @@ -81,14 +77,12 @@ class NoiseSessionManager( * Handle incoming handshake message */ fun processHandshakeMessage(peerID: String, message: ByteArray): ByteArray? { - Log.d(TAG, "processHandshakeMessage($peerID, ${message.size} bytes)") - try { var session = getSession(peerID) // If no session exists, create one as responder if (session == null) { - Log.d(TAG, "Creating new RESPONDER session for $peerID") + Log.i(TAG, "Responding to Noise handshake from $peerID") session = NoiseSession( peerID = peerID, isInitiator = false, @@ -103,7 +97,7 @@ class NoiseSessionManager( // Check if session is established if (session.isEstablished()) { - Log.d(TAG, "✅ Session ESTABLISHED with $peerID") + Log.i(TAG, "Noise session established with $peerID") val remoteStaticKey = session.getRemoteStaticPublicKey() if (remoteStaticKey != null) { onSessionEstablished?.invoke(peerID, remoteStaticKey) @@ -137,11 +131,9 @@ class NoiseSessionManager( fun decrypt(encryptedData: ByteArray, peerID: String): ByteArray { val session = getSession(peerID) if (session == null) { - Log.e(TAG, "No session found for $peerID when trying to decrypt") throw IllegalStateException("No session found for $peerID") } if (!session.isEstablished()) { - Log.e(TAG, "Session not established with $peerID when trying to decrypt") throw IllegalStateException("Session not established with $peerID") } return session.decrypt(encryptedData) @@ -151,9 +143,7 @@ class NoiseSessionManager( * Check if session is established with peer */ fun hasEstablishedSession(peerID: String): Boolean { - val hasSession = getSession(peerID)?.isEstablished() ?: false - Log.d(TAG, "hasEstablishedSession($peerID): $hasSession") - return hasSession + return getSession(peerID)?.isEstablished() ?: false } /** @@ -210,7 +200,6 @@ class NoiseSessionManager( fun shutdown() { sessions.values.forEach { it.destroy() } sessions.clear() - Log.d(TAG, "Noise session manager shut down") } } diff --git a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java index 91fec78a..de90b1eb 100644 --- a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/HandshakeState.java @@ -22,8 +22,6 @@ package com.bitchat.android.noise.southernstorm.protocol; -import android.util.Log; - import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -35,8 +33,6 @@ import javax.crypto.ShortBufferException; */ public class HandshakeState implements Destroyable { - private static final String TAG = "AndroidHandshake"; - private SymmetricState symmetric; private boolean isInitiator; private DHState localKeyPair; @@ -467,17 +463,6 @@ public class HandshakeState implements Destroyable { // Empty value for when the prologue is not supplied. private static final byte[] emptyPrologue = new byte [0]; - /** - * Converts a byte array to hex string for logging (matching iOS hex format) - */ - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - /** * Starts the handshake running. * @@ -524,19 +509,11 @@ public class HandshakeState implements Destroyable { throw new IllegalStateException("Pre-shared key required"); } - // Log the symmetric state BEFORE any mixing operations (matching iOS) - Log.d(TAG, "=== ANDROID HANDSHAKE START - INITIAL STATE ==="); - Log.d(TAG, "Protocol: " + symmetric.getProtocolName()); - Log.d(TAG, "Role: " + (isInitiator ? "INITIATOR" : "RESPONDER")); - Log.d(TAG, "Initial symmetric hash: " + bytesToHex(symmetric.getHandshakeHash())); - // Hash the prologue value. - Log.d(TAG, "Mixing empty prologue"); if (prologue != null) symmetric.mixHash(prologue, 0, prologue.length); else symmetric.mixHash(emptyPrologue, 0, 0); - Log.d(TAG, "Hash after empty prologue: " + bytesToHex(symmetric.getHandshakeHash())); // Hash the pre-shared key into the chaining key and handshake hash. if (preSharedKey != null) @@ -544,7 +521,6 @@ public class HandshakeState implements Destroyable { // Mix the pre-supplied public keys into the handshake hash. if (isInitiator) { - Log.d(TAG, "XX pattern - no pre-message keys to mix"); if ((requirements & LOCAL_PREMSG) != 0) symmetric.mixPublicKey(localKeyPair); if ((requirements & FALLBACK_PREMSG) != 0) { @@ -557,7 +533,6 @@ public class HandshakeState implements Destroyable { if ((requirements & REMOTE_PREMSG) != 0) symmetric.mixPublicKey(remotePublicKey); } else { - Log.d(TAG, "XX pattern - no pre-message keys to mix"); if ((requirements & REMOTE_PREMSG) != 0) symmetric.mixPublicKey(remotePublicKey); if ((requirements & FALLBACK_PREMSG) != 0) { @@ -571,11 +546,6 @@ public class HandshakeState implements Destroyable { symmetric.mixPublicKey(localKeyPair); } - // Log final state after all initialization (matching iOS) - Log.d(TAG, "=== ANDROID HANDSHAKE START - FINAL STATE ==="); - Log.d(TAG, "Final symmetric hash after mixPreMessageKeys(): " + bytesToHex(symmetric.getHandshakeHash())); - Log.d(TAG, "==========================================="); - // The handshake has officially started - set the first action. if (isInitiator) action = WRITE_MESSAGE; diff --git a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java index 0e70f9f3..ffdb8428 100644 --- a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/SymmetricState.java @@ -22,8 +22,6 @@ package com.bitchat.android.noise.southernstorm.protocol; -import android.util.Log; - import java.io.UnsupportedEncodingException; import java.security.DigestException; import java.security.MessageDigest; @@ -38,8 +36,6 @@ import javax.crypto.ShortBufferException; */ class SymmetricState implements Destroyable { - private static final String TAG = "AndroidSymmetric"; - private String name; private CipherState cipher; private MessageDigest hash; @@ -47,17 +43,6 @@ class SymmetricState implements Destroyable { private byte[] h; private byte[] prev_h; - /** - * Converts a byte array to hex string for logging (matching iOS hex format) - */ - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - /** * Constructs a new symmetric state object. * @@ -94,14 +79,6 @@ class SymmetricState implements Destroyable { } System.arraycopy(h, 0, ck, 0, hashLength); - - // LOGGING: Initial symmetric state after protocol name initialization (matching iOS) - Log.d(TAG, "=== ANDROID SYMMETRIC STATE INITIALIZED ==="); - Log.d(TAG, "Protocol: " + protocolName); - Log.d(TAG, "Initial hash (h): " + bytesToHex(h)); - Log.d(TAG, "Initial chaining key (ck): " + bytesToHex(ck)); - Log.d(TAG, "Hash length: " + h.length); - Log.d(TAG, "========================================="); } /** @@ -134,14 +111,6 @@ class SymmetricState implements Destroyable { */ public void mixKey(byte[] data, int offset, int length) { - // LOGGING: Before mixKey operation (matching iOS) - byte[] inputData = new byte[length]; - System.arraycopy(data, offset, inputData, 0, length); - Log.d(TAG, "*** Android mixKey() BEFORE ***"); - Log.d(TAG, "Input data (" + length + " bytes): " + bytesToHex(inputData)); - Log.d(TAG, "Current CK: " + bytesToHex(ck)); - Log.d(TAG, "Current Hash: " + bytesToHex(h)); - int keyLength = cipher.getKeyLength(); byte[] tempKey = new byte [keyLength]; try { @@ -150,12 +119,6 @@ class SymmetricState implements Destroyable { } finally { Noise.destroy(tempKey); } - - // LOGGING: After mixKey operation (matching iOS) - Log.d(TAG, "*** Android mixKey() AFTER ***"); - Log.d(TAG, "New CK: " + bytesToHex(ck)); - Log.d(TAG, "Hash unchanged: " + bytesToHex(h)); - Log.d(TAG, "Cipher now has key: " + (cipher.getMACLength() > 0)); } /** @@ -167,18 +130,7 @@ class SymmetricState implements Destroyable { */ public void mixHash(byte[] data, int offset, int length) { - // LOGGING: Before mixHash operation (matching iOS) - byte[] inputData = new byte[length]; - System.arraycopy(data, offset, inputData, 0, length); - Log.d(TAG, "*** Android mixHash() BEFORE ***"); - Log.d(TAG, "Input data (" + length + " bytes): " + bytesToHex(inputData)); - Log.d(TAG, "Current Hash: " + bytesToHex(h)); - hashTwo(h, 0, h.length, data, offset, length, h, 0, h.length); - - // LOGGING: After mixHash operation (matching iOS) - Log.d(TAG, "*** Android mixHash() AFTER ***"); - Log.d(TAG, "New Hash: " + bytesToHex(h)); } /** diff --git a/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt b/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt index 04341fa5..7a6a47d0 100644 --- a/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt +++ b/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.asSharedFlow import java.util.Date import java.util.concurrent.ConcurrentLinkedQueue import com.bitchat.android.protocol.BitchatPacket +import com.bitchat.android.protocol.MessageType import com.bitchat.android.util.toHexString /** @@ -378,97 +379,6 @@ class DebugSettingsManager private constructor() { } } - fun logIncomingPacket(senderPeerID: String, senderNickname: String?, messageType: String, viaDeviceId: String?) { - if (verboseLoggingEnabled.value) { - val who = if (!senderNickname.isNullOrBlank()) "$senderNickname ($senderPeerID)" else senderPeerID - val routeInfo = if (!viaDeviceId.isNullOrBlank()) " via $viaDeviceId" else " (direct)" - addDebugMessage(DebugMessage.PacketEvent( - "📦 Received $messageType from $who$routeInfo" - )) - } - } - fun logPacketRelay( - packetType: String, - originalPeerID: String, - originalNickname: String?, - viaDeviceId: String? - ) { - // Backward-compatible simple API; delegate to detailed formatter with best effort - logPacketRelayDetailed( - packetType = packetType, - senderPeerID = originalPeerID, - senderNickname = originalNickname, - fromPeerID = null, - fromNickname = null, - fromDeviceAddress = viaDeviceId, - toPeerID = null, - toNickname = null, - toDeviceAddress = null, - ttl = null, - isRelay = true - ) - } - - - // New, more detailed relay logger used by the mesh/broadcaster - fun logPacketRelayDetailed( - packetType: String, - senderPeerID: String?, - senderNickname: String?, - fromPeerID: String?, - fromNickname: String?, - fromDeviceAddress: String?, - toPeerID: String?, - toNickname: String?, - toDeviceAddress: String?, - ttl: UByte?, - isRelay: Boolean = true, - packetVersion: UByte = 1u, - routeInfo: String? = null - ) { - // Build message only if verbose logging is enabled, but always update stats - val senderLabel = when { - !senderNickname.isNullOrBlank() && !senderPeerID.isNullOrBlank() -> "$senderNickname ($senderPeerID)" - !senderNickname.isNullOrBlank() -> senderNickname - !senderPeerID.isNullOrBlank() -> senderPeerID - else -> "unknown" - } - val fromName = when { - !fromNickname.isNullOrBlank() -> fromNickname - !fromPeerID.isNullOrBlank() -> fromPeerID - else -> "unknown" - } - val toName = when { - !toNickname.isNullOrBlank() -> toNickname - !toPeerID.isNullOrBlank() -> toPeerID - else -> "unknown" - } - - val fromAddr = fromDeviceAddress ?: "?" - val toAddr = toDeviceAddress ?: "?" - val ttlStr = ttl?.toString() ?: "?" - val routeStr = if (routeInfo != null) " $routeInfo" else "" - - if (verboseLoggingEnabled.value) { - if (isRelay) { - // Relay: show [previousPeer] -> [nextPeer] - addDebugMessage( - DebugMessage.RelayEvent( - "♻️ Relayed v$packetVersion $packetType by $senderLabel from $fromName (${fromPeerID ?: "?"}, $fromAddr) to $toName (${toPeerID ?: "?"}, $toAddr) with TTL $ttlStr$routeStr" - ) - ) - } else { - addDebugMessage( - DebugMessage.PacketEvent( - "📤 Sent v$packetVersion $packetType by $senderLabel to $toName (${toPeerID ?: "?"}, $toAddr) with TTL $ttlStr$routeStr" - ) - ) - } - } - - // Do not update counters here; this path is for readable logs only. - } - // MARK: - Debug Events for Animation sealed class MeshVisualEvent { data class PacketActivity(val peerID: String) : MeshVisualEvent() @@ -490,17 +400,30 @@ class DebugSettingsManager private constructor() { // Peer nickname resolver private var nicknameResolver: ((String) -> String?)? = null fun setNicknameResolver(resolver: (String) -> String?) { nicknameResolver = resolver } + + private fun packetTypeName(type: UByte): String { + return MessageType.fromValue(type)?.name ?: "0x%02X".format(type.toInt()) + } + + private fun peerLabel(peerID: String?, nickname: String?): String { + return when { + !nickname.isNullOrBlank() && !peerID.isNullOrBlank() -> "$nickname ($peerID)" + !nickname.isNullOrBlank() -> nickname + !peerID.isNullOrBlank() -> peerID + else -> "unknown" + } + } // Explicit incoming/outgoing logging to avoid double counting fun logIncoming(packet: BitchatPacket, fromPeerID: String, fromNickname: String?, fromDeviceAddress: String?, myPeerID: String) { - val packetType = packet.type.toString() + val packetType = packetTypeName(packet.type) val packetVersion = packet.version val route = packet.route val routeInfo = if (!route.isNullOrEmpty()) "routed: ${route.size} hops" else null if (verboseLoggingEnabled.value) { val resolvedNick = fromNickname ?: nicknameResolver?.invoke(fromPeerID) ?: "unknown" - val who = if (resolvedNick != "unknown") "$resolvedNick ($fromPeerID)" else fromPeerID + val who = peerLabel(fromPeerID, resolvedNick.takeUnless { it == "unknown" }) val routeStr = if (routeInfo != null) " $routeInfo" else "" addDebugMessage(DebugMessage.PacketEvent("📥 Incoming v$packetVersion $packetType from $who (${fromDeviceAddress ?: "?"})$routeStr")) } @@ -537,11 +460,45 @@ class DebugSettingsManager private constructor() { if (visible) updateRelayStatsFromTimestamps() } - fun logOutgoing(packetType: String, toPeerID: String?, toNickname: String?, toDeviceAddress: String?, previousHopPeerID: String? = null, packetVersion: UByte = 1u, routeInfo: String? = null) { + fun logPacketValidationFailure(packet: BitchatPacket, fromPeerID: String, reason: String, detail: String? = null) { + if (!verboseLoggingEnabled.value) return + + val packetType = packetTypeName(packet.type) + val detailText = detail?.takeIf { it.isNotBlank() }?.let { " - $it" } ?: "" + addDebugMessage( + DebugMessage.ValidationEvent( + "⚠️ Dropped v${packet.version} $packetType from $fromPeerID: $reason$detailText (ttl=${packet.ttl})" + ) + ) + } + + fun logOutgoing( + packetType: String, + toPeerID: String?, + toNickname: String?, + toDeviceAddress: String?, + previousHopPeerID: String? = null, + packetVersion: UByte = 1u, + routeInfo: String? = null, + ttl: UByte? = null + ) { if (verboseLoggingEnabled.value) { - val who = toNickname ?: toPeerID ?: "unknown" + val who = peerLabel(toPeerID, toNickname) val routeStr = if (routeInfo != null) " $routeInfo" else "" - addDebugMessage(DebugMessage.PacketEvent("📤 Outgoing v$packetVersion $packetType to $who (${toPeerID ?: "?"}, ${toDeviceAddress ?: "?"})$routeStr")) + val ttlStr = ttl?.let { " ttl=$it" } ?: "" + if (!previousHopPeerID.isNullOrBlank()) { + addDebugMessage( + DebugMessage.RelayEvent( + "📤 Relay v$packetVersion $packetType from $previousHopPeerID to $who (${toDeviceAddress ?: "?"})$ttlStr$routeStr" + ) + ) + } else { + addDebugMessage( + DebugMessage.PacketEvent( + "📤 Outgoing v$packetVersion $packetType to $who (${toDeviceAddress ?: "?"})$ttlStr$routeStr" + ) + ) + } } val now = System.currentTimeMillis() val visible = _debugSheetVisible.value @@ -577,6 +534,43 @@ class DebugSettingsManager private constructor() { _scanResults.value = emptyList() addDebugMessage(DebugMessage.SystemMessage("🗑️ Scan results cleared")) } + + fun resetForTesting() { + _verboseLoggingEnabled.value = false + debugMessageQueue.clear() + scanResultsQueue.clear() + _debugMessages.value = emptyList() + _scanResults.value = emptyList() + _connectedDevices.value = emptyList() + _relayStats.value = PacketRelayStats() + relayTimestamps.clear() + incomingTimestamps.clear() + outgoingTimestamps.clear() + perDeviceRelayTimestamps.clear() + perPeerRelayTimestamps.clear() + perDeviceIncoming.clear() + perDeviceOutgoing.clear() + perPeerIncoming.clear() + perPeerOutgoing.clear() + deviceIncomingTotalsMap.clear() + deviceOutgoingTotalsMap.clear() + peerIncomingTotalsMap.clear() + peerOutgoingTotalsMap.clear() + _perDeviceLastSecond.value = emptyMap() + _perPeerLastSecond.value = emptyMap() + _perDeviceIncomingLastSecond.value = emptyMap() + _perDeviceOutgoingLastSecond.value = emptyMap() + _perPeerIncomingLastSecond.value = emptyMap() + _perPeerOutgoingLastSecond.value = emptyMap() + _perDeviceIncomingLastMinute.value = emptyMap() + _perDeviceOutgoingLastMinute.value = emptyMap() + _perPeerIncomingLastMinute.value = emptyMap() + _perPeerOutgoingLastMinute.value = emptyMap() + _perDeviceIncomingTotalsFlow.value = emptyMap() + _perDeviceOutgoingTotalsFlow.value = emptyMap() + _perPeerIncomingTotalsFlow.value = emptyMap() + _perPeerOutgoingTotalsFlow.value = emptyMap() + } } // MARK: - Data Models @@ -589,6 +583,7 @@ sealed class DebugMessage(val content: String, val timestamp: Date = Date()) { class PeerEvent(content: String) : DebugMessage(content) class PacketEvent(content: String) : DebugMessage(content) class RelayEvent(content: String) : DebugMessage(content) + class ValidationEvent(content: String) : DebugMessage(content) } /** diff --git a/app/src/test/kotlin/com/bitchat/android/mesh/SecurityManagerTest.kt b/app/src/test/kotlin/com/bitchat/android/mesh/SecurityManagerTest.kt index aa629ca7..7ba74c4e 100644 --- a/app/src/test/kotlin/com/bitchat/android/mesh/SecurityManagerTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/mesh/SecurityManagerTest.kt @@ -5,6 +5,8 @@ import com.bitchat.android.crypto.EncryptionService import com.bitchat.android.model.IdentityAnnouncement import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType +import com.bitchat.android.ui.debug.DebugMessage +import com.bitchat.android.ui.debug.DebugSettingsManager import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -23,6 +25,7 @@ class SecurityManagerTest { private lateinit var securityManager: SecurityManager private lateinit var fakeEncryptionService: FakeEncryptionService private lateinit var mockDelegate: SecurityManagerDelegate + private lateinit var debugSettingsManager: DebugSettingsManager private val myPeerID = "1111222233334444" private val otherPeerID = "aaaabbbbccccdddd" @@ -61,6 +64,10 @@ class SecurityManagerTest { @Before fun setup() { + debugSettingsManager = DebugSettingsManager.getInstance() + debugSettingsManager.resetForTesting() + debugSettingsManager.setVerboseLoggingEnabled(true) + fakeEncryptionService = FakeEncryptionService() mockDelegate = mock() @@ -73,6 +80,9 @@ class SecurityManagerTest { if (::securityManager.isInitialized) { securityManager.shutdown() } + if (::debugSettingsManager.isInitialized) { + debugSettingsManager.resetForTesting() + } } @Test @@ -88,6 +98,7 @@ class SecurityManagerTest { val result = securityManager.validatePacket(packet, otherPeerID) assertFalse("Packet without signature should be rejected", result) + assertValidationLogContains("missing signature") } @Test @@ -105,6 +116,41 @@ class SecurityManagerTest { val result = securityManager.validatePacket(packet, otherPeerID) assertFalse("Packet with invalid signature should be rejected", result) + assertValidationLogContains("invalid signature") + } + + @Test + fun `validatePacket - rejects stale timestamp and logs validation failure`() { + setupKnownPeer(otherPeerID, otherSigningKey) + val staleTimestamp = (System.currentTimeMillis() - + com.bitchat.android.util.AppConstants.Security.MESSAGE_TIMEOUT_MS - + 1_000L).toULong() + + val packet = packetWithTimestamp(staleTimestamp) + packet.signature = validSignature + + val result = securityManager.validatePacket(packet, otherPeerID) + + assertFalse("Packet with stale timestamp should be rejected", result) + assertValidationLogContains("stale timestamp") + } + + @Test + fun `validatePacket - invalid signature does not poison duplicate cache`() { + setupKnownPeer(otherPeerID, otherSigningKey) + + val packet = BitchatPacket( + type = MessageType.MESSAGE.value, + ttl = 10u, + senderID = otherPeerID, + payload = dummyPayload + ) + packet.signature = invalidSignature + + assertFalse("Invalid signed packet should be rejected", securityManager.validatePacket(packet, otherPeerID)) + + packet.signature = validSignature + assertTrue("Valid retry should not be rejected as a duplicate", securityManager.validatePacket(packet, otherPeerID)) } @Test @@ -283,4 +329,34 @@ class SecurityManagerTest { ) whenever(mockDelegate.getPeerInfo(peerID)).thenReturn(info) } + + private fun assertValidationLogContains(expected: String) { + val validationMessages = debugSettingsManager.debugMessages.value + .filterIsInstance() + + assertTrue( + "Expected validation log containing '$expected', got ${validationMessages.map { it.content }}", + validationMessages.any { it.content.contains(expected, ignoreCase = true) } + ) + } + + private fun packetWithTimestamp(timestamp: ULong): BitchatPacket { + return BitchatPacket( + version = 1u, + type = MessageType.MESSAGE.value, + senderID = peerIDBytes(otherPeerID), + recipientID = null, + timestamp = timestamp, + payload = dummyPayload, + signature = null, + ttl = 10u + ) + } + + private fun peerIDBytes(peerID: String): ByteArray { + return peerID.chunked(2) + .take(8) + .map { it.toInt(16).toByte() } + .toByteArray() + } }