mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Add helper for HEX string to byte array conversion
This commit introduces a new helper function `fromHex()` that converts a HEX string (with optional separators like spaces or hyphens) into a byte array. It also adds a corresponding `toHex()` function for converting a byte array to a HEX string. These helpers are now used in various parts of the codebase, including: - Fake BLE data generation - AirPod key input dialog - RPA checker tests - BaseAirPodsTest Additionally, a unit test for these conversion functions has been added.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
package eu.darken.capod.common
|
||||
|
||||
import java.util.*
|
||||
import java.util.BitSet
|
||||
|
||||
fun Byte.toHex(): String = String.format("%02X", this)
|
||||
fun UByte.toHex(): String = this.toByte().toHex()
|
||||
@@ -18,3 +18,12 @@ fun UShort.isBitSet(pos: Int): Boolean = this.toShort().isBitSet(pos)
|
||||
|
||||
fun UShort.toBinaryString(): String = Integer.toBinaryString(this.toInt()).padStart(4, '0')
|
||||
fun UByte.toBinaryString(): String = Integer.toBinaryString(this.toInt()).padStart(8, '0')
|
||||
|
||||
|
||||
fun ByteArray.toHex(separator: String = "-"): String = joinToString(separator) { "%02X".format(it) }
|
||||
fun String.fromHex(): ByteArray = this
|
||||
.replace(" ", "")
|
||||
.replace("-", "")
|
||||
.also { require(it.length % 2 == 0) { "Not a HEX string, length: ${it.length}" } }
|
||||
.chunked(2).map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.darken.capod.common.bluetooth
|
||||
import dagger.Reusable
|
||||
import eu.darken.capod.common.SystemClockWrap
|
||||
import eu.darken.capod.common.debug.DebugSettings
|
||||
import eu.darken.capod.common.fromHex
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
@@ -25,7 +26,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "78:73:AF:B4:85:22",
|
||||
rssi = Random.nextInt(100) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -36,7 +37,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "78:73:FF:B4:85:5E",
|
||||
rssi = Random.nextInt(100) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -47,7 +48,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "4E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -57,7 +58,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "7E:E5:C7:65:D2:B5",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -67,7 +68,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "5E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -78,7 +79,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "5E:9E:D1:29:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -89,22 +90,11 @@ class FakeBleData @Inject constructor(
|
||||
address = "6E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
|
||||
return fakeDevices
|
||||
}
|
||||
|
||||
|
||||
private fun String.hexToByteArray(): ByteArray {
|
||||
val trimmed = this
|
||||
.replace(" ", "")
|
||||
.replace(">", "")
|
||||
.replace("<", "")
|
||||
require(trimmed.length % 2 == 0) { "Not a HEX string" }
|
||||
return trimmed.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package eu.darken.capod.common
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import testhelpers.BaseTest
|
||||
|
||||
class ByteArrayExtensionsTest : BaseTest() {
|
||||
|
||||
@Test
|
||||
fun `hex - ByteArray conversion`() = runTest {
|
||||
val addr = "78-73-AF-B4-85-22"
|
||||
val raw = addr.fromHex()
|
||||
raw.toHex() shouldBe addr
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.darken.capod.monitor.core
|
||||
|
||||
import eu.darken.capod.common.fromHex
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.jupiter.api.Test
|
||||
import testhelpers.BaseTest
|
||||
@@ -11,10 +12,28 @@ class RPACheckerTest : BaseTest() {
|
||||
val checker = RPAChecker()
|
||||
checker.verify(
|
||||
address = "5A:16:2B:91:D1:CD",
|
||||
irk = "79-04-65-1E-E2-CC-D9-26-F2-6E-20-EE-3E-CC-DE-79"
|
||||
.replace("-", "")
|
||||
.also { require(it.length % 2 == 0) { "Not a HEX string" } }
|
||||
.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
irk = "79-04-65-1E-E2-CC-D9-26-F2-6E-20-EE-3E-CC-DE-79".fromHex(),
|
||||
) shouldBe true
|
||||
checker.verify(
|
||||
address = "5A:16:2B:91:D1:CD",
|
||||
irk = "79-04-65-1E-E2-CC-D9-26-F2-6E-20-EE-3E-CC-DE-AA".fromHex(),
|
||||
) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bad input check`() {
|
||||
val checker = RPAChecker()
|
||||
checker.verify(
|
||||
address = "5A:16:2B:91:D1:CD",
|
||||
irk = "".fromHex(),
|
||||
) shouldBe false
|
||||
checker.verify(
|
||||
address = "",
|
||||
irk = "79-04-65-1E-E2-CC-D9-26-F2-6E-20-EE-3E-CC-DE-AA".fromHex(),
|
||||
) shouldBe false
|
||||
checker.verify(
|
||||
address = "",
|
||||
irk = "".fromHex(),
|
||||
) shouldBe false
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import eu.darken.capod.common.SystemClockWrap
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.fromHex
|
||||
import eu.darken.capod.common.serialization.SerializationModule
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -39,12 +40,9 @@ abstract class BaseAirPodsTest : BaseTest() {
|
||||
}
|
||||
|
||||
private fun hexToByteArray(hex: String): ByteArray = hex
|
||||
.replace(" ", "")
|
||||
.replace(">", "")
|
||||
.replace("<", "")
|
||||
.replace("-", "")
|
||||
.also { require(it.length % 2 == 0) { "Not a HEX string" } }
|
||||
.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
.fromHex()
|
||||
|
||||
private fun cleanKey(key: String): ByteArray = hexToByteArray(key)
|
||||
.also { require(it.size == 16) { "Not a valid key: ${it.size} byte" } }
|
||||
|
||||
Reference in New Issue
Block a user