mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-10-25 07:40:36 +00:00
Reassemble split file content in linear time
decode tolerates a CONTENT TLV split across several TLVs by appending each chunk with `contentBytes + value`. That reallocates and copies the whole accumulator every time, so k chunks totalling N bytes cost O(k*N). The split is the sender's choice, and a CONTENT TLV costs only five header bytes, so one content byte per chunk maximises the chunk count. The decoder runs on the mesh handler for any FILE_TRANSFER from a verified peer — verification binds an identity, it does not authorise anything, and a peer mints one by signing its own announcement. Measured on a ~1.2 MB payload split into 200k single-byte chunks, which is the scale BLE reassembly already admits: 2435ms of copying, repeatable for the cost of sending the packet again. Collecting the chunks and joining once brings the same payload to 65ms. The surrounding decoder was already hardened against exactly this shape for unknown tags, which are skipped without copying and logged once rather than per TLV. This closes the same hole on the tag that carries the bytes.
This commit is contained in:
parent
5156f7de89
commit
98a52269e3
@ -104,7 +104,9 @@ data class BitchatFilePacket(
|
||||
var name: String? = null
|
||||
var size: Long? = null
|
||||
var mime: String? = null
|
||||
var contentBytes: ByteArray? = null
|
||||
val contentChunks = ArrayList<ByteArray>(1)
|
||||
var contentLength = 0
|
||||
var sawContent = false
|
||||
var skippedUnknownTLVs = 0
|
||||
while (off < data.size) {
|
||||
// Every TLV needs at least a type and a 2-byte length.
|
||||
@ -150,10 +152,18 @@ data class BitchatFilePacket(
|
||||
}
|
||||
TLVType.MIME_TYPE -> mime = String(value, Charsets.UTF_8)
|
||||
TLVType.CONTENT -> {
|
||||
// Expect a single CONTENT TLV
|
||||
if (contentBytes == null) contentBytes = value else {
|
||||
// If multiple CONTENT TLVs appear, concatenate for tolerance
|
||||
contentBytes = (contentBytes!! + value)
|
||||
// Expect a single CONTENT TLV, but tolerate a split one.
|
||||
//
|
||||
// `contentBytes + value` reallocates and copies the whole
|
||||
// accumulator per chunk, so k chunks totalling N bytes cost
|
||||
// O(k*N). The chunk count is the sender's to choose — a 6-byte
|
||||
// TLV carries one content byte — so at the 1 MB BLE reassembly
|
||||
// ceiling one packet buys ~10^10 byte copies on the mesh handler
|
||||
// thread. Collect the chunks and join once instead.
|
||||
sawContent = true
|
||||
if (value.isNotEmpty()) {
|
||||
contentChunks.add(value)
|
||||
contentLength += value.size
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,7 +172,18 @@ data class BitchatFilePacket(
|
||||
android.util.Log.d("BitchatFilePacket", "⏭️ Skipped $skippedUnknownTLVs unknown TLV(s)")
|
||||
}
|
||||
val n = name ?: return null
|
||||
val c = contentBytes ?: return null
|
||||
if (!sawContent) return null
|
||||
val c = when (contentChunks.size) {
|
||||
0 -> ByteArray(0)
|
||||
1 -> contentChunks[0]
|
||||
else -> ByteArray(contentLength).also { joined ->
|
||||
var at = 0
|
||||
for (chunk in contentChunks) {
|
||||
System.arraycopy(chunk, 0, joined, at, chunk.size)
|
||||
at += chunk.size
|
||||
}
|
||||
}
|
||||
}
|
||||
val s = size ?: c.size.toLong()
|
||||
val m = mime ?: "application/octet-stream"
|
||||
val result = BitchatFilePacket(n, s, m, c)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user