mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 04:25:22 +00:00
Migrate private media without silent downgrades
This commit is contained in:
@@ -2,6 +2,8 @@ package com.bitchat.android.mesh
|
||||
|
||||
import android.os.Build
|
||||
import com.bitchat.android.model.IdentityAnnouncement
|
||||
import com.bitchat.android.model.BitchatFilePacket
|
||||
import com.bitchat.android.model.PeerCapabilities
|
||||
import com.bitchat.android.model.RoutedPacket
|
||||
import com.bitchat.android.protocol.BitchatPacket
|
||||
import com.bitchat.android.protocol.MessageType
|
||||
@@ -16,6 +18,7 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.isNull
|
||||
import org.mockito.kotlin.mock
|
||||
@@ -49,7 +52,11 @@ class MessageHandlerTest {
|
||||
|
||||
whenever(delegate.getPeerInfo(peerID)).thenReturn(null)
|
||||
whenever(delegate.verifyEd25519Signature(any(), any(), any())).thenReturn(true)
|
||||
whenever(delegate.updatePeerInfo(any(), any(), any(), any(), any())).thenReturn(true)
|
||||
whenever(
|
||||
delegate.updatePeerInfoFromVerifiedAnnouncement(
|
||||
any(), any(), any(), any(), any(), anyOrNull()
|
||||
)
|
||||
).thenReturn(true)
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -58,46 +65,161 @@ class MessageHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleAnnounce accepts announce within clock skew tolerance for identity binding`() = runBlocking {
|
||||
val packet = announcePacket(ageMs = AppConstants.Mesh.STALE_PEER_TIMEOUT_MS + 1_000)
|
||||
fun `handleAnnounce accepts announce within clock skew tolerance for identity binding`() {
|
||||
runBlocking {
|
||||
val packet = announcePacket(ageMs = AppConstants.Mesh.STALE_PEER_TIMEOUT_MS + 1_000)
|
||||
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "direct-link"))
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "direct-link"))
|
||||
|
||||
assertTrue("Announce within clock skew tolerance should still store peer identity", result)
|
||||
verify(delegate).updatePeerInfo(eq(peerID), eq(nickname), any(), any(), eq(true))
|
||||
verify(delegate).updatePeerIDBinding(eq(peerID), eq(nickname), any(), isNull())
|
||||
assertTrue("Announce within clock skew tolerance should still store peer identity", result)
|
||||
verify(delegate).updatePeerInfoFromVerifiedAnnouncement(
|
||||
eq(peerID), eq(nickname), any(), any(), eq(true), isNull()
|
||||
)
|
||||
verify(delegate).onVerifiedAnnouncementProcessed(peerID)
|
||||
verify(delegate).updatePeerIDBinding(eq(peerID), eq(nickname), any(), isNull())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleAnnounce accepts future announce within clock skew tolerance`() = runBlocking {
|
||||
val packet = announcePacket(ageMs = -(AppConstants.Mesh.STALE_PEER_TIMEOUT_MS + 1_000))
|
||||
fun `handleAnnounce accepts future announce within clock skew tolerance`() {
|
||||
runBlocking {
|
||||
val packet = announcePacket(ageMs = -(AppConstants.Mesh.STALE_PEER_TIMEOUT_MS + 1_000))
|
||||
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "direct-link"))
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "direct-link"))
|
||||
|
||||
assertTrue("Future announce within clock skew tolerance should still store peer identity", result)
|
||||
verify(delegate).updatePeerInfo(eq(peerID), eq(nickname), any(), any(), eq(true))
|
||||
Unit
|
||||
assertTrue("Future announce within clock skew tolerance should still store peer identity", result)
|
||||
verify(delegate).updatePeerInfoFromVerifiedAnnouncement(
|
||||
eq(peerID), eq(nickname), any(), any(), eq(true), isNull()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleAnnounce rejects announce older than clock skew tolerance`() = runBlocking {
|
||||
val packet = announcePacket(ageMs = announceClockSkewToleranceMs + 1_000)
|
||||
fun `handleAnnounce stores advertised capabilities including unknown bits`() {
|
||||
runBlocking {
|
||||
val capabilities = PeerCapabilities(
|
||||
PeerCapabilities.PRIVATE_MEDIA.rawValue or (1L shl 15)
|
||||
)
|
||||
val packet = announcePacket(ageMs = 0, capabilities = capabilities)
|
||||
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "relay-link"))
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "direct-link"))
|
||||
|
||||
assertFalse("Announce older than clock skew tolerance should not store peer identity", result)
|
||||
verify(delegate, never()).updatePeerInfo(any(), any(), any(), any(), any())
|
||||
verify(delegate, never()).updatePeerIDBinding(any(), any(), any(), any())
|
||||
assertTrue(result)
|
||||
verify(delegate).updatePeerInfoFromVerifiedAnnouncement(
|
||||
eq(peerID),
|
||||
eq(nickname),
|
||||
any(),
|
||||
any(),
|
||||
eq(true),
|
||||
eq(capabilities)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleAnnounce rejects announce older than clock skew tolerance`() {
|
||||
runBlocking {
|
||||
val packet = announcePacket(ageMs = announceClockSkewToleranceMs + 1_000)
|
||||
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "relay-link"))
|
||||
|
||||
assertFalse("Announce older than clock skew tolerance should not store peer identity", result)
|
||||
verify(delegate, never()).updatePeerInfoFromVerifiedAnnouncement(
|
||||
any(), any(), any(), any(), any(), anyOrNull()
|
||||
)
|
||||
verify(delegate, never()).onVerifiedAnnouncementProcessed(any())
|
||||
verify(delegate, never()).updatePeerIDBinding(any(), any(), any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleAnnounce never promotes capability after invalid signature`() {
|
||||
runBlocking {
|
||||
whenever(delegate.verifyEd25519Signature(any(), any(), any())).thenReturn(false)
|
||||
val packet = announcePacket(ageMs = 0, capabilities = PeerCapabilities.PRIVATE_MEDIA)
|
||||
|
||||
val result = handler.handleAnnounce(RoutedPacket(packet, peerID, "direct-link"))
|
||||
|
||||
assertFalse(result)
|
||||
verify(delegate, never()).updatePeerInfoFromVerifiedAnnouncement(
|
||||
any(), any(), any(), any(), any(), anyOrNull()
|
||||
)
|
||||
verify(delegate, never()).onVerifiedAnnouncementProcessed(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `directed raw private media requires a valid signature`() {
|
||||
runBlocking {
|
||||
whenever(delegate.getBroadcastRecipient()).thenReturn(SpecialRecipients.BROADCAST)
|
||||
whenever(delegate.getPeerNickname(peerID)).thenReturn(nickname)
|
||||
whenever(delegate.verifySignature(any(), eq(peerID))).thenReturn(false)
|
||||
val file = BitchatFilePacket(
|
||||
fileName = "legacy.jpg",
|
||||
fileSize = 3,
|
||||
mimeType = "image/jpeg",
|
||||
content = byteArrayOf(1, 2, 3)
|
||||
)
|
||||
val unsigned = BitchatPacket(
|
||||
version = 2u,
|
||||
type = MessageType.FILE_TRANSFER.value,
|
||||
senderID = peerID.hexToBytes(),
|
||||
recipientID = myPeerID.hexToBytes(),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = file.encode()!!,
|
||||
signature = null,
|
||||
ttl = 7u
|
||||
)
|
||||
|
||||
handler.handleMessage(RoutedPacket(unsigned, peerID, "direct-link"))
|
||||
handler.handleMessage(
|
||||
RoutedPacket(unsigned.copy(signature = ByteArray(64) { 1 }), peerID, "direct-link")
|
||||
)
|
||||
|
||||
verify(delegate, never()).onMessageReceived(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid signed directed raw private media remains interoperable`() {
|
||||
runBlocking {
|
||||
whenever(delegate.getBroadcastRecipient()).thenReturn(SpecialRecipients.BROADCAST)
|
||||
whenever(delegate.getPeerNickname(peerID)).thenReturn(nickname)
|
||||
whenever(delegate.verifySignature(any(), eq(peerID))).thenReturn(true)
|
||||
val file = BitchatFilePacket(
|
||||
fileName = "legacy-valid.jpg",
|
||||
fileSize = 3,
|
||||
mimeType = "image/jpeg",
|
||||
content = byteArrayOf(1, 2, 3)
|
||||
)
|
||||
val packet = BitchatPacket(
|
||||
version = 2u,
|
||||
type = MessageType.FILE_TRANSFER.value,
|
||||
senderID = peerID.hexToBytes(),
|
||||
recipientID = myPeerID.hexToBytes(),
|
||||
timestamp = System.currentTimeMillis().toULong(),
|
||||
payload = file.encode()!!,
|
||||
signature = signature,
|
||||
ttl = 7u
|
||||
)
|
||||
|
||||
handler.handleMessage(RoutedPacket(packet, peerID, "direct-link"))
|
||||
|
||||
verify(delegate).verifySignature(packet, peerID)
|
||||
verify(delegate).onMessageReceived(any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun announcePacket(
|
||||
ageMs: Long,
|
||||
ttl: UByte = (AppConstants.MESSAGE_TTL_HOPS.toInt() - 1).toUByte()
|
||||
ttl: UByte = (AppConstants.MESSAGE_TTL_HOPS.toInt() - 1).toUByte(),
|
||||
capabilities: PeerCapabilities? = null
|
||||
): BitchatPacket {
|
||||
val announcement = IdentityAnnouncement(
|
||||
nickname = nickname,
|
||||
noisePublicKey = noiseKey,
|
||||
signingPublicKey = signingKey
|
||||
signingPublicKey = signingKey,
|
||||
capabilities = capabilities
|
||||
)
|
||||
return BitchatPacket(
|
||||
version = 1u,
|
||||
|
||||
Reference in New Issue
Block a user