fix: address bulletin board review findings

This commit is contained in:
callebtc 2026-07-27 00:54:25 +02:00
parent 771b063094
commit ee6d69e111
38 changed files with 180 additions and 46 deletions

View File

@ -106,6 +106,18 @@ class BoardStore(
return result
}
/**
* Ingests a packet received from the mesh and reports whether this exact
* arrival may continue through the live relay path.
*
* A duplicate board payload must not relay again: the payload signature
* does not authenticate mutable outer packet fields such as timestamp, so
* accepting duplicates for relay would let one captured notice be wrapped
* in infinitely many distinct outer packets.
*/
fun ingestRemoteForRelay(wire: BoardWire, packet: BitchatPacket): Boolean =
ingest(wire, packet, BoardIngestSource.REMOTE) == BoardIngestResult.ACCEPTED
fun posts(forGeohash: String): List<BoardPostPacket> = synchronized(lock) {
pruneExpiredLocked(nowMs())
posts.asSequence()
@ -194,7 +206,11 @@ class BoardStore(
now: ULong,
retainUntilOverride: ULong?
): BoardIngestResult {
if (tombstones.any { it.tombstone.postID.contentEquals(tombstone.postID) }) {
if (tombstones.any {
it.tombstone.postID.contentEquals(tombstone.postID) &&
it.tombstone.authorSigningKey.contentEquals(tombstone.authorSigningKey)
}
) {
return BoardIngestResult.DUPLICATE
}
@ -271,8 +287,8 @@ class BoardStore(
count: Int
) {
if (count <= 0) return
val victimIDs = candidates.take(count).map { it.tombstone.postID.toHex() }.toSet()
tombstones.removeAll { it.tombstone.postID.toHex() in victimIDs }
val victims = candidates.take(count)
tombstones.removeAll { stored -> victims.any { it === stored } }
}
private fun pruneExpiredLocked(now: ULong): Boolean {

View File

@ -680,16 +680,7 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic
override fun handleBoardPost(routed: RoutedPacket): Boolean {
val wire = com.bitchat.android.board.BoardWireCodec.decode(routed.packet.payload)
?: return false
if (!wire.verifySignature()) return false
return when (boardStore.ingest(
wire,
routed.packet,
com.bitchat.android.board.BoardIngestSource.REMOTE
)) {
com.bitchat.android.board.BoardIngestResult.ACCEPTED,
com.bitchat.android.board.BoardIngestResult.DUPLICATE -> true
com.bitchat.android.board.BoardIngestResult.REJECTED -> false
}
return boardStore.ingestRemoteForRelay(wire, routed.packet)
}
}

View File

@ -511,16 +511,7 @@ class MeshCore(
override fun handleBoardPost(routed: RoutedPacket): Boolean {
val wire = com.bitchat.android.board.BoardWireCodec.decode(routed.packet.payload)
?: return false
if (!wire.verifySignature()) return false
return when (boardStore.ingest(
wire,
routed.packet,
com.bitchat.android.board.BoardIngestSource.REMOTE
)) {
com.bitchat.android.board.BoardIngestResult.ACCEPTED,
com.bitchat.android.board.BoardIngestResult.DUPLICATE -> true
com.bitchat.android.board.BoardIngestResult.REJECTED -> false
}
return boardStore.ingestRemoteForRelay(wire, routed.packet)
}
}
}

View File

@ -0,0 +1,11 @@
package com.bitchat.android.ui
internal object NoticeComposerPolicy {
private val finiteExpiryDays = listOf(1, 3, 7)
fun expiryOptions(isGeo: Boolean): List<Int> =
if (isGeo) listOf(0) + finiteExpiryDays else finiteExpiryDays
fun isPermanentRelayOnlyGeo(isGeo: Boolean, expiryDays: Int): Boolean =
isGeo && expiryDays == 0
}

View File

@ -13,8 +13,10 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.Delete
@ -239,7 +241,11 @@ private fun NoticesSheet(
enabled = true,
sendFailed = sendFailed,
onSend = {
if (selectedTab == NoticesTab.GEO && expiryDays == 0) {
if (NoticeComposerPolicy.isPermanentRelayOnlyGeo(
isGeo = selectedTab == NoticesTab.GEO,
expiryDays = expiryDays
)
) {
notesManager.send(
content = draft,
nickname = nickname,
@ -470,29 +476,34 @@ private fun Composer(
)
Switch(checked = urgent, onCheckedChange = onUrgentChange)
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.notices_expiry),
style = MaterialTheme.typography.labelMedium
)
listOf(1, 3, 7).forEach { days ->
val daysDescription = stringResource(R.string.notices_days, days)
FilterChip(
selected = expiryDays == days,
onClick = { onExpiryChange(days) },
label = {
Text(
text = "${days}d",
modifier = Modifier.semantics {
contentDescription = daysDescription
}
)
}
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.notices_expiry),
style = MaterialTheme.typography.labelMedium
)
NoticeComposerPolicy.expiryOptions(isGeo).forEach { days ->
val optionDescription = if (days == 0) {
stringResource(R.string.notices_permanent)
} else {
stringResource(R.string.notices_days, days)
}
FilterChip(
selected = expiryDays == days,
onClick = { onExpiryChange(days) },
modifier = Modifier.semantics {
contentDescription = optionDescription
},
label = {
Text(if (days == 0) "" else "${days}d")
}
)
}
}
Row(

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">جغرافي · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">ثبّت إعلانات قصيرة لمن حولك. تنتقل من هاتف إلى هاتف حتى دون اتصال، وتختفي وحدها بعد أيام قليلة.</string>
<string name="notices_permanent">دائم</string>
<string name="notices_fades">يتلاشى %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">نت</string>

View File

@ -393,6 +393,7 @@
<string name="notices_tab_geo_count">এলাকা · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">আশেপাশের মানুষের জন্য ছোট নোটিশ পিন করুন। অফলাইনেও ফোন থেকে ফোনে পৌঁছে যায় আর কয়েক দিন পরে নিজে থেকেই মুছে যায়।</string>
<string name="notices_permanent">স্থায়ী</string>
<string name="notices_fades">%1$s মুছে যাবে</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">নেট</string>

View File

@ -407,6 +407,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">hefte kurze hinweise für leute in deiner nähe an. sie springen von handy zu handy, auch offline, und verschwinden nach ein paar tagen von selbst.</string>
<string name="notices_permanent">dauerhaft</string>
<string name="notices_fades">verblasst %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">netz</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">fija avisos cortos para la gente cercana. saltan de teléfono a teléfono, incluso sin conexión, y desaparecen solos después de unos días.</string>
<string name="notices_permanent">permanente</string>
<string name="notices_fades">se desvanece %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">red</string>

View File

@ -393,6 +393,7 @@
<string name="notices_tab_geo_count">ژئو · %1$d</string>
<string name="notices_tab_mesh_count">مش · %1$d</string>
<string name="notices_description_mesh">اطلاعیه‌های کوتاه برای اطرافیان‌تان سنجاق کنید. گوشی به گوشی جابه‌جا می‌شوند، حتی آفلاین، و بعد از چند روز خودبه‌خود ناپدید می‌شوند.</string>
<string name="notices_permanent">دائمی</string>
<string name="notices_fades">%1$s محو می‌شود</string>
<string name="notices_source_mesh">مش</string>
<string name="notices_source_network">نت</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">mag-pin ng maiikling paunawa para sa mga taong nasa paligid mo. lumilipat ito mula sa isang telepono patungo sa iba, kahit offline, at kusang nawawala pagkatapos ng ilang araw.</string>
<string name="notices_permanent">permanente</string>
<string name="notices_fades">maglalaho %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">net</string>

View File

@ -420,6 +420,7 @@
<string name="notices_tab_geo_count">géo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">épingle de courtes annonces pour les gens autour de toi. elles passent de téléphone en téléphone, même hors ligne, et disparaissent d\'elles-mêmes après quelques jours.</string>
<string name="notices_permanent">permanent</string>
<string name="notices_fades">s\'efface %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">net</string>

View File

@ -59,6 +59,7 @@
<string name="notices_tab_geo_count">אזור · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">הצמד מודעות קצרות לאנשים סביבך. הן עוברות מטלפון לטלפון, גם בלי אינטרנט, ונעלמות מעצמן אחרי כמה ימים.</string>
<string name="notices_permanent">קבוע</string>
<string name="notices_fades">דוהה %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">רשת</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">क्षेत्र · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">आस-पास के लोगों के लिए छोटी सूचनाएँ पिन करें। ये ऑफ़लाइन भी फ़ोन से फ़ोन तक पहुँचती हैं और कुछ दिनों बाद अपने आप मिट जाती हैं।</string>
<string name="notices_permanent">स्थायी</string>
<string name="notices_fades">%1$s मिट जाएगा</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">नेट</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">area · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">sematkan pengumuman singkat untuk orang di sekitarmu. berpindah dari ponsel ke ponsel, bahkan saat offline, dan hilang sendiri setelah beberapa hari.</string>
<string name="notices_permanent">permanen</string>
<string name="notices_fades">memudar %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">net</string>

View File

@ -440,6 +440,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">appunta brevi avvisi per chi ti sta intorno. passano da telefono a telefono, anche offline, e spariscono da soli dopo qualche giorno.</string>
<string name="notices_permanent">permanente</string>
<string name="notices_fades">svanisce %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">rete</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">エリア · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">近くの人に向けて短いお知らせをピン留め。オフラインでもスマホからスマホへ伝わり、数日後に自動的に消えます。</string>
<string name="notices_permanent">無期限</string>
<string name="notices_fades">%1$sに消えます</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">ネット</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">지역 · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">주변 사람들을 위해 짧은 공지를 고정하세요. 오프라인에서도 휴대폰에서 휴대폰으로 전달되고 며칠 후 스스로 사라집니다.</string>
<string name="notices_permanent">영구</string>
<string name="notices_fades">%1$s 사라짐</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network"></string>

View File

@ -46,6 +46,7 @@
<string name="notices_tab_geo_count">kawasan · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">semat pengumuman ringkas untuk orang di sekeliling anda. ia berpindah dari telefon ke telefon, walaupun di luar talian, dan hilang sendiri selepas beberapa hari.</string>
<string name="notices_permanent">kekal</string>
<string name="notices_fades">pudar %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">net</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">क्षेत्र · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">वरपरका मानिसहरूका लागि छोटा सूचना पिन गर। अफलाइनमा पनि फोनबाट फोनमा पुग्छन् र केही दिनपछि आफैँ हराउँछन्।</string>
<string name="notices_permanent">स्थायी</string>
<string name="notices_fades">%1$s मेटिन्छ</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">नेट</string>

View File

@ -438,6 +438,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">prik korte mededelingen voor mensen om je heen. ze springen van telefoon naar telefoon, ook offline, en verdwijnen vanzelf na een paar dagen.</string>
<string name="notices_permanent">permanent</string>
<string name="notices_fades">vervaagt %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">net</string>

View File

@ -59,6 +59,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">przypinaj krótkie ogłoszenia dla ludzi w pobliżu. przeskakują z telefonu na telefon, nawet offline, i same znikają po kilku dniach.</string>
<string name="notices_permanent">na stałe</string>
<string name="notices_fades">zniknie %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">sieć</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">fixe avisos curtos para as pessoas por perto. eles pulam de celular em celular, mesmo offline, e somem sozinhos depois de alguns dias.</string>
<string name="notices_permanent">permanente</string>
<string name="notices_fades">desaparece %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">rede</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">afixa avisos curtos para quem está por perto. saltam de telemóvel em telemóvel, mesmo offline, e desaparecem sozinhos após alguns dias.</string>
<string name="notices_permanent">permanente</string>
<string name="notices_fades">desvanece %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">rede</string>

View File

@ -396,6 +396,7 @@
<string name="notices_tab_geo_count">гео · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">закрепляй короткие объявления для людей рядом. они передаются с телефона на телефон, даже офлайн, и сами исчезают через несколько дней.</string>
<string name="notices_permanent">навсегда</string>
<string name="notices_fades">исчезнет %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">сеть</string>

View File

@ -394,6 +394,7 @@
<string name="notices_tab_geo_count">geo · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">nåla upp korta anslag för folk i närheten. de hoppar från telefon till telefon, även offline, och försvinner av sig själva efter några dagar.</string>
<string name="notices_permanent">permanent</string>
<string name="notices_fades">tonar bort %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">nät</string>

View File

@ -46,6 +46,7 @@
<string name="notices_tab_geo_count">பகுதி · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">அருகிலுள்ளவர்களுக்காக சிறிய அறிவிப்புகளைப் பின் செய்யவும். ஆஃப்லைனிலும் ஃபோனிலிருந்து ஃபோனுக்குப் பரவி, சில நாட்களில் தானாக மறைந்துவிடும்.</string>
<string name="notices_permanent">நிரந்தரம்</string>
<string name="notices_fades">%1$s மறையும்</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">நெட்</string>

View File

@ -393,6 +393,7 @@
<string name="notices_tab_geo_count">พื้นที่ · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">ปักประกาศสั้น ๆ ให้คนรอบตัว ส่งต่อจากมือถือสู่มือถือได้แม้ออฟไลน์ และหายไปเองหลังผ่านไปสองสามวัน</string>
<string name="notices_permanent">ถาวร</string>
<string name="notices_fades">เลือนหาย %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">เน็ต</string>

View File

@ -394,6 +394,7 @@
<string name="notices_tab_geo_count">bölge · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">çevrendekiler için kısa duyurular sabitle. çevrimdışıyken bile telefondan telefona geçer ve birkaç gün sonra kendiliğinden kaybolur.</string>
<string name="notices_permanent">kalıcı</string>
<string name="notices_fades">%1$s kaybolur</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network"></string>

View File

@ -46,6 +46,7 @@
<string name="notices_tab_geo_count">гео · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">закріплюй короткі оголошення для людей поруч. вони передаються з телефона на телефон, навіть офлайн, і самі зникають за кілька днів.</string>
<string name="notices_permanent">назавжди</string>
<string name="notices_fades">зникне %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">мережа</string>

View File

@ -406,6 +406,7 @@
<string name="notices_tab_geo_count">علاقہ · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">آس پاس کے لوگوں کیلئے مختصر اعلانات پن کریں۔ یہ آف لائن بھی فون سے فون تک پہنچتے ہیں اور کچھ دنوں بعد خود مٹ جاتے ہیں۔</string>
<string name="notices_permanent">مستقل</string>
<string name="notices_fades">%1$s مٹ جائے گا</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">نیٹ</string>

View File

@ -393,6 +393,7 @@
<string name="notices_tab_geo_count">khu vực · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">ghim thông báo ngắn cho những người quanh bạn. chúng truyền từ điện thoại này sang điện thoại khác, kể cả khi ngoại tuyến, và tự biến mất sau vài ngày.</string>
<string name="notices_permanent">vĩnh viễn</string>
<string name="notices_fades">mờ dần %1$s</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">mạng</string>

View File

@ -59,6 +59,7 @@
<string name="notices_tab_geo_count">区域 · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">为周围的人钉上简短公告。即使离线也能在手机间传递,几天后自动消失。</string>
<string name="notices_permanent">永久</string>
<string name="notices_fades">%1$s消失</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">网络</string>

View File

@ -59,6 +59,7 @@
<string name="notices_tab_geo_count">區域 · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">為周圍的人釘上簡短公告。即使離線也能在手機間傳遞,幾天後自動消失。</string>
<string name="notices_permanent">永久</string>
<string name="notices_fades">%1$s消失</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">網路</string>

View File

@ -419,6 +419,7 @@
<string name="notices_tab_geo_count">区域 · %1$d</string>
<string name="notices_tab_mesh_count">mesh · %1$d</string>
<string name="notices_description_mesh">为周围的人钉上简短公告。即使离线也能在手机间传递,几天后自动消失。</string>
<string name="notices_permanent">永久</string>
<string name="notices_fades">%1$s消失</string>
<string name="notices_source_mesh">mesh</string>
<string name="notices_source_network">网络</string>

View File

@ -258,6 +258,7 @@
<string name="notices_delete" tools:ignore="MissingTranslation">Delete notice</string>
<string name="notices_expiry" tools:ignore="MissingTranslation">expires</string>
<string name="notices_days" tools:ignore="MissingTranslation,PluralsCandidate">%1$d days</string>
<string name="notices_permanent" tools:ignore="MissingTranslation">permanent</string>
<string name="notices_fades" tools:ignore="MissingTranslation">fades %1$s</string>
<string name="notices_source_mesh" tools:ignore="MissingTranslation">mesh</string>
<string name="notices_source_network" tools:ignore="MissingTranslation">net</string>

View File

@ -5,6 +5,7 @@ import com.bitchat.android.protocol.MessageType
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
import org.bouncycastle.crypto.signers.Ed25519Signer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@ -70,6 +71,48 @@ class BoardStoreTest {
assertEquals(1, store.syncCandidates().size)
}
@Test
fun `rewrapped duplicate payload is stored but never relayed again`() {
val store = BoardStore(nowMs = { now })
val wire = BoardWire.Post(signedPost())
val firstOuterPacket = packet(wire)
assertTrue(store.ingestRemoteForRelay(wire, firstOuterPacket))
assertFalse(
store.ingestRemoteForRelay(
wire,
firstOuterPacket.copy(timestamp = firstOuterPacket.timestamp + 1uL)
)
)
assertEquals(1, store.posts("").size)
}
@Test
fun `attacker orphan tombstone cannot block author deletion`() {
val store = BoardStore(nowMs = { now })
val post = signedPost()
val attackerTombstone = signedTombstone(post, attacker)
assertEquals(
BoardIngestResult.ACCEPTED,
store.ingestTombstone(attackerTombstone)
)
assertEquals(BoardIngestResult.ACCEPTED, store.ingestPost(post))
val authorTombstone = signedTombstone(post, author, deletedAt = now + 1uL)
assertEquals(
BoardIngestResult.ACCEPTED,
store.ingestTombstone(authorTombstone)
)
assertTrue(store.posts("").isEmpty())
assertEquals(2, store.syncCandidates().size)
assertEquals(
BoardIngestResult.DUPLICATE,
store.ingestTombstone(attackerTombstone)
)
}
@Test
fun `tombstone suppresses a stale copy until original expiry`() {
val store = BoardStore(nowMs = { now })

View File

@ -0,0 +1,40 @@
package com.bitchat.android.ui
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class NoticeComposerPolicyTest {
@Test
fun `geo offers permanent and finite board lifetimes`() {
assertEquals(listOf(0, 1, 3, 7), NoticeComposerPolicy.expiryOptions(isGeo = true))
}
@Test
fun `mesh offers only finite board lifetimes`() {
assertEquals(listOf(1, 3, 7), NoticeComposerPolicy.expiryOptions(isGeo = false))
}
@Test
fun `only permanent geo selection skips the board`() {
assertTrue(
NoticeComposerPolicy.isPermanentRelayOnlyGeo(
isGeo = true,
expiryDays = 0
)
)
assertFalse(
NoticeComposerPolicy.isPermanentRelayOnlyGeo(
isGeo = true,
expiryDays = 1
)
)
assertFalse(
NoticeComposerPolicy.isPermanentRelayOnlyGeo(
isGeo = false,
expiryDays = 1
)
)
}
}