Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ class BluetoothConnectionManager(
}
}

if (peerID == myPeerID) return // Ignore messages from self
// Normally ignore messages from self, but accept local-only sync responses (TTL=0)
if (peerID == myPeerID) {
if (packet.ttl == 0u.toUByte()) {
Log.d(TAG, "Accepting self-sender packet with TTL=0 (likely SYNC response)")
} else {
return // Ignore non-sync self packets
}
}

delegate?.onPacketReceived(packet, peerID, device)
}
Expand Down
12 changes: 7 additions & 5 deletions app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class MessageHandler(private val myPeerID: String) {
val packet = routed.packet
val peerID = routed.peerID ?: "unknown"

if (peerID == myPeerID) return false
if (peerID == myPeerID && routed.packet.ttl != 0u.toUByte()) return false

// Try to decode as iOS-compatible IdentityAnnouncement with TLV format
val announcement = IdentityAnnouncement.decode(packet.payload)
Expand Down Expand Up @@ -304,7 +304,8 @@ class MessageHandler(private val myPeerID: String) {
suspend fun handleMessage(routed: RoutedPacket) {
val packet = routed.packet
val peerID = routed.peerID ?: "unknown"
if (peerID == myPeerID) return
// IMPORTANT: Allow TTL=0 self packets (sync responses) to flow through
if (peerID == myPeerID && packet.ttl != 0u.toUByte()) return
val senderNickname = delegate?.getPeerNickname(peerID)
if (senderNickname != null) {
Log.d(TAG, "Received message from $senderNickname")
Expand All @@ -329,18 +330,19 @@ class MessageHandler(private val myPeerID: String) {
private suspend fun handleBroadcastMessage(routed: RoutedPacket) {
val packet = routed.packet
val peerID = routed.peerID ?: "unknown"

// Enforce: only accept public messages from verified peers we know
// EXCEPTION: allow our own messages (peerID == myPeerID), e.g. TTL=0 sync responses
val peerInfo = delegate?.getPeerInfo(peerID)
if (peerInfo == null || !peerInfo.isVerifiedNickname) {
if (peerID != myPeerID && (peerInfo == null || !peerInfo.isVerifiedNickname)) {
Log.w(TAG, "🚫 Dropping public message from unverified or unknown peer ${peerID.take(8)}...")
return
}

try {
// Parse message
val message = BitchatMessage(
sender = delegate?.getPeerNickname(peerID) ?: "unknown",
sender = if (peerID == myPeerID) (delegate?.getMyNickname() ?: myPeerID) else (delegate?.getPeerNickname(peerID) ?: "unknown"),
content = String(packet.payload, Charsets.UTF_8),
senderPeerID = peerID,
timestamp = Date(packet.timestamp.toLong())
Expand Down
12 changes: 9 additions & 3 deletions app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ class SecurityManager(private val encryptionService: EncryptionService, private
* Validate packet security (timestamp, replay attacks, duplicates, signatures)
*/
fun validatePacket(packet: BitchatPacket, peerID: String): Boolean {
// Skip validation for our own packets
// Allow our own packets only when they are local-only (TTL=0) — used by SYNC responses
if (peerID == myPeerID) {
Log.d(TAG, "Skipping validation for our own packet")
return false
val isLocalOnly = packet.ttl == 0u.toUByte()
if (isLocalOnly) {
Log.d(TAG, "Accepting self packet due to TTL=0 (SYNC/local-only)")
// Intentionally continue to duplicate detection to avoid showing duplicates
} else {
Log.d(TAG, "Dropping non-local self packet during validation")
return false
}
}

// Validate packet payload
Expand Down
18 changes: 17 additions & 1 deletion app/src/main/java/com/bitchat/android/sync/GossipSyncManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,20 @@ class GossipSyncManager(
return GCSFilter.contains(sorted, v)
}

// Track what we send per original sender peerID (hex)
data class SentStats(var announce: Boolean = false, var msgCount: Int = 0)
val sentBySender = mutableMapOf<String, SentStats>()

// 1) Announcements: send latest per peerID if remote doesn't have them
for ((_, pair) in latestAnnouncementByPeer.entries) {
for ((senderHex, pair) in latestAnnouncementByPeer.entries) {
val (id, pkt) = pair
val idBytes = hexToBytes(id)
if (!mightContain(idBytes)) {
// Send original packet unchanged to requester only (keep local TTL)
val toSend = pkt.copy(ttl = 0u)
delegate?.sendPacketToPeer(fromPeerID, toSend)
Log.d(TAG, "Sent sync announce: Type ${toSend.type} from ${toSend.senderID.toHexString()} to $fromPeerID packet id ${idBytes.toHexString()}")
sentBySender.getOrPut(senderHex) { SentStats() }.apply { announce = true }
}
}

Expand All @@ -176,6 +181,17 @@ class GossipSyncManager(
val toSend = pkt.copy(ttl = 0u)
delegate?.sendPacketToPeer(fromPeerID, toSend)
Log.d(TAG, "Sent sync message: Type ${toSend.type} to $fromPeerID packet id ${idBytes.toHexString()}")
val senderHex = pkt.senderID.joinToString("") { b -> "%02x".format(b) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to have proper name for b here

sentBySender.getOrPut(senderHex) { SentStats() }.apply { msgCount += 1 }
}
}

// Summary log per sender (original packet senderID)
if (sentBySender.isEmpty()) {
Log.d(TAG, "SYNC_RESPONSE summary to $fromPeerID: nothing to send")
} else {
for ((senderHex, stats) in sentBySender) {
Log.d(TAG, "SYNC_RESPONSE summary to $fromPeerID for sender $senderHex: announce=${stats.announce}, messages=${stats.msgCount}")
}
}
}
Expand Down
Loading