FixedSizeFrameParser.kt
package io.github.tritbool.euc.ble.frames
/**
* Compatible with legacy implementation: looks for an optional header,
* extracts fixed-size frames and validates an optional footer.
*
* @param frameSize The fixed size of each frame in bytes
* @param header Optional header bytes that must precede each frame
* @param footer Optional footer bytes that must follow each frame
*/
class FixedSizeFrameParser(
private val frameSize: Int,
private val header: ByteArray = byteArrayOf(),
private val footer: ByteArray = byteArrayOf()
) : FrameParser {
private val buffer = ArrayList<Byte>()
override fun appendAndExtract(data: ByteArray): List<ByteArray> {
val out = mutableListOf<ByteArray>()
for (b in data) buffer.add(b)
extractReadyFramesInto(out)
return out
}
override fun reset() {
buffer.clear()
}
private fun extractReadyFramesInto(out: MutableList<ByteArray>) {
var idx = findHeaderIndex(0)
while (idx >= 0) {
if (buffer.size - idx < frameSize) break
val end = idx + frameSize
val candidate = ByteArray(frameSize) { i -> buffer[idx + i] }
if (matchesFooter(candidate)) {
out.add(candidate)
repeat(end) { buffer.removeAt(0) }
idx = findHeaderIndex(0)
} else {
buffer.removeAt(0)
idx = findHeaderIndex(0)
}
}
if (idx < 0) {
val keep = maxOf(0, header.size - 1)
while (buffer.size > keep) buffer.removeAt(0)
}
}
private fun findHeaderIndex(from: Int): Int {
if (header.isEmpty()) return 0.takeIf { buffer.size >= 0 } ?: -1
if (buffer.size < header.size) return -1
var i = from
val maxStart = buffer.size - header.size
while (i <= maxStart) {
var ok = true
for (j in header.indices) {
if (buffer[i + j] != header[j]) { ok = false; break }
}
if (ok) return i
i++
}
return -1
}
private fun matchesFooter(frame: ByteArray): Boolean {
if (footer.isEmpty()) return true
val start = frame.size - footer.size
if (start < 0) return false
for (i in footer.indices) {
if (frame[start + i] != footer[i]) return false
}
return true
}
}