Fix delivery acks and read receipts (#186)

* delivery ack and read receipt

* handle deliveryack

* fix uppercase id

* fix read receipts too
This commit is contained in:
callebtc
2025-07-25 17:54:12 +02:00
committed by GitHub
parent 534be7e613
commit 565e4ab33c
8 changed files with 274 additions and 92 deletions
@@ -66,7 +66,7 @@ class BluetoothMeshService(private val context: Context) {
// Wire up PacketProcessor reference for recursive handling in MessageHandler
messageHandler.packetProcessor = packetProcessor
sendPeriodicBroadcastAnnounce()
startPeriodicDebugLogging()
// startPeriodicDebugLogging()
}
/**
@@ -89,13 +89,13 @@ class BluetoothMeshService(private val context: Context) {
}
/**
* Send broadcast announcement every 10 seconds
* Send broadcast announcement every 30 seconds
*/
private fun sendPeriodicBroadcastAnnounce() {
serviceScope.launch {
while (isActive) {
try {
delay(10000) // 10 seconds
delay(30000) // 30 seconds
sendBroadcastAnnounce()
} catch (e: Exception) {
Log.e(TAG, "Error in periodic broadcast announce: ${e.message}")
@@ -356,9 +356,9 @@ class BluetoothMeshService(private val context: Context) {
return fragmentManager.handleFragment(packet)
}
override fun handleDeliveryAck(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleDeliveryAck(routed) }
}
// override fun handleDeliveryAck(routed: RoutedPacket) {
// serviceScope.launch { messageHandler.handleDeliveryAck(routed) }
// }
override fun handleReadReceipt(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleReadReceipt(routed) }
@@ -552,7 +552,7 @@ class BluetoothMeshService(private val context: Context) {
originalMessageID = message.id,
recipientID = myPeerID,
recipientNickname = nickname,
hopCount = 0u // Will be calculated during relay
hopCount = 0u.toUByte() // Will be calculated during relay
)
try {
@@ -586,6 +586,47 @@ class BluetoothMeshService(private val context: Context) {
}
}
/**
* Send read receipt for a received private message
*/
fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) {
serviceScope.launch {
try {
Log.d(TAG, "Sending read receipt for message $messageID to $recipientPeerID")
// Create the read receipt
val receipt = ReadReceipt(
originalMessageID = messageID,
readerID = myPeerID,
readerNickname = readerNickname
)
// Encode the receipt
val receiptData = receipt.encode()
// Create inner read receipt packet
val innerPacket = BitchatPacket(
version = 1u,
type = MessageType.READ_RECEIPT.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = receiptData,
signature = null,
ttl = 3u
)
// Encrypt the entire inner packet and send as NOISE_ENCRYPTED
encryptAndBroadcastNoisePacket(innerPacket, recipientPeerID)
Log.d(TAG, "Sent read receipt for message $messageID to $recipientPeerID")
} catch (e: Exception) {
Log.e(TAG, "Failed to send read receipt for message $messageID: ${e.message}")
}
}
}
/**
* Encrypt a BitchatPacket and broadcast it as a NOISE_ENCRYPTED message
* This is the correct protocol implementation - encrypt the entire packet, not just the payload
@@ -644,14 +685,6 @@ class BluetoothMeshService(private val context: Context) {
payload = nickname.toByteArray()
)
// Send multiple times for reliability
delay(Random.nextLong(0, 500))
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
delay(500 + Random.nextLong(0, 500))
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
delay(1000 + Random.nextLong(0, 500))
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
}
}
@@ -58,19 +58,12 @@ class MessageHandler(private val myPeerID: String) {
// Check if this is a delivery ACK with the new format
if (typeMarker == MessageType.DELIVERY_ACK.value) {
// Extract the ACK JSON data (skip the type marker)
val ackData = decryptedData.sliceArray(1 until decryptedData.size)
// Decode the delivery ACK
val ack = DeliveryAck.decode(ackData)
if (ack != null) {
delegate?.onDeliveryAckReceived(ack)
Log.d(TAG, "Processed delivery ACK from $peerID")
return
}
handleDeliveryAck(decryptedData)
Log.d(TAG, "Processed delivery ACK from $peerID")
}
// Check for read receipt with type marker
// NOTE: THIS DOESN'T WORK WITH IOS, IT SENDS AN INNER PACKET INSTEAD
if (typeMarker == MessageType.READ_RECEIPT.value) {
val receiptData = decryptedData.sliceArray(1 until decryptedData.size)
val receipt = ReadReceipt.decode(receiptData)
@@ -268,7 +261,7 @@ class MessageHandler(private val myPeerID: String) {
delegate?.onMessageReceived(message)
// Send delivery ACK
// delegate?.sendDeliveryAck(message, peerID)
delegate?.sendDeliveryAck(message, peerID)
}
} catch (e: Exception) {
@@ -302,23 +295,13 @@ class MessageHandler(private val myPeerID: String) {
/**
* Handle delivery acknowledgment
*/
suspend fun handleDeliveryAck(routed: RoutedPacket) {
val packet = routed.packet
val peerID = routed.peerID ?: "unknown"
if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
try {
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
if (decryptedData != null) {
val ack = DeliveryAck.decode(decryptedData)
if (ack != null) {
delegate?.onDeliveryAckReceived(ack)
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to decrypt delivery ACK: ${e.message}")
}
suspend fun handleDeliveryAck(decryptedData: ByteArray) {
val ackData = decryptedData.sliceArray(1 until decryptedData.size)
val ack = DeliveryAck.decode(ackData)
if (ack != null) {
delegate?.onDeliveryAckReceived(ack)
}
// Delivery ACK relay is now handled by centralized PacketRelayManager
return
}
/**
@@ -327,20 +310,11 @@ class MessageHandler(private val myPeerID: String) {
suspend fun handleReadReceipt(routed: RoutedPacket) {
val packet = routed.packet
val peerID = routed.peerID ?: "unknown"
if (packet.recipientID != null && String(packet.recipientID).replace("\u0000", "") == myPeerID) {
try {
val decryptedData = delegate?.decryptFromPeer(packet.payload, peerID)
if (decryptedData != null) {
val receipt = ReadReceipt.decode(decryptedData)
if (receipt != null) {
delegate?.onReadReceiptReceived(receipt)
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to decrypt read receipt: ${e.message}")
}
val receipt = ReadReceipt.decode(routed.packet.payload)
if (receipt != null) {
delegate?.onReadReceiptReceived(receipt)
}
// Read receipt relay is now handled by centralized PacketRelayManager
return
}
/**
@@ -139,7 +139,7 @@ class PacketProcessor(private val myPeerID: String) {
MessageType.NOISE_HANDSHAKE_INIT -> handleNoiseHandshake(routed, 1)
MessageType.NOISE_HANDSHAKE_RESP -> handleNoiseHandshake(routed, 2)
MessageType.NOISE_ENCRYPTED -> handleNoiseEncrypted(routed)
MessageType.DELIVERY_ACK -> handleDeliveryAck(routed)
//MessageType.DELIVERY_ACK -> handleDeliveryAck(routed) // custom packet type...
MessageType.READ_RECEIPT -> handleReadReceipt(routed)
else -> {
validPacket = false
@@ -234,11 +234,11 @@ class PacketProcessor(private val myPeerID: String) {
/**
* 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)
}
// private suspend fun handleDeliveryAck(routed: RoutedPacket) {
// val peerID = routed.peerID ?: "unknown"
// Log.d(TAG, "Processing delivery ACK from ${formatPeerForLog(peerID)}")
// delegate?.handleDeliveryAck(routed)
// }
/**
* Handle read receipt
@@ -313,7 +313,7 @@ interface PacketProcessorDelegate {
fun handleMessage(routed: RoutedPacket)
fun handleLeave(routed: RoutedPacket)
fun handleFragment(packet: BitchatPacket): BitchatPacket?
fun handleDeliveryAck(routed: RoutedPacket)
// fun handleDeliveryAck(routed: RoutedPacket)
fun handleReadReceipt(routed: RoutedPacket)
// Communication