mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 01:45:22 +00:00
Changes bitchat protocol (#265)
* create payload * compiles and can send messages * identityannouncement * DMs work, read receipt not sent yet * works * delete old code * simplify * working * fragment wip * compression wip * use zlib compression * clean * nice * mesh * remove comments
This commit is contained in:
@@ -3,13 +3,10 @@ package com.bitchat.android.mesh
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.bitchat.android.crypto.EncryptionService
|
||||
import com.bitchat.android.protocol.MessagePadding
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.HandshakeRequest
|
||||
import com.bitchat.android.protocol.MessagePadding
|
||||
import com.bitchat.android.model.RoutedPacket
|
||||
import com.bitchat.android.model.DeliveryAck
|
||||
import com.bitchat.android.model.ReadReceipt
|
||||
import com.bitchat.android.model.NoiseIdentityAnnouncement
|
||||
import com.bitchat.android.model.IdentityAnnouncement
|
||||
import com.bitchat.android.protocol.BitchatPacket
|
||||
import com.bitchat.android.protocol.MessageType
|
||||
import com.bitchat.android.protocol.SpecialRecipients
|
||||
@@ -64,7 +61,7 @@ class BluetoothMeshService(private val context: Context) {
|
||||
init {
|
||||
setupDelegates()
|
||||
messageHandler.packetProcessor = packetProcessor
|
||||
startPeriodicDebugLogging()
|
||||
//startPeriodicDebugLogging()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +92,6 @@ class BluetoothMeshService(private val context: Context) {
|
||||
try {
|
||||
delay(30000) // 30 seconds
|
||||
sendBroadcastAnnounce()
|
||||
broadcastNoiseIdentityAnnouncement()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in periodic broadcast announce: ${e.message}")
|
||||
}
|
||||
@@ -131,7 +127,7 @@ class BluetoothMeshService(private val context: Context) {
|
||||
// Send Noise handshake response
|
||||
val responsePacket = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.NOISE_HANDSHAKE_RESP.value,
|
||||
type = MessageType.NOISE_HANDSHAKE.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(peerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
@@ -224,18 +220,18 @@ class BluetoothMeshService(private val context: Context) {
|
||||
try {
|
||||
// Initiate proper Noise handshake with specific peer
|
||||
val handshakeData = encryptionService.initiateHandshake(peerID)
|
||||
|
||||
|
||||
if (handshakeData != null) {
|
||||
val packet = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.NOISE_HANDSHAKE_INIT.value,
|
||||
type = MessageType.NOISE_HANDSHAKE.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(peerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = handshakeData,
|
||||
ttl = MAX_TTL
|
||||
)
|
||||
|
||||
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
Log.d(TAG, "Initiated Noise handshake with $peerID (${handshakeData.size} bytes)")
|
||||
} else {
|
||||
@@ -247,6 +243,15 @@ class BluetoothMeshService(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun processNoiseHandshakeMessage(payload: ByteArray, peerID: String): ByteArray? {
|
||||
return try {
|
||||
encryptionService.processHandshakeMessage(payload, peerID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to process handshake message from $peerID: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun updatePeerIDBinding(newPeerID: String, nickname: String,
|
||||
publicKey: ByteArray, previousPeerID: String?) {
|
||||
|
||||
@@ -270,10 +275,6 @@ class BluetoothMeshService(private val context: Context) {
|
||||
return delegate?.decryptChannelMessage(encryptedContent, channel)
|
||||
}
|
||||
|
||||
override fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) {
|
||||
this@BluetoothMeshService.sendDeliveryAck(message, senderPeerID)
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
override fun onMessageReceived(message: BitchatMessage) {
|
||||
delegate?.didReceiveMessage(message)
|
||||
@@ -283,12 +284,12 @@ class BluetoothMeshService(private val context: Context) {
|
||||
delegate?.didReceiveChannelLeave(channel, fromPeer)
|
||||
}
|
||||
|
||||
override fun onDeliveryAckReceived(ack: DeliveryAck) {
|
||||
delegate?.didReceiveDeliveryAck(ack)
|
||||
override fun onDeliveryAckReceived(messageID: String, peerID: String) {
|
||||
delegate?.didReceiveDeliveryAck(messageID, peerID)
|
||||
}
|
||||
|
||||
override fun onReadReceiptReceived(receipt: ReadReceipt) {
|
||||
delegate?.didReceiveReadReceipt(receipt)
|
||||
override fun onReadReceiptReceived(messageID: String, peerID: String) {
|
||||
delegate?.didReceiveReadReceipt(messageID, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,18 +316,14 @@ class BluetoothMeshService(private val context: Context) {
|
||||
return SpecialRecipients.BROADCAST
|
||||
}
|
||||
|
||||
override fun handleNoiseHandshake(routed: RoutedPacket, step: Int): Boolean {
|
||||
return runBlocking { securityManager.handleNoiseHandshake(routed, step) }
|
||||
override fun handleNoiseHandshake(routed: RoutedPacket): Boolean {
|
||||
return runBlocking { securityManager.handleNoiseHandshake(routed) }
|
||||
}
|
||||
|
||||
override fun handleNoiseEncrypted(routed: RoutedPacket) {
|
||||
serviceScope.launch { messageHandler.handleNoiseEncrypted(routed) }
|
||||
}
|
||||
|
||||
override fun handleNoiseIdentityAnnouncement(routed: RoutedPacket) {
|
||||
serviceScope.launch { messageHandler.handleNoiseIdentityAnnouncement(routed) }
|
||||
}
|
||||
|
||||
override fun handleAnnounce(routed: RoutedPacket) {
|
||||
serviceScope.launch { messageHandler.handleAnnounce(routed) }
|
||||
}
|
||||
@@ -343,14 +340,6 @@ class BluetoothMeshService(private val context: Context) {
|
||||
return fragmentManager.handleFragment(packet)
|
||||
}
|
||||
|
||||
// override fun handleDeliveryAck(routed: RoutedPacket) {
|
||||
// serviceScope.launch { messageHandler.handleDeliveryAck(routed) }
|
||||
// }
|
||||
|
||||
override fun handleReadReceipt(routed: RoutedPacket) {
|
||||
serviceScope.launch { messageHandler.handleReadReceipt(routed) }
|
||||
}
|
||||
|
||||
override fun sendAnnouncementToPeer(peerID: String) {
|
||||
this@BluetoothMeshService.sendAnnouncementToPeer(peerID)
|
||||
}
|
||||
@@ -376,11 +365,6 @@ class BluetoothMeshService(private val context: Context) {
|
||||
delay(200)
|
||||
sendBroadcastAnnounce()
|
||||
}
|
||||
// Send key exchange to newly connected device
|
||||
serviceScope.launch {
|
||||
delay(400) // Ensure connection is stable
|
||||
broadcastNoiseIdentityAnnouncement()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRSSIUpdated(deviceAddress: String, rssi: Int) {
|
||||
@@ -453,371 +437,199 @@ class BluetoothMeshService(private val context: Context) {
|
||||
if (content.isEmpty()) return
|
||||
|
||||
serviceScope.launch {
|
||||
val nickname = delegate?.getNickname() ?: myPeerID
|
||||
|
||||
val message = BitchatMessage(
|
||||
sender = nickname,
|
||||
content = content,
|
||||
timestamp = Date(),
|
||||
isRelay = false,
|
||||
senderPeerID = myPeerID,
|
||||
mentions = if (mentions.isNotEmpty()) mentions else null,
|
||||
channel = channel
|
||||
val packet = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.MESSAGE.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = SpecialRecipients.BROADCAST,
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = content.toByteArray(Charsets.UTF_8),
|
||||
signature = null,
|
||||
ttl = MAX_TTL
|
||||
)
|
||||
|
||||
message.toBinaryPayload()?.let { messageData ->
|
||||
// Sign the message: TODO: NOT SIGNED
|
||||
// val signature = securityManager.signPacket(messageData)
|
||||
|
||||
val packet = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.MESSAGE.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = SpecialRecipients.BROADCAST,
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = messageData,
|
||||
signature = null,
|
||||
ttl = MAX_TTL
|
||||
)
|
||||
|
||||
// Send with random delay and retry for reliability
|
||||
// delay(Random.nextLong(50, 500))
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
}
|
||||
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send private message
|
||||
* Send private message - SIMPLIFIED iOS-compatible version
|
||||
* Uses NoisePayloadType system exactly like iOS SimplifiedBluetoothService
|
||||
*/
|
||||
fun sendPrivateMessage(content: String, recipientPeerID: String, recipientNickname: String, messageID: String? = null) {
|
||||
if (content.isEmpty() || recipientPeerID.isEmpty() || recipientNickname.isEmpty()) return
|
||||
|
||||
val nickname = delegate?.getNickname() ?: myPeerID
|
||||
|
||||
val message = BitchatMessage(
|
||||
id = messageID ?: UUID.randomUUID().toString(),
|
||||
sender = nickname,
|
||||
content = content,
|
||||
timestamp = Date(),
|
||||
isRelay = false,
|
||||
isPrivate = true,
|
||||
recipientNickname = recipientNickname,
|
||||
senderPeerID = myPeerID
|
||||
)
|
||||
|
||||
message.toBinaryPayload()?.let { messageData ->
|
||||
try {
|
||||
|
||||
// Create inner packet with the padded message data
|
||||
val innerPacket = BitchatPacket(
|
||||
type = MessageType.MESSAGE.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(recipientPeerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = messageData,
|
||||
signature = null,
|
||||
ttl = MAX_TTL
|
||||
)
|
||||
|
||||
// Cache for offline favorites
|
||||
if (storeForwardManager.shouldCacheForPeer(recipientPeerID)) {
|
||||
storeForwardManager.cacheMessage(innerPacket, messageID ?: message.id)
|
||||
}
|
||||
|
||||
// Use the new encrypt and broadcast function
|
||||
encryptAndBroadcastNoisePacket(innerPacket, recipientPeerID)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send private message: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send delivery acknowledgment for a received private message
|
||||
*/
|
||||
fun sendDeliveryAck(message: BitchatMessage, senderPeerID: String) {
|
||||
val nickname = delegate?.getNickname() ?: myPeerID
|
||||
val ack = DeliveryAck(
|
||||
originalMessageID = message.id,
|
||||
recipientID = myPeerID,
|
||||
recipientNickname = nickname,
|
||||
hopCount = 0u.toUByte() // Will be calculated during relay
|
||||
)
|
||||
|
||||
try {
|
||||
val ackData = ack.encode() ?: return
|
||||
val typeMarker = MessageType.DELIVERY_ACK.value.toByte()
|
||||
val payloadWithMarker = byteArrayOf(typeMarker) + ackData
|
||||
val encryptedPayload = securityManager.encryptForPeer(payloadWithMarker, senderPeerID)
|
||||
|
||||
if (encryptedPayload == null) {
|
||||
Log.w(TAG, "Failed to encrypt delivery ACK for $senderPeerID")
|
||||
return
|
||||
}
|
||||
|
||||
// Create inner packet with the delivery ACK data
|
||||
val packet = BitchatPacket(
|
||||
type = MessageType.NOISE_ENCRYPTED.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(senderPeerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = encryptedPayload,
|
||||
signature = null,
|
||||
ttl = 3u
|
||||
)
|
||||
|
||||
// Use the new encrypt and broadcast function
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send delivery ACK: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send read receipt for a received private message
|
||||
*/
|
||||
fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) {
|
||||
serviceScope.launch {
|
||||
// Create the read receipt
|
||||
val receipt = ReadReceipt(
|
||||
originalMessageID = messageID,
|
||||
readerID = myPeerID,
|
||||
readerNickname = readerNickname
|
||||
)
|
||||
|
||||
try {
|
||||
// Encode the receipt
|
||||
val receiptData = receipt.encode()
|
||||
val typeMarker = MessageType.READ_RECEIPT.value.toByte()
|
||||
val payloadWithMarker = byteArrayOf(typeMarker) + receiptData
|
||||
val encryptedPayload = securityManager.encryptForPeer(payloadWithMarker, recipientPeerID)
|
||||
|
||||
if (encryptedPayload == null) {
|
||||
Log.w(TAG, "Failed to encrypt delivery ACK for $recipientPeerID")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Create inner packet with the delivery ACK data
|
||||
val packet = BitchatPacket(
|
||||
type = MessageType.NOISE_ENCRYPTED.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(recipientPeerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = encryptedPayload,
|
||||
signature = null,
|
||||
ttl = 3u
|
||||
)
|
||||
|
||||
Log.d(TAG, "Sending read receipt for message $messageID to $recipientPeerID")
|
||||
|
||||
// Use the new encrypt and broadcast function
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
|
||||
} 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
|
||||
*/
|
||||
private fun encryptAndBroadcastNoisePacket(innerPacket: BitchatPacket, recipientPeerID: String) {
|
||||
serviceScope.launch {
|
||||
try {
|
||||
// Serialize the inner packet to binary data
|
||||
val innerPacketData = innerPacket.toBinaryData()
|
||||
if (innerPacketData == null) {
|
||||
Log.e(TAG, "Failed to serialize inner packet for encryption")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Encrypt the serialized packet using Noise encryption
|
||||
val encryptedPayload = securityManager.encryptForPeer(innerPacketData, recipientPeerID)
|
||||
|
||||
if (encryptedPayload != null) {
|
||||
// Create the outer NOISE_ENCRYPTED packet
|
||||
val outerPacket = BitchatPacket(
|
||||
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 {
|
||||
// Create TLV-encoded private message exactly like iOS
|
||||
val privateMessage = com.bitchat.android.model.PrivateMessagePacket(
|
||||
messageID = finalMessageID,
|
||||
content = content
|
||||
)
|
||||
|
||||
val tlvData = privateMessage.encode()
|
||||
if (tlvData == null) {
|
||||
Log.e(TAG, "Failed to encode private message with TLV")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Create message payload with NoisePayloadType prefix: [type byte] + [TLV data]
|
||||
val messagePayload = com.bitchat.android.model.NoisePayload(
|
||||
type = com.bitchat.android.model.NoisePayloadType.PRIVATE_MESSAGE,
|
||||
data = tlvData
|
||||
)
|
||||
|
||||
// Encrypt the payload
|
||||
val encrypted = encryptionService.encrypt(messagePayload.encode(), recipientPeerID)
|
||||
|
||||
// Create NOISE_ENCRYPTED packet exactly like iOS
|
||||
val packet = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.NOISE_ENCRYPTED.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(recipientPeerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = encryptedPayload,
|
||||
payload = encrypted,
|
||||
signature = null,
|
||||
ttl = MAX_TTL
|
||||
)
|
||||
|
||||
// Broadcast the encrypted packet
|
||||
connectionManager.broadcastPacket(RoutedPacket(outerPacket))
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
Log.d(TAG, "📤 Sent encrypted private message to $recipientPeerID (${encrypted.size} bytes)")
|
||||
|
||||
Log.d(TAG, "Encrypted and sent packet type ${innerPacket.type} to $recipientPeerID (${encryptedPayload.size} bytes encrypted)")
|
||||
} else {
|
||||
Log.w(TAG, "Failed to encrypt packet for $recipientPeerID - no session available")
|
||||
// FIXED: Don't send didReceiveMessage for our own sent messages
|
||||
// This was causing self-notifications - iOS doesn't do this
|
||||
// The UI handles showing sent messages through its own message sending logic
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to encrypt private message for $recipientPeerID: ${e.message}")
|
||||
}
|
||||
} 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)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to encrypt and broadcast Noise packet to $recipientPeerID: ${e.message}")
|
||||
// FIXED: Don't send didReceiveMessage for our own sent messages
|
||||
// The UI will handle showing the message in the chat interface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast announce
|
||||
* Send read receipt for a received private message - NEW NoisePayloadType implementation
|
||||
* Uses same encryption approach as iOS SimplifiedBluetoothService
|
||||
*/
|
||||
fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) {
|
||||
serviceScope.launch {
|
||||
Log.d(TAG, "📖 Sending read receipt for message $messageID to $recipientPeerID")
|
||||
|
||||
try {
|
||||
// Create read receipt payload using NoisePayloadType exactly like iOS
|
||||
val readReceiptPayload = com.bitchat.android.model.NoisePayload(
|
||||
type = com.bitchat.android.model.NoisePayloadType.READ_RECEIPT,
|
||||
data = messageID.toByteArray(Charsets.UTF_8)
|
||||
)
|
||||
|
||||
// Encrypt the payload
|
||||
val encrypted = encryptionService.encrypt(readReceiptPayload.encode(), recipientPeerID)
|
||||
|
||||
// Create NOISE_ENCRYPTED packet exactly like iOS
|
||||
val packet = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.NOISE_ENCRYPTED.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(recipientPeerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = encrypted,
|
||||
signature = null,
|
||||
ttl = 7u // Same TTL as iOS messageTTL
|
||||
)
|
||||
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
Log.d(TAG, "📤 Sent read receipt to $recipientPeerID for message $messageID")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send read receipt to $recipientPeerID: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast announce with TLV-encoded identity announcement - exactly like iOS
|
||||
*/
|
||||
fun sendBroadcastAnnounce() {
|
||||
Log.d(TAG, "Sending broadcast announce")
|
||||
serviceScope.launch {
|
||||
val nickname = delegate?.getNickname() ?: myPeerID
|
||||
|
||||
// Get the static public key for the announcement
|
||||
val staticKey = encryptionService.getStaticPublicKey()
|
||||
if (staticKey == null) {
|
||||
Log.e(TAG, "No static public key available for announcement")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Create iOS-compatible IdentityAnnouncement with TLV encoding
|
||||
val announcement = IdentityAnnouncement(nickname, staticKey)
|
||||
val tlvPayload = announcement.encode()
|
||||
if (tlvPayload == null) {
|
||||
Log.e(TAG, "Failed to encode announcement as TLV")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val announcePacket = BitchatPacket(
|
||||
type = MessageType.ANNOUNCE.value,
|
||||
ttl = MAX_TTL,
|
||||
senderID = myPeerID,
|
||||
payload = nickname.toByteArray()
|
||||
payload = tlvPayload
|
||||
)
|
||||
|
||||
connectionManager.broadcastPacket(RoutedPacket(announcePacket))
|
||||
Log.d(TAG, "Sent iOS-compatible TLV announce (${tlvPayload.size} bytes)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send announcement to specific peer
|
||||
* Send announcement to specific peer with TLV-encoded identity announcement - exactly like iOS
|
||||
*/
|
||||
private fun sendAnnouncementToPeer(peerID: String) {
|
||||
fun sendAnnouncementToPeer(peerID: String) {
|
||||
if (peerManager.hasAnnouncedToPeer(peerID)) return
|
||||
|
||||
val nickname = delegate?.getNickname() ?: myPeerID
|
||||
|
||||
// Get the static public key for the announcement
|
||||
val staticKey = encryptionService.getStaticPublicKey()
|
||||
if (staticKey == null) {
|
||||
Log.e(TAG, "No static public key available for peer announcement")
|
||||
return
|
||||
}
|
||||
|
||||
// Create iOS-compatible IdentityAnnouncement with TLV encoding
|
||||
val announcement = IdentityAnnouncement(nickname, staticKey)
|
||||
val tlvPayload = announcement.encode()
|
||||
if (tlvPayload == null) {
|
||||
Log.e(TAG, "Failed to encode peer announcement as TLV")
|
||||
return
|
||||
}
|
||||
|
||||
val packet = BitchatPacket(
|
||||
type = MessageType.ANNOUNCE.value,
|
||||
ttl = MAX_TTL,
|
||||
senderID = myPeerID,
|
||||
payload = nickname.toByteArray()
|
||||
payload = tlvPayload
|
||||
)
|
||||
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
peerManager.markPeerAsAnnouncedTo(peerID)
|
||||
Log.d(TAG, "Sent iOS-compatible TLV peer announce to $peerID (${tlvPayload.size} bytes)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Send key exchange to newly connected device
|
||||
*/
|
||||
fun broadcastNoiseIdentityAnnouncement() {
|
||||
serviceScope.launch {
|
||||
try {
|
||||
val nickname = delegate?.getNickname() ?: myPeerID
|
||||
|
||||
// Create the identity announcement using proper binary format
|
||||
val announcement = createNoiseIdentityAnnouncement(nickname, null)
|
||||
if (announcement != null) {
|
||||
val announcementData = announcement.toBinaryData()
|
||||
|
||||
val packet = BitchatPacket(
|
||||
type = MessageType.NOISE_IDENTITY_ANNOUNCE.value,
|
||||
ttl = MAX_TTL,
|
||||
senderID = myPeerID,
|
||||
payload = announcementData,
|
||||
)
|
||||
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
Log.d(TAG, "Sent NoiseIdentityAnnouncement (${announcementData.size} bytes)")
|
||||
} else {
|
||||
Log.e(TAG, "Failed to create NoiseIdentityAnnouncement")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send NoiseIdentityAnnouncement: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send handshake request to target peer for pending messages
|
||||
*/
|
||||
fun sendHandshakeRequest(targetPeerID: String, pendingCount: UByte) {
|
||||
serviceScope.launch {
|
||||
try {
|
||||
// Create handshake request
|
||||
val request = HandshakeRequest(
|
||||
requesterID = myPeerID,
|
||||
requesterNickname = delegate?.getNickname() ?: myPeerID,
|
||||
targetID = targetPeerID,
|
||||
pendingMessageCount = pendingCount
|
||||
)
|
||||
|
||||
val requestData = request.toBinaryData()
|
||||
|
||||
// Create packet for handshake request
|
||||
val packet = BitchatPacket(
|
||||
version = 1u,
|
||||
type = MessageType.HANDSHAKE_REQUEST.value,
|
||||
senderID = hexStringToByteArray(myPeerID),
|
||||
recipientID = hexStringToByteArray(targetPeerID),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = requestData,
|
||||
ttl = 6u
|
||||
)
|
||||
|
||||
// Broadcast the packet (Android equivalent of both direct and relay attempts)
|
||||
connectionManager.broadcastPacket(RoutedPacket(packet))
|
||||
Log.d(TAG, "Sent handshake request to $targetPeerID (pending: $pendingCount, ${requestData.size} bytes)")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send handshake request to $targetPeerID: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a properly formatted NoiseIdentityAnnouncement exactly like iOS
|
||||
*/
|
||||
private fun createNoiseIdentityAnnouncement(nickname: String, previousPeerID: String?): NoiseIdentityAnnouncement? {
|
||||
return try {
|
||||
// Get the static public key for Noise protocol
|
||||
val staticKey = encryptionService.getStaticPublicKey()
|
||||
if (staticKey == null) {
|
||||
Log.e(TAG, "No static public key available for identity announcement")
|
||||
return null
|
||||
}
|
||||
|
||||
// Get the signing public key for Ed25519 signatures
|
||||
val signingKey = encryptionService.getSigningPublicKey()
|
||||
if (signingKey == null) {
|
||||
Log.e(TAG, "No signing public key available for identity announcement")
|
||||
return null
|
||||
}
|
||||
|
||||
val now = Date()
|
||||
|
||||
// Create the binding data to sign (same format as iOS)
|
||||
val timestampMs = now.time
|
||||
val bindingData = myPeerID.toByteArray(Charsets.UTF_8) +
|
||||
staticKey +
|
||||
timestampMs.toString().toByteArray(Charsets.UTF_8)
|
||||
|
||||
// Sign the binding with our Ed25519 signing key
|
||||
val signature = encryptionService.signData(bindingData) ?: ByteArray(0)
|
||||
|
||||
// Create the identity announcement
|
||||
NoiseIdentityAnnouncement(
|
||||
peerID = myPeerID,
|
||||
publicKey = staticKey,
|
||||
signingPublicKey = signingKey,
|
||||
nickname = nickname,
|
||||
timestamp = now,
|
||||
previousPeerID = previousPeerID,
|
||||
signature = signature
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to create NoiseIdentityAnnouncement: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send leave announcement
|
||||
*/
|
||||
@@ -1014,8 +826,8 @@ interface BluetoothMeshDelegate {
|
||||
fun didReceiveMessage(message: BitchatMessage)
|
||||
fun didUpdatePeerList(peers: List<String>)
|
||||
fun didReceiveChannelLeave(channel: String, fromPeer: String)
|
||||
fun didReceiveDeliveryAck(ack: DeliveryAck)
|
||||
fun didReceiveReadReceipt(receipt: ReadReceipt)
|
||||
fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String)
|
||||
fun didReceiveReadReceipt(messageID: String, recipientPeerID: String)
|
||||
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
|
||||
fun getNickname(): String?
|
||||
fun isFavorite(peerID: String): Boolean
|
||||
|
||||
Reference in New Issue
Block a user