mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 06:05:21 +00:00
fix(sync): resolve hash alignment and early break bugs on GCS filter trimming (#708)
* fix(sync): resolve hash alignment and early break bugs on GCS filter trimming * fix(sync): filter out zero bucket and deduplicate buckets before encoding * fix(sync): preserve zero-bucket mapping by clamping 0 to 1 during encoding and decoding
This commit is contained in:
@@ -43,21 +43,29 @@ object GCSFilter {
|
||||
targetFpr: Double
|
||||
): Params {
|
||||
val p = deriveP(targetFpr)
|
||||
var nCap = estimateMaxElementsForSize(maxBytes, p)
|
||||
val n = ids.size.coerceAtMost(nCap)
|
||||
val selected = ids.take(n)
|
||||
// Map to [0, M)
|
||||
val m = (n.toLong() shl p)
|
||||
val mapped = selected.map { id -> (h64(id) % m) }.sorted()
|
||||
val nCap = estimateMaxElementsForSize(maxBytes, p)
|
||||
var trimmedN = ids.size.coerceAtMost(nCap)
|
||||
|
||||
var finalM = (trimmedN.toLong() shl p).coerceAtLeast(1L)
|
||||
var selected = ids.take(trimmedN)
|
||||
var mapped = selected.map { id ->
|
||||
val v = h64(id) % finalM
|
||||
if (v == 0L) 1L else v
|
||||
}.distinct().sorted()
|
||||
var encoded = encode(mapped, p)
|
||||
|
||||
// If estimate was too optimistic, trim until it fits
|
||||
var trimmedN = n
|
||||
while (encoded.size > maxBytes && trimmedN > 0) {
|
||||
trimmedN = (trimmedN * 9) / 10 // drop 10%
|
||||
val mapped2 = mapped.take(trimmedN)
|
||||
encoded = encode(mapped2, p)
|
||||
finalM = (trimmedN.toLong() shl p).coerceAtLeast(1L)
|
||||
selected = ids.take(trimmedN)
|
||||
mapped = selected.map { id ->
|
||||
val v = h64(id) % finalM
|
||||
if (v == 0L) 1L else v
|
||||
}.distinct().sorted()
|
||||
encoded = encode(mapped, p)
|
||||
}
|
||||
val finalM = (trimmedN.toLong() shl p)
|
||||
|
||||
return Params(p = p, m = finalM, data = encoded)
|
||||
}
|
||||
|
||||
@@ -96,7 +104,7 @@ object GCSFilter {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun h64(id16: ByteArray): Long {
|
||||
internal fun h64(id16: ByteArray): Long {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
md.update(id16)
|
||||
val d = md.digest()
|
||||
|
||||
@@ -169,14 +169,9 @@ class GossipSyncManager(
|
||||
// Decode GCS into sorted set for membership checks
|
||||
val sorted = GCSFilter.decodeToSortedSet(request.p, request.m, request.data)
|
||||
fun mightContain(id: ByteArray): Boolean {
|
||||
val v = (GCSFilter.run {
|
||||
// reuse hashing method from GCSFilter
|
||||
val md = java.security.MessageDigest.getInstance("SHA-256");
|
||||
md.update(id); val d = md.digest();
|
||||
var x = 0L; for (i in 0 until 8) { x = (x shl 8) or (d[i].toLong() and 0xFF) }
|
||||
(x and 0x7fff_ffff_ffff_ffffL) % request.m
|
||||
})
|
||||
return GCSFilter.contains(sorted, v)
|
||||
val v = GCSFilter.h64(id) % request.m
|
||||
val nonZeroV = if (v == 0L) 1L else v
|
||||
return GCSFilter.contains(sorted, nonZeroV)
|
||||
}
|
||||
|
||||
// 1) Announcements: send latest per peerID if remote doesn't have them
|
||||
|
||||
Reference in New Issue
Block a user