mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Merge 7d343930fe381f1a921eaf7b48e72a2912410654 into c127eb83ab94c069c32d37530d2faecd381cd2a8
This commit is contained in:
commit
45dadb3e5f
@ -18,6 +18,14 @@ import kotlin.math.ln
|
||||
* - Bitstream is packed MSB-first in each byte.
|
||||
*/
|
||||
object GCSFilter {
|
||||
/**
|
||||
* Highest Golomb-Rice parameter accepted from the wire. P maps to an FPR of
|
||||
* ~1/2^P; beyond 32 the remainder width exceeds any practical filter, and
|
||||
* the shifts in decode silently wrap (Kotlin shifts use the low 6 bits of
|
||||
* the count) into garbage values.
|
||||
*/
|
||||
const val MAX_P = 32
|
||||
|
||||
data class Params(
|
||||
val p: Int, // Golomb-Rice parameter (>= 1)
|
||||
val m: Long, // Range M = N * 2^P
|
||||
@ -70,6 +78,11 @@ object GCSFilter {
|
||||
}
|
||||
|
||||
fun decodeToSortedSet(p: Int, m: Long, data: ByteArray): LongArray {
|
||||
// p and m arrive off the wire. Reject out-of-range parameters rather
|
||||
// than decoding garbage: callers read the result as "peer has nothing"
|
||||
// and fall back to sending the data, which is the safe direction.
|
||||
// Matches the iOS guard in GCSFilter.decodeToSortedSet.
|
||||
if (p < 1 || p > MAX_P || m <= 1L) return LongArray(0)
|
||||
val values = ArrayList<Long>()
|
||||
val reader = BitReader(data)
|
||||
var acc = 0L
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
package com.bitchat.android.sync
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Random
|
||||
|
||||
/**
|
||||
* `p` and `m` arrive off the wire — a REQUEST_SYNC carries P as a uint8 — and
|
||||
* the decoded set decides which packets a peer is told it already has. Decoding
|
||||
* garbage from an out-of-range parameter therefore withholds real packets, so
|
||||
* out-of-range parameters have to decode to "peer has nothing" instead.
|
||||
*/
|
||||
class GCSFilterParameterTest {
|
||||
|
||||
private fun ids(n: Int): List<ByteArray> {
|
||||
val random = Random(42)
|
||||
return List(n) {
|
||||
val bytes = ByteArray(16)
|
||||
random.nextBytes(bytes)
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a filter round-trips with the parameters it was built with`() {
|
||||
val params = GCSFilter.buildFilter(ids(20), maxBytes = 400, targetFpr = 0.01)
|
||||
val decoded = GCSFilter.decodeToSortedSet(params.p, params.m, params.data)
|
||||
|
||||
assertTrue("expected a non-empty decode", decoded.isNotEmpty())
|
||||
assertTrue("values must stay in range", decoded.all { it in 1 until params.m })
|
||||
assertEquals(decoded.toList(), decoded.sorted())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an out-of-range p decodes to nothing rather than to garbage`() {
|
||||
val params = GCSFilter.buildFilter(ids(20), maxBytes = 400, targetFpr = 0.01)
|
||||
|
||||
// 64 and above wrap Kotlin's shift operators; 255 is what the byte allows.
|
||||
for (p in listOf(0, 33, 64, 200, 255)) {
|
||||
val decoded = GCSFilter.decodeToSortedSet(p, params.m, params.data)
|
||||
assertTrue("p=$p should decode to nothing, got ${decoded.size} values", decoded.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a degenerate m decodes to nothing`() {
|
||||
val params = GCSFilter.buildFilter(ids(20), maxBytes = 400, targetFpr = 0.01)
|
||||
|
||||
assertTrue(GCSFilter.decodeToSortedSet(params.p, 0L, params.data).isEmpty())
|
||||
assertTrue(GCSFilter.decodeToSortedSet(params.p, 1L, params.data).isEmpty())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user