mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 21:05:24 +00:00
better
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package com.bitchat.android.protocol
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.Deflater
|
||||
import java.util.zip.Inflater
|
||||
|
||||
/**
|
||||
* Compression utilities - LZ4-like functionality using Deflater/Inflater
|
||||
* Android doesn't have native LZ4, so we use Java's built-in compression
|
||||
*/
|
||||
object CompressionUtil {
|
||||
private const val COMPRESSION_THRESHOLD = 100 // bytes - same as iOS
|
||||
|
||||
/**
|
||||
* Helper to check if compression is worth it - exact same logic as iOS
|
||||
*/
|
||||
fun shouldCompress(data: ByteArray): Boolean {
|
||||
// Don't compress if:
|
||||
// 1. Data is too small
|
||||
// 2. Data appears to be already compressed (high entropy)
|
||||
if (data.size < COMPRESSION_THRESHOLD) return false
|
||||
|
||||
// Simple entropy check - count unique bytes
|
||||
val byteFrequency = mutableMapOf<Byte, Int>()
|
||||
for (byte in data) {
|
||||
byteFrequency[byte] = (byteFrequency[byte] ?: 0) + 1
|
||||
}
|
||||
|
||||
// If we have very high byte diversity, data is likely already compressed
|
||||
val uniqueByteRatio = byteFrequency.size.toDouble() / minOf(data.size, 256).toDouble()
|
||||
return uniqueByteRatio < 0.9 // Compress if less than 90% unique bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress data using Deflater (closest to LZ4 available on Android)
|
||||
*/
|
||||
fun compress(data: ByteArray): ByteArray? {
|
||||
// Skip compression for small data
|
||||
if (data.size < COMPRESSION_THRESHOLD) return null
|
||||
|
||||
try {
|
||||
val deflater = Deflater(Deflater.BEST_SPEED) // Fast compression like LZ4
|
||||
deflater.setInput(data)
|
||||
deflater.finish()
|
||||
|
||||
val buffer = ByteArray(data.size + 16) // Some overhead space
|
||||
val compressedSize = deflater.deflate(buffer)
|
||||
deflater.end()
|
||||
|
||||
// Only return if compression was beneficial
|
||||
if (compressedSize > 0 && compressedSize < data.size) {
|
||||
return buffer.copyOfRange(0, compressedSize)
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress data using Inflater
|
||||
*/
|
||||
fun decompress(compressedData: ByteArray, originalSize: Int): ByteArray? {
|
||||
try {
|
||||
val inflater = Inflater()
|
||||
inflater.setInput(compressedData)
|
||||
|
||||
val result = ByteArray(originalSize)
|
||||
val decompressedSize = inflater.inflate(result)
|
||||
inflater.end()
|
||||
|
||||
if (decompressedSize > 0) {
|
||||
return if (decompressedSize == originalSize) {
|
||||
result
|
||||
} else {
|
||||
result.copyOfRange(0, decompressedSize)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.bitchat.android.protocol
|
||||
|
||||
import java.security.SecureRandom
|
||||
|
||||
/**
|
||||
* Privacy-preserving padding utilities - exact same as iOS version
|
||||
* Provides traffic analysis resistance by normalizing message sizes
|
||||
*/
|
||||
object MessagePadding {
|
||||
// Standard block sizes for padding - exact same as iOS
|
||||
private val blockSizes = listOf(256, 512, 1024, 2048)
|
||||
|
||||
/**
|
||||
* Find optimal block size for data - exact same logic as iOS
|
||||
*/
|
||||
fun optimalBlockSize(dataSize: Int): Int {
|
||||
// Account for encryption overhead (~16 bytes for AES-GCM tag)
|
||||
val totalSize = dataSize + 16
|
||||
|
||||
// Find smallest block that fits
|
||||
for (blockSize in blockSizes) {
|
||||
if (totalSize <= blockSize) {
|
||||
return blockSize
|
||||
}
|
||||
}
|
||||
|
||||
// For very large messages, just use the original size
|
||||
// (will be fragmented anyway)
|
||||
return dataSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Add PKCS#7-style padding to reach target size - exact same as iOS
|
||||
*/
|
||||
fun pad(data: ByteArray, targetSize: Int): ByteArray {
|
||||
if (data.size >= targetSize) return data
|
||||
|
||||
val paddingNeeded = targetSize - data.size
|
||||
|
||||
// PKCS#7 only supports padding up to 255 bytes
|
||||
// If we need more padding than that, don't pad - return original data
|
||||
if (paddingNeeded > 255) return data
|
||||
|
||||
val result = ByteArray(targetSize)
|
||||
|
||||
// Copy original data
|
||||
System.arraycopy(data, 0, result, 0, data.size)
|
||||
|
||||
// Standard PKCS#7 padding - fill with random bytes then add padding length
|
||||
val randomBytes = ByteArray(paddingNeeded - 1)
|
||||
SecureRandom().nextBytes(randomBytes)
|
||||
|
||||
// Copy random bytes
|
||||
System.arraycopy(randomBytes, 0, result, data.size, paddingNeeded - 1)
|
||||
|
||||
// Last byte tells how much padding was added
|
||||
result[result.size - 1] = paddingNeeded.toByte()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove padding from data - exact same as iOS
|
||||
*/
|
||||
fun unpad(data: ByteArray): ByteArray {
|
||||
if (data.isEmpty()) return data
|
||||
|
||||
// Last byte tells us how much padding to remove
|
||||
val paddingLength = data[data.size - 1].toInt() and 0xFF
|
||||
if (paddingLength <= 0 || paddingLength > data.size) {
|
||||
// Invalid padding, return original data
|
||||
return data
|
||||
}
|
||||
|
||||
return data.copyOfRange(0, data.size - paddingLength)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user