mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 11:25:21 +00:00
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:
@@ -46,7 +46,7 @@ sealed class DeliveryStatus : Parcelable {
|
||||
*/
|
||||
@Parcelize
|
||||
data class BitchatMessage(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val id: String = UUID.randomUUID().toString().uppercase(),
|
||||
val sender: String,
|
||||
val content: String,
|
||||
val timestamp: Date,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.bitchat.android.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.GsonBuilder
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.bitchat.android.util.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Delivery acknowledgment structure - exact same as iOS version
|
||||
* Uses binary encoding for efficient protocol communication
|
||||
*/
|
||||
@Parcelize
|
||||
data class DeliveryAck(
|
||||
@@ -15,29 +16,91 @@ data class DeliveryAck(
|
||||
val recipientID: String,
|
||||
val recipientNickname: String,
|
||||
val timestamp: Date = Date(),
|
||||
val hopCount: UInt
|
||||
val hopCount: UByte
|
||||
) : Parcelable {
|
||||
|
||||
private val gson = GsonBuilder()
|
||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
.create()
|
||||
|
||||
fun encode(): ByteArray? {
|
||||
return try {
|
||||
gson.toJson(this).toByteArray(Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
// Primary constructor for creating new acks
|
||||
constructor(originalMessageID: String, recipientID: String, recipientNickname: String, hopCount: UByte) : this(
|
||||
originalMessageID = originalMessageID,
|
||||
ackID = UUID.randomUUID().toString(),
|
||||
recipientID = recipientID,
|
||||
recipientNickname = recipientNickname,
|
||||
timestamp = Date(),
|
||||
hopCount = hopCount
|
||||
)
|
||||
|
||||
/**
|
||||
* Encode to binary data matching iOS toBinaryData implementation
|
||||
*/
|
||||
fun encode(): ByteArray {
|
||||
val builder = BinaryDataBuilder()
|
||||
|
||||
// Append original message UUID
|
||||
builder.appendUUID(originalMessageID)
|
||||
|
||||
// Append ack ID UUID
|
||||
builder.appendUUID(ackID)
|
||||
|
||||
// Append recipient ID as 8-byte hex string
|
||||
val recipientData = ByteArray(8) { 0 }
|
||||
var tempID = recipientID
|
||||
var index = 0
|
||||
|
||||
while (tempID.length >= 2 && index < 8) {
|
||||
val hexByte = tempID.substring(0, 2)
|
||||
val byte = hexByte.toIntOrNull(16)?.toByte()
|
||||
if (byte != null) {
|
||||
recipientData[index] = byte
|
||||
}
|
||||
tempID = tempID.substring(2)
|
||||
index++
|
||||
}
|
||||
|
||||
builder.buffer.addAll(recipientData.toList())
|
||||
|
||||
// Append hop count (UInt8)
|
||||
builder.appendUInt8(hopCount)
|
||||
|
||||
// Append timestamp
|
||||
builder.appendDate(timestamp)
|
||||
|
||||
// Append recipient nickname as string
|
||||
builder.appendString(recipientNickname)
|
||||
|
||||
return builder.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Decode from binary data matching iOS fromBinaryData implementation
|
||||
*/
|
||||
fun decode(data: ByteArray): DeliveryAck? {
|
||||
return try {
|
||||
val json = String(data, Charsets.UTF_8)
|
||||
com.google.gson.Gson().fromJson(json, DeliveryAck::class.java)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
// Create defensive copy
|
||||
val dataCopy = data.copyOf()
|
||||
|
||||
// Minimum size: 2 UUIDs (32) + recipientID (8) + hopCount (1) + timestamp (8) + min nickname
|
||||
if (dataCopy.size < 50) return null
|
||||
|
||||
val offset = intArrayOf(0)
|
||||
|
||||
val originalMessageID = dataCopy.readUUID(offset) ?: return null
|
||||
val ackID = dataCopy.readUUID(offset) ?: return null
|
||||
|
||||
val recipientIDData = dataCopy.readFixedBytes(offset, 8) ?: return null
|
||||
val recipientID = recipientIDData.hexEncodedString()
|
||||
|
||||
val hopCount = dataCopy.readUInt8(offset) ?: return null
|
||||
val timestamp = dataCopy.readDate(offset) ?: return null
|
||||
val recipientNickname = dataCopy.readString(offset) ?: return null
|
||||
|
||||
return DeliveryAck(
|
||||
originalMessageID = originalMessageID,
|
||||
ackID = ackID,
|
||||
recipientID = recipientID,
|
||||
recipientNickname = recipientNickname,
|
||||
timestamp = timestamp,
|
||||
hopCount = hopCount
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.bitchat.android.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.bitchat.android.util.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Read receipt structure - exact same as iOS version
|
||||
* Uses binary encoding for efficient protocol communication
|
||||
*/
|
||||
@Parcelize
|
||||
data class ReadReceipt(
|
||||
@@ -15,23 +17,83 @@ data class ReadReceipt(
|
||||
val readerNickname: String,
|
||||
val timestamp: Date = Date()
|
||||
) : Parcelable {
|
||||
|
||||
// Primary constructor for creating new read receipts
|
||||
constructor(originalMessageID: String, readerID: String, readerNickname: String) : this(
|
||||
originalMessageID = originalMessageID,
|
||||
receiptID = UUID.randomUUID().toString(),
|
||||
readerID = readerID,
|
||||
readerNickname = readerNickname,
|
||||
timestamp = Date()
|
||||
)
|
||||
|
||||
fun encode(): ByteArray? {
|
||||
return try {
|
||||
com.google.gson.Gson().toJson(this).toByteArray(Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
/**
|
||||
* Encode to binary data matching iOS toBinaryData implementation
|
||||
*/
|
||||
fun encode(): ByteArray {
|
||||
val builder = BinaryDataBuilder()
|
||||
|
||||
// Append original message UUID
|
||||
builder.appendUUID(originalMessageID)
|
||||
|
||||
// Append receipt ID UUID
|
||||
builder.appendUUID(receiptID)
|
||||
|
||||
// Append reader ID as 8-byte hex string
|
||||
val readerData = ByteArray(8) { 0 }
|
||||
var tempID = readerID
|
||||
var index = 0
|
||||
|
||||
while (tempID.length >= 2 && index < 8) {
|
||||
val hexByte = tempID.substring(0, 2)
|
||||
val byte = hexByte.toIntOrNull(16)?.toByte()
|
||||
if (byte != null) {
|
||||
readerData[index] = byte
|
||||
}
|
||||
tempID = tempID.substring(2)
|
||||
index++
|
||||
}
|
||||
|
||||
builder.buffer.addAll(readerData.toList())
|
||||
|
||||
// Append timestamp
|
||||
builder.appendDate(timestamp)
|
||||
|
||||
// Append reader nickname as string
|
||||
builder.appendString(readerNickname)
|
||||
|
||||
return builder.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Decode from binary data matching iOS fromBinaryData implementation
|
||||
*/
|
||||
fun decode(data: ByteArray): ReadReceipt? {
|
||||
return try {
|
||||
val json = String(data, Charsets.UTF_8)
|
||||
com.google.gson.Gson().fromJson(json, ReadReceipt::class.java)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
// Create defensive copy
|
||||
val dataCopy = data.copyOf()
|
||||
|
||||
// Minimum size: 2 UUIDs (32) + readerID (8) + timestamp (8) + min nickname
|
||||
if (dataCopy.size < 49) return null
|
||||
|
||||
val offset = intArrayOf(0)
|
||||
|
||||
val originalMessageID = dataCopy.readUUID(offset) ?: return null
|
||||
val receiptID = dataCopy.readUUID(offset) ?: return null
|
||||
|
||||
val readerIDData = dataCopy.readFixedBytes(offset, 8) ?: return null
|
||||
val readerID = readerIDData.hexEncodedString()
|
||||
|
||||
val timestamp = dataCopy.readDate(offset) ?: return null
|
||||
val readerNickname = dataCopy.readString(offset) ?: return null
|
||||
|
||||
return ReadReceipt(
|
||||
originalMessageID = originalMessageID,
|
||||
receiptID = receiptID,
|
||||
readerID = readerID,
|
||||
readerNickname = readerNickname,
|
||||
timestamp = timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user