mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Merge 3754d416d2ce070a1b6164b4626c0b2914323bb2 into c127eb83ab94c069c32d37530d2faecd381cd2a8
This commit is contained in:
commit
afb2f16b86
@ -19,16 +19,28 @@ fun ByteArray.hexEncodedString(): String {
|
||||
}
|
||||
|
||||
fun String.dataFromHexString(): ByteArray? {
|
||||
val len = this.length / 2
|
||||
val data = ByteArray(len)
|
||||
var index = 0
|
||||
|
||||
for (i in 0 until len) {
|
||||
val hexByte = this.substring(i * 2, i * 2 + 2)
|
||||
val byte = hexByte.toIntOrNull(16)?.toByte() ?: return null
|
||||
data[index++] = byte
|
||||
// Match iOS Data(hexString:): trim, optional 0x/0X prefix, reject odd length.
|
||||
var hex = this.trim()
|
||||
if (hex.startsWith("0x", ignoreCase = true)) {
|
||||
hex = hex.substring(2)
|
||||
}
|
||||
|
||||
if (hex.isEmpty()) {
|
||||
return ByteArray(0)
|
||||
}
|
||||
// Radix parsing accepts signs and Unicode digits; encoded bytes require ASCII hex.
|
||||
if (hex.length % 2 != 0 || hex.any { it !in '0'..'9' && it !in 'a'..'f' && it !in 'A'..'F' }) {
|
||||
return null
|
||||
}
|
||||
|
||||
val len = hex.length / 2
|
||||
val data = ByteArray(len)
|
||||
|
||||
for (i in 0 until len) {
|
||||
val hexByte = hex.substring(i * 2, i * 2 + 2)
|
||||
val byte = hexByte.toIntOrNull(16)?.toByte() ?: return null
|
||||
data[i] = byte
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
65
app/src/test/java/com/bitchat/android/util/HexStringTest.kt
Normal file
65
app/src/test/java/com/bitchat/android/util/HexStringTest.kt
Normal file
@ -0,0 +1,65 @@
|
||||
package com.bitchat.android.util
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class HexStringTest {
|
||||
|
||||
@Test
|
||||
fun `decodes even-length lowercase hex`() {
|
||||
assertArrayEquals(
|
||||
byteArrayOf(0x0a, 0xbc.toByte()),
|
||||
"0abc".dataFromHexString()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects odd-length hex instead of truncating`() {
|
||||
// Previously length/2 silently dropped the trailing nibble.
|
||||
assertNull("abc".dataFromHexString())
|
||||
assertNull("0ab".dataFromHexString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `strips optional 0x prefix like iOS`() {
|
||||
assertArrayEquals(
|
||||
byteArrayOf(0xde.toByte(), 0xad.toByte()),
|
||||
"0xdead".dataFromHexString()
|
||||
)
|
||||
assertArrayEquals(
|
||||
byteArrayOf(0xbe.toByte(), 0xef.toByte()),
|
||||
"0Xbeef".dataFromHexString()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty string and empty after prefix yield empty array`() {
|
||||
assertArrayEquals(ByteArray(0), "".dataFromHexString())
|
||||
assertArrayEquals(ByteArray(0), "0x".dataFromHexString())
|
||||
assertTrue(" ".dataFromHexString()!!.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects non-hex characters`() {
|
||||
assertNull("zz".dataFromHexString())
|
||||
assertNull("0xgg".dataFromHexString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects signed byte chunks and non-ascii digits`() {
|
||||
for (invalid in listOf("0x-1", "0x+1", "-1", "+1", "00-1", "0X+100", "01")) {
|
||||
assertNull(invalid.dataFromHexString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trips with hexEncodedString`() {
|
||||
val original = byteArrayOf(0x00, 0x7f, 0xff.toByte())
|
||||
val encoded = original.hexEncodedString()
|
||||
assertEquals("007fff", encoded)
|
||||
assertArrayEquals(original, encoded.dataFromHexString())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user