Files
bitchat-android/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt
T
2025-07-22 12:04:55 +02:00

822 lines
32 KiB
Kotlin

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.RoutedPacket
import com.bitchat.android.model.DeliveryAck
import com.bitchat.android.model.ReadReceipt
import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.MessageType
import com.bitchat.android.protocol.SpecialRecipients
import com.bitchat.android.util.toHexString
import kotlinx.coroutines.*
import java.util.*
import kotlin.math.sign
import kotlin.random.Random
/**
* Bluetooth mesh service - REFACTORED to use component-based architecture
* 100% compatible with iOS version and maintains exact same UUIDs, packet format, and protocol logic
*
* This is now a coordinator that orchestrates the following components:
* - PeerManager: Peer lifecycle management
* - FragmentManager: Message fragmentation and reassembly
* - SecurityManager: Security, duplicate detection, encryption
* - StoreForwardManager: Offline message caching
* - MessageHandler: Message type processing and relay logic
* - BluetoothConnectionManager: BLE connections and GATT operations
* - PacketProcessor: Incoming packet routing
*/
class BluetoothMeshService(private val context: Context) {
companion object {
private const val TAG = "BluetoothMeshService"
private const val MAX_TTL: UByte = 7u
}
// My peer identification - same format as iOS
val myPeerID: String = generateCompatiblePeerID()
// Core components - each handling specific responsibilities
private val encryptionService = EncryptionService(context)
private val peerManager = PeerManager()
private val fragmentManager = FragmentManager()
private val securityManager = SecurityManager(encryptionService, myPeerID)
private val storeForwardManager = StoreForwardManager()
private val messageHandler = MessageHandler(myPeerID)
internal val connectionManager = BluetoothConnectionManager(context, myPeerID, fragmentManager) // Made internal for access
private val packetProcessor = PacketProcessor(myPeerID)
// Service state management
private var isActive = false
// Delegate for message callbacks (maintains same interface)
var delegate: BluetoothMeshDelegate? = null
// Coroutines
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init {
setupDelegates()
// Wire up PacketProcessor reference for recursive handling in MessageHandler
messageHandler.packetProcessor = packetProcessor
startPeriodicDebugLogging()
}
/**
* Start periodic debug logging every 10 seconds
*/
private fun startPeriodicDebugLogging() {
serviceScope.launch {
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}")
}
}
}
}
/**
* Setup delegate connections between components
*/
private fun setupDelegates() {
// PeerManager delegates to main mesh service delegate
peerManager.delegate = object : PeerManagerDelegate {
override fun onPeerConnected(nickname: String) {
delegate?.didConnectToPeer(nickname)
}
override fun onPeerDisconnected(nickname: String) {
delegate?.didDisconnectFromPeer(nickname)
}
override fun onPeerListUpdated(peerIDs: List<String>) {
delegate?.didUpdatePeerList(peerIDs)
}
}
// SecurityManager delegate for key exchange notifications
securityManager.delegate = object : SecurityManagerDelegate {
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) {
// Notify delegate about key exchange completion so it can register peer fingerprint
delegate?.registerPeerPublicKey(peerID, peerPublicKeyData)
receivedAddress?.let { address ->
connectionManager.addressPeerMap[address] = peerID
}
// Send announcement and cached messages after key exchange
serviceScope.launch {
delay(100)
sendAnnouncementToPeer(peerID)
delay(500)
storeForwardManager.sendCachedMessages(peerID)
}
}
override fun sendHandshakeResponse(peerID: String, response: ByteArray) {
// Send Noise handshake response
val responsePacket = BitchatPacket(
version = 1u,
type = MessageType.NOISE_HANDSHAKE_RESP.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(peerID),
timestamp = System.currentTimeMillis().toULong(),
payload = response,
ttl = 1u
)
connectionManager.broadcastPacket(RoutedPacket(responsePacket))
Log.d(TAG, "Sent Noise handshake response to $peerID (${response.size} bytes)")
}
}
// StoreForwardManager delegates
storeForwardManager.delegate = object : StoreForwardManagerDelegate {
override fun isFavorite(peerID: String): Boolean {
return delegate?.isFavorite(peerID) ?: false
}
override fun isPeerOnline(peerID: String): Boolean {
return peerManager.isPeerActive(peerID)
}
override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(RoutedPacket(packet))
}
}
// MessageHandler delegates
messageHandler.delegate = object : MessageHandlerDelegate {
// Peer management
override fun addOrUpdatePeer(peerID: String, nickname: String): Boolean {
return peerManager.addOrUpdatePeer(peerID, nickname)
}
override fun removePeer(peerID: String) {
peerManager.removePeer(peerID)
}
override fun updatePeerNickname(peerID: String, nickname: String) {
peerManager.addOrUpdatePeer(peerID, nickname)
}
override fun getPeerNickname(peerID: String): String? {
return peerManager.getPeerNickname(peerID)
}
override fun getNetworkSize(): Int {
return peerManager.getActivePeerCount()
}
override fun getMyNickname(): String? {
return delegate?.getNickname()
}
// Packet operations
override fun sendPacket(packet: BitchatPacket) {
connectionManager.broadcastPacket(RoutedPacket(packet))
}
override fun relayPacket(routed: RoutedPacket) {
connectionManager.broadcastPacket(routed)
}
override fun getBroadcastRecipient(): ByteArray {
return SpecialRecipients.BROADCAST
}
// Cryptographic operations
override fun verifySignature(packet: BitchatPacket, peerID: String): Boolean {
return securityManager.verifySignature(packet, peerID)
}
override fun encryptForPeer(data: ByteArray, recipientPeerID: String): ByteArray? {
return securityManager.encryptForPeer(data, recipientPeerID)
}
override fun decryptFromPeer(encryptedData: ByteArray, senderPeerID: String): ByteArray? {
return securityManager.decryptFromPeer(encryptedData, senderPeerID)
}
// Noise protocol operations
override fun hasNoiseSession(peerID: String): Boolean {
return encryptionService.hasEstablishedSession(peerID)
}
override fun initiateNoiseHandshake(peerID: String) {
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,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(peerID),
timestamp = System.currentTimeMillis().toULong(),
payload = handshakeData,
ttl = 1u
)
connectionManager.broadcastPacket(RoutedPacket(packet))
Log.d(TAG, "Initiated Noise handshake with $peerID (${handshakeData.size} bytes)")
} else {
Log.w(TAG, "Failed to generate Noise handshake data for $peerID")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to initiate Noise handshake with $peerID: ${e.message}")
}
}
override fun updatePeerIDBinding(newPeerID: String, fingerprint: String, nickname: String,
publicKey: ByteArray, previousPeerID: String?) {
// Update peer mapping in the PeerManager for peer ID rotation support
peerManager.addOrUpdatePeer(newPeerID, nickname)
// If there was a previous peer ID, remove it to avoid duplicates
previousPeerID?.let { oldPeerID ->
peerManager.removePeer(oldPeerID)
}
// Register the public key with the delegate (ChatViewModel)
delegate?.registerPeerPublicKey(newPeerID, publicKey)
Log.d(TAG, "Updated peer ID binding: $newPeerID (was: $previousPeerID), fingerprint: ${fingerprint.take(16)}...")
}
// Message operations
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
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)
}
override fun onChannelLeave(channel: String, fromPeer: String) {
delegate?.didReceiveChannelLeave(channel, fromPeer)
}
override fun onPeerDisconnected(nickname: String) {
delegate?.didDisconnectFromPeer(nickname)
}
override fun onDeliveryAckReceived(ack: DeliveryAck) {
delegate?.didReceiveDeliveryAck(ack)
}
override fun onReadReceiptReceived(receipt: ReadReceipt) {
delegate?.didReceiveReadReceipt(receipt)
}
}
// PacketProcessor delegates
packetProcessor.delegate = object : PacketProcessorDelegate {
override fun validatePacketSecurity(packet: BitchatPacket, peerID: String): Boolean {
return securityManager.validatePacket(packet, peerID)
}
override fun updatePeerLastSeen(peerID: String) {
peerManager.updatePeerLastSeen(peerID)
}
override fun handleNoiseHandshake(routed: RoutedPacket, step: Int): Boolean {
return runBlocking { securityManager.handleNoiseHandshake(routed, step) }
}
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) }
}
override fun handleMessage(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleMessage(routed) }
}
override fun handleLeave(routed: RoutedPacket) {
serviceScope.launch { messageHandler.handleLeave(routed) }
}
override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
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)
}
override fun sendCachedMessages(peerID: String) {
storeForwardManager.sendCachedMessages(peerID)
}
override fun relayPacket(routed: RoutedPacket) {
connectionManager.broadcastPacket(routed)
}
}
// BluetoothConnectionManager delegates
connectionManager.delegate = object : BluetoothConnectionManagerDelegate {
override fun onPacketReceived(packet: BitchatPacket, peerID: String, device: android.bluetooth.BluetoothDevice?) {
packetProcessor.processPacket(RoutedPacket(packet, peerID, device?.address))
}
override fun onDeviceConnected(device: android.bluetooth.BluetoothDevice) {
// Send initial announcements after services are ready
serviceScope.launch {
delay(100)
sendBroadcastAnnounce()
}
// Send key exchange to newly connected device
serviceScope.launch {
delay(100) // Ensure connection is stable
sendKeyExchangeToDevice()
}
}
override fun onRSSIUpdated(deviceAddress: String, rssi: Int) {
// Find the peer ID for this device address and update RSSI in PeerManager
connectionManager.addressPeerMap[deviceAddress]?.let { peerID ->
peerManager.updatePeerRSSI(peerID, rssi)
}
}
}
}
/**
* Start the mesh service
*/
fun startServices() {
// Prevent double starts (defensive programming)
if (isActive) {
Log.w(TAG, "Mesh service already active, ignoring duplicate start request")
return
}
Log.i(TAG, "Starting Bluetooth mesh service with peer ID: $myPeerID")
if (connectionManager.startServices()) {
isActive = true
} else {
Log.e(TAG, "Failed to start Bluetooth services")
}
}
/**
* Stop all mesh services
*/
fun stopServices() {
if (!isActive) {
Log.w(TAG, "Mesh service not active, ignoring stop request")
return
}
Log.i(TAG, "Stopping Bluetooth mesh service")
isActive = false
// Send leave announcement
sendLeaveAnnouncement()
serviceScope.launch {
delay(200) // Give leave message time to send
// Stop all components
connectionManager.stopServices()
peerManager.shutdown()
fragmentManager.shutdown()
securityManager.shutdown()
storeForwardManager.shutdown()
messageHandler.shutdown()
packetProcessor.shutdown()
serviceScope.cancel()
}
}
/**
* Send public message
*/
fun sendMessage(content: String, mentions: List<String> = emptyList(), channel: String? = null) {
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
)
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))
}
}
}
/**
* Send private message
*/
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 // Will be calculated during relay
)
try {
// TODO: THIS FORMAT FOR DELIVERY ACKS SHOULD BE DEPRECATED
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}")
}
}
/**
* 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(
type = MessageType.NOISE_ENCRYPTED.value,
senderID = hexStringToByteArray(myPeerID),
recipientID = hexStringToByteArray(recipientPeerID),
timestamp = System.currentTimeMillis().toULong(),
payload = encryptedPayload,
signature = null,
ttl = MAX_TTL
)
// Broadcast the encrypted packet
connectionManager.broadcastPacket(RoutedPacket(outerPacket))
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")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to encrypt and broadcast Noise packet to $recipientPeerID: ${e.message}")
}
}
}
/**
* Send broadcast announce
*/
fun sendBroadcastAnnounce() {
serviceScope.launch {
val nickname = delegate?.getNickname() ?: myPeerID
val announcePacket = BitchatPacket(
type = MessageType.ANNOUNCE.value,
ttl = 3u,
senderID = myPeerID,
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))
}
}
/**
* Send announcement to specific peer
*/
private fun sendAnnouncementToPeer(peerID: String) {
if (peerManager.hasAnnouncedToPeer(peerID)) return
val nickname = delegate?.getNickname() ?: myPeerID
val packet = BitchatPacket(
type = MessageType.ANNOUNCE.value,
ttl = 3u,
senderID = myPeerID,
payload = nickname.toByteArray()
)
connectionManager.broadcastPacket(RoutedPacket(packet))
peerManager.markPeerAsAnnouncedTo(peerID)
}
/**
* Send identity announcement (broadcast our static public key)
*/
private fun sendKeyExchangeToDevice() {
// For discovery, broadcast our static identity key (32 bytes) so peers can identify us
// This is not a handshake initiation, but identity announcement
val identityData = encryptionService.getCombinedPublicKeyData()
val packet = BitchatPacket(
version = 1u,
type = MessageType.NOISE_IDENTITY_ANNOUNCE.value, // Changed to identity announce
senderID = hexStringToByteArray(myPeerID),
recipientID = SpecialRecipients.BROADCAST,
timestamp = System.currentTimeMillis().toULong(),
payload = identityData,
ttl = 1u
)
connectionManager.broadcastPacket(RoutedPacket(packet))
Log.d(TAG, "Sent Noise identity announcement (${identityData.size} bytes)")
}
/**
* Send leave announcement
*/
private fun sendLeaveAnnouncement() {
val nickname = delegate?.getNickname() ?: myPeerID
val packet = BitchatPacket(
type = MessageType.LEAVE.value,
ttl = 1u,
senderID = myPeerID,
payload = nickname.toByteArray()
)
connectionManager.broadcastPacket(RoutedPacket(packet))
}
/**
* Get peer nicknames
*/
fun getPeerNicknames(): Map<String, String> = peerManager.getAllPeerNicknames()
/**
* Get peer RSSI values
*/
fun getPeerRSSI(): Map<String, Int> = peerManager.getAllPeerRSSI()
/**
* Check if we have an established Noise session with a peer
*/
fun hasEstablishedSession(peerID: String): Boolean {
return encryptionService.hasEstablishedSession(peerID)
}
/**
* Get peer fingerprint for identity management
*/
fun getPeerFingerprint(peerID: String): String? {
return encryptionService.getPeerFingerprint(peerID)
}
/**
* Get our identity fingerprint
*/
fun getIdentityFingerprint(): String {
return encryptionService.getIdentityFingerprint()
}
/**
* Check if encryption icon should be shown for a peer
*/
fun shouldShowEncryptionIcon(peerID: String): Boolean {
return encryptionService.hasEstablishedSession(peerID)
}
/**
* Get all peers with established encrypted sessions
*/
fun getEncryptedPeers(): List<String> {
// SIMPLIFIED: Return empty list for now since we don't have direct access to sessionManager
// This method is not critical for the session retention fix
return emptyList()
}
/**
* Get device address for a specific peer ID
*/
fun getDeviceAddressForPeer(peerID: String): String? {
return connectionManager.addressPeerMap.entries.find { it.value == peerID }?.key
}
/**
* Get all device addresses mapped to their peer IDs
*/
fun getDeviceAddressToPeerMapping(): Map<String, String> {
return connectionManager.addressPeerMap.toMap()
}
/**
* Print device addresses for all connected peers
*/
fun printDeviceAddressesForPeers(): String {
return peerManager.getDebugInfoWithDeviceAddresses(connectionManager.addressPeerMap)
}
/**
* Get debug status information
*/
fun getDebugStatus(): String {
return buildString {
appendLine("=== Bluetooth Mesh Service Debug Status ===")
appendLine("My Peer ID: $myPeerID")
appendLine()
appendLine(connectionManager.getDebugInfo())
appendLine()
appendLine(peerManager.getDebugInfo(connectionManager.addressPeerMap))
appendLine()
appendLine(fragmentManager.getDebugInfo())
appendLine()
appendLine(securityManager.getDebugInfo())
appendLine()
appendLine(storeForwardManager.getDebugInfo())
appendLine()
appendLine(messageHandler.getDebugInfo())
appendLine()
appendLine(packetProcessor.getDebugInfo())
}
}
/**
* Generate peer ID compatible with iOS - exactly 8 bytes (16 hex characters)
*/
private fun generateCompatiblePeerID(): String {
val randomBytes = ByteArray(8) // 8 bytes = 16 hex characters (like iOS)
Random.nextBytes(randomBytes)
return randomBytes.joinToString("") { "%02x".format(it) }
}
/**
* Convert hex string peer ID to binary data (8 bytes) - exactly same as iOS
*/
private fun hexStringToByteArray(hexString: String): ByteArray {
val result = ByteArray(8) { 0 } // Initialize with zeros, exactly 8 bytes
var tempID = hexString
var index = 0
while (tempID.length >= 2 && index < 8) {
val hexByte = tempID.substring(0, 2)
val byte = hexByte.toIntOrNull(16)?.toByte()
if (byte != null) {
result[index] = byte
}
tempID = tempID.substring(2)
index++
}
return result
}
}
/**
* Delegate interface for mesh service callbacks (maintains exact same interface)
*/
interface BluetoothMeshDelegate {
fun didReceiveMessage(message: BitchatMessage)
fun didConnectToPeer(peerID: String)
fun didDisconnectFromPeer(peerID: String)
fun didUpdatePeerList(peers: List<String>)
fun didReceiveChannelLeave(channel: String, fromPeer: String)
fun didReceiveDeliveryAck(ack: DeliveryAck)
fun didReceiveReadReceipt(receipt: ReadReceipt)
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
fun getNickname(): String?
fun isFavorite(peerID: String): Boolean
fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray)
}