ByteUtils.kt
package io.github.tritbool.euc.ble.core
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.experimental.xor
/**
* Utility class for byte operations commonly used in BLE protocols
*/
object ByteUtils {
/**
* Convert bytes to hex string
*/
fun bytesToHex(bytes: ByteArray, separator: String = " "): String {
return bytes.joinToString(separator) { "%02X".format(it) }
}
/**
* Convert hex string to byte array
*/
fun hexToBytes(hexString: String): ByteArray {
val cleanHexString=hexString.filterNot { it.isWhitespace() }
val length = cleanHexString.length
val byteArray = ByteArray(length / 2)
for (i in 0 until length step 2) {
val byteString = cleanHexString.substring(i, i + 2)
byteArray[i / 2] = byteString.toInt(16).toByte()
}
return byteArray
}
/**
* Get unsigned byte value (0-255)
*/
fun getUnsignedByte(data: ByteArray, offset: Int): Int {
return data[offset].toInt() and 0xFF
}
/**
* Get unsigned short (2 bytes) in little-endian format
*/
fun getUnsignedShortLE(data: ByteArray, offset: Int): Int {
return (getUnsignedByte(data, offset) or (getUnsignedByte(data, offset + 1) shl 8))
}
/**
* Get unsigned short (2 bytes) in big-endian format
*/
fun getUnsignedShortBE(data: ByteArray, offset: Int): Int {
return (getUnsignedByte(data, offset) shl 8) or getUnsignedByte(data, offset + 1)
}
/**
* Get signed short (2 bytes) in little-endian format
*/
fun getSignedShortLE(data: ByteArray, offset: Int): Short {
return ByteBuffer.wrap(data, offset, 2).order(ByteOrder.LITTLE_ENDIAN).short
}
/**
* Get signed short (2 bytes) in big-endian format
*/
fun getSignedShortBE(data: ByteArray, offset: Int): Short {
return ByteBuffer.wrap(data, offset, 2).order(ByteOrder.BIG_ENDIAN).short
}
/**
* Get unsigned int (4 bytes) in little-endian format
*/
fun getUnsignedIntLE(data: ByteArray, offset: Int): Long {
return (getUnsignedShortLE(data, offset).toLong() or
(getUnsignedShortLE(data, offset + 2).toLong() shl 16))
}
/**
* Get unsigned int (4 bytes) in big-endian format
*/
fun getUnsignedIntBE(data: ByteArray, offset: Int): Long {
return (getUnsignedShortBE(data, offset).toLong() shl 16) or
getUnsignedShortBE(data, offset + 2).toLong()
}
/**
* Get signed int (4 bytes) in little-endian format
*/
fun getSignedIntLE(data: ByteArray, offset: Int): Int {
return ByteBuffer.wrap(data, offset, 4).order(ByteOrder.LITTLE_ENDIAN).int
}
/**
* Get signed int (4 bytes) in big-endian format
*/
fun getSignedIntBE(data: ByteArray, offset: Int): Int {
return ByteBuffer.wrap(data, offset, 4).order(ByteOrder.BIG_ENDIAN).int
}
/**
* Convert short to byte array in little-endian format
*/
fun shortToBytesLE(value: Short): ByteArray {
return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array()
}
/**
* Convert short to byte array in big-endian format
*/
fun shortToBytesBE(value: Short): ByteArray {
return ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putShort(value).array()
}
/**
* Convert int to byte array in little-endian format
*/
fun intToBytesLE(value: Int): ByteArray {
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array()
}
/**
* Convert int to byte array in big-endian format
*/
fun intToBytesBE(value: Int): ByteArray {
return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(value).array()
}
/**
* Calculate checksum for byte array
*/
fun calculateChecksum(data: ByteArray, offset: Int, length: Int): Byte {
var checksum: Byte = 0
for (i in offset until offset + length) {
checksum = (checksum + data[i]).toByte()
}
return checksum
}
/**
* Calculate XOR checksum for byte array
*/
fun calculateXorChecksum(data: ByteArray, offset: Int, length: Int): Byte {
var checksum: Byte = 0
for (i in offset until offset + length) {
checksum = (checksum xor data[i]).toByte()
}
return checksum
}
/**
* Check if byte array starts with specific pattern
*/
fun startsWith(data: ByteArray, pattern: ByteArray): Boolean {
if (data.size < pattern.size) return false
for (i in pattern.indices) {
if (data[i] != pattern[i]) return false
}
return true
}
/**
* Find the index of a pattern in byte array
*/
fun findPattern(data: ByteArray, pattern: ByteArray): Int {
if (data.size < pattern.size) return -1
for (i in 0..data.size - pattern.size) {
var match = true
for (j in pattern.indices) {
if (data[i + j] != pattern[j]) {
match = false
break
}
}
if (match) return i
}
return -1
}
// -- Safe access methods (return null if out of bounds) --
fun tryGetUnsignedByte(data: ByteArray, offset: Int): Int? =
if (offset in 0 until data.size) data[offset].toInt() and 0xFF else null
fun tryGetSignedByte(data: ByteArray, offset: Int): Int? =
tryGetUnsignedByte(data, offset)?.let { raw -> if (raw >= 0x80) raw - 0x100 else raw }
fun tryGetUnsignedShortLE(data: ByteArray, offset: Int): Int? =
if (offset + 1 < data.size) getUnsignedShortLE(data, offset) else null
fun tryGetUnsignedShortBE(data: ByteArray, offset: Int): Int? =
if (offset + 1 < data.size) getUnsignedShortBE(data, offset) else null
fun tryGetSignedShortLE(data: ByteArray, offset: Int): Short? =
if (offset + 1 < data.size) getSignedShortLE(data, offset) else null
fun tryGetSignedShortBE(data: ByteArray, offset: Int): Short? =
if (offset + 1 < data.size) getSignedShortBE(data, offset) else null
fun tryGetUnsignedIntLE(data: ByteArray, offset: Int): Long? =
if (offset + 3 < data.size) getUnsignedIntLE(data, offset) else null
fun tryGetUnsignedIntBE(data: ByteArray, offset: Int): Long? =
if (offset + 3 < data.size) getUnsignedIntBE(data, offset) else null
fun tryGetSignedIntLE(data: ByteArray, offset: Int): Int? =
if (offset + 3 < data.size) getSignedIntLE(data, offset) else null
fun tryGetSignedIntBE(data: ByteArray, offset: Int): Int? =
if (offset + 3 < data.size) getSignedIntBE(data, offset) else null
}