|
| 1 | +package com.sameerasw.airsync.data.ble |
| 2 | + |
| 3 | +import android.util.Log |
| 4 | + |
| 5 | +object BleChunkUtil { |
| 6 | + private const val TAG = "BleChunkUtil" |
| 7 | + |
| 8 | + /** |
| 9 | + * Splits a string payload into chunks suitable for BLE transmission. |
| 10 | + * Each chunk starts with a 2-byte header: [currentIndex, totalChunks] |
| 11 | + */ |
| 12 | + fun splitIntoChunks(payload: String, mtu: Int): List<ByteArray> { |
| 13 | + val data = payload.toByteArray(Charsets.UTF_8) |
| 14 | + val maxPayloadSize = mtu - BleConstants.CHUNK_HEADER_SIZE |
| 15 | + |
| 16 | + if (maxPayloadSize <= 0) { |
| 17 | + Log.e(TAG, "MTU too small: $mtu") |
| 18 | + return emptyList() |
| 19 | + } |
| 20 | + |
| 21 | + val totalChunks = (data.size + maxPayloadSize - 1) / maxPayloadSize |
| 22 | + val chunks = mutableListOf<ByteArray>() |
| 23 | + |
| 24 | + for (i in 0 until totalChunks) { |
| 25 | + val start = i * maxPayloadSize |
| 26 | + val end = minOf(start + maxPayloadSize, data.size) |
| 27 | + val chunkData = data.sliceArray(start until end) |
| 28 | + |
| 29 | + val chunk = ByteArray(BleConstants.CHUNK_HEADER_SIZE + chunkData.size) |
| 30 | + val buffer = java.nio.ByteBuffer.wrap(chunk) |
| 31 | + buffer.putShort(i.toShort()) |
| 32 | + buffer.putShort(totalChunks.toShort()) |
| 33 | + |
| 34 | + chunkData.copyInto(chunk, BleConstants.CHUNK_HEADER_SIZE) |
| 35 | + chunks.add(chunk) |
| 36 | + } |
| 37 | + |
| 38 | + return chunks |
| 39 | + } |
| 40 | + |
| 41 | + /** |
| 42 | + * Reassembles chunks into the original string. |
| 43 | + * Expects a map of index to chunk data (without the header). |
| 44 | + */ |
| 45 | + fun reassemble(chunks: Map<Int, ByteArray>): String { |
| 46 | + val sortedIndices = chunks.keys.sorted() |
| 47 | + if (sortedIndices.isEmpty()) return "" |
| 48 | + |
| 49 | + val totalSize = chunks.values.sumOf { it.size } |
| 50 | + val result = ByteArray(totalSize) |
| 51 | + |
| 52 | + var offset = 0 |
| 53 | + for (index in sortedIndices) { |
| 54 | + val chunk = chunks[index] ?: continue |
| 55 | + chunk.copyInto(result, offset) |
| 56 | + offset += chunk.size |
| 57 | + } |
| 58 | + |
| 59 | + return String(result, Charsets.UTF_8) |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Extracts header information from a raw BLE packet. |
| 64 | + */ |
| 65 | + fun parseHeader(packet: ByteArray): Pair<Int, Int>? { |
| 66 | + if (packet.size < BleConstants.CHUNK_HEADER_SIZE) return null |
| 67 | + val buffer = java.nio.ByteBuffer.wrap(packet) |
| 68 | + val current = buffer.short.toInt() and 0xFFFF |
| 69 | + val total = buffer.short.toInt() and 0xFFFF |
| 70 | + return Pair(current, total) |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * Extracts payload data from a raw BLE packet (strips header). |
| 75 | + */ |
| 76 | + fun getPayload(packet: ByteArray): ByteArray { |
| 77 | + if (packet.size <= BleConstants.CHUNK_HEADER_SIZE) return byteArrayOf() |
| 78 | + return packet.sliceArray(BleConstants.CHUNK_HEADER_SIZE until packet.size) |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Helper class to reassemble chunks as they arrive. |
| 83 | + */ |
| 84 | + class Reassembler { |
| 85 | + private val chunks = mutableMapOf<Int, ByteArray>() |
| 86 | + private var totalChunks = -1 |
| 87 | + |
| 88 | + fun addChunk(packet: ByteArray): String? { |
| 89 | + val header = parseHeader(packet) ?: return null |
| 90 | + val current = header.first |
| 91 | + val total = header.second |
| 92 | + |
| 93 | + if (totalChunks != -1 && totalChunks != total) { |
| 94 | + // New transmission started or mismatch, reset |
| 95 | + chunks.clear() |
| 96 | + } |
| 97 | + totalChunks = total |
| 98 | + |
| 99 | + chunks[current] = getPayload(packet) |
| 100 | + |
| 101 | + if (chunks.size == totalChunks) { |
| 102 | + val result = reassemble(chunks) |
| 103 | + chunks.clear() |
| 104 | + totalChunks = -1 |
| 105 | + return result |
| 106 | + } |
| 107 | + return null |
| 108 | + } |
| 109 | + |
| 110 | + fun clear() { |
| 111 | + chunks.clear() |
| 112 | + totalChunks = -1 |
| 113 | + } |
| 114 | + } |
| 115 | +} |
0 commit comments