diff --git a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt index 1cd6e67b..8b2d2186 100644 --- a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt +++ b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt @@ -1,13 +1,28 @@ package com.capacitorjs.community.plugins.bluetoothle +// Create a LUT for high performance ByteArray conversion +val HEX_LOOKUP_TABLE = IntArray(256) { + val hexChars = "0123456789ABCDEF" + val h: Int = (hexChars[(it shr 4)].code shl 8) + val l: Int = hexChars[(it and 0x0F)].code + (h or l) +} -fun bytesToString(bytes: ByteArray): String { - val stringBuilder = StringBuilder(bytes.size) - for (byte in bytes) { - // byte to hex string - stringBuilder.append(String.format("%02X ", byte)) +// Custom implementation of ByteArray.toHexString until stdlib stabilizes +private fun ByteArray.toHexString(): String { + val result = CharArray(this.size * 2); + var i = 0; + for (byte in this) { + val hx = HEX_LOOKUP_TABLE[byte.toInt() and 0xFF] + result[i] = (hx shr 8).toChar() + result[i+1] = (hx and 0xFF).toChar() + i+=2 } - return stringBuilder.toString() + return result.concatToString() +} + +fun bytesToString(bytes: ByteArray): String { + return bytes.toHexString() } fun stringToBytes(value: String): ByteArray { diff --git a/android/src/test/java/com/capacitorjs/community/plugins/bluetoothle/ConversionKtTest.kt b/android/src/test/java/com/capacitorjs/community/plugins/bluetoothle/ConversionKtTest.kt index 42687234..8496dabd 100644 --- a/android/src/test/java/com/capacitorjs/community/plugins/bluetoothle/ConversionKtTest.kt +++ b/android/src/test/java/com/capacitorjs/community/plugins/bluetoothle/ConversionKtTest.kt @@ -9,7 +9,7 @@ class ConversionKtTest : TestCase() { fun testBytesToString() { val input = byteArrayOfInts(0xA1, 0x2E, 0x38, 0xD4, 0x89, 0xC3) val output = bytesToString(input) - assertEquals("A1 2E 38 D4 89 C3 ", output) + assertEquals("A12E38D489C3", output) } fun testEmptyBytesToString() { diff --git a/ios/Plugin/Conversion.swift b/ios/Plugin/Conversion.swift index df9bd1b1..9ebbc13a 100644 --- a/ios/Plugin/Conversion.swift +++ b/ios/Plugin/Conversion.swift @@ -15,12 +15,36 @@ func descriptorValueToString(_ value: Any) -> String { return "" } -func dataToString(_ data: Data) -> String { - var valueString = "" - for byte in data { - valueString += String(format: "%02hhx ", byte) +extension Data { + func toHexString() -> String { + let hexChars = Array("0123456789abcdef".utf8) + if #available(iOS 14, *) { + return String(unsafeUninitializedCapacity: self.count*2) { (ptr) -> Int in + var strp = ptr.baseAddress! + for byte in self { + strp[0] = hexChars[Int(byte >> 4)] + strp[1] = hexChars[Int(byte & 0xF)] + strp += 2 + } + return 2 * self.count + } + } else { + // Fallback implementation for iOS < 14, a bit slower + var result = "" + result.reserveCapacity(self.count * 2) + for byte in self { + let high = Int(byte >> 4) + let low = Int(byte & 0xF) + result.append(Character(UnicodeScalar(hexChars[high]))) + result.append(Character(UnicodeScalar(hexChars[low]))) + } + return result + } } - return valueString +} + +func dataToString(_ data: Data) -> String { + return data.toHexString() } func stringToData(_ dataString: String) -> Data { diff --git a/ios/PluginTests/ConversionTests.swift b/ios/PluginTests/ConversionTests.swift index 60d2c065..71fabac6 100644 --- a/ios/PluginTests/ConversionTests.swift +++ b/ios/PluginTests/ConversionTests.swift @@ -8,7 +8,7 @@ class ConversionTests: XCTestCase { func testDataToString() throws { let input = Data([0xA1, 0x2E, 0x38, 0xD4, 0x89, 0xC3]) let output = dataToString(input) - XCTAssertEqual(output, "a1 2e 38 d4 89 c3 ") + XCTAssertEqual(output, "a12e38d489c3") } func testStringToData() throws { @@ -44,11 +44,11 @@ class ConversionTests: XCTestCase { } func testDescriptorValueToString() throws { - XCTAssertEqual(descriptorValueToString("Hello"), "48 65 6c 6c 6f ") - XCTAssertEqual(descriptorValueToString(Data([0, 5, 255])), "00 05 ff ") - XCTAssertEqual(descriptorValueToString(UInt16(258)), "02 01 ") - XCTAssertEqual(descriptorValueToString(UInt16(1)), "01 00 ") - XCTAssertEqual(descriptorValueToString(NSNumber(1)), "01 00 ") + XCTAssertEqual(descriptorValueToString("Hello"), "48656c6c6f") + XCTAssertEqual(descriptorValueToString(Data([0, 5, 255])), "0005ff") + XCTAssertEqual(descriptorValueToString(UInt16(258)), "0201") + XCTAssertEqual(descriptorValueToString(UInt16(1)), "0100") + XCTAssertEqual(descriptorValueToString(NSNumber(1)), "0100") XCTAssertEqual(descriptorValueToString(0), "") } diff --git a/src/conversion.spec.ts b/src/conversion.spec.ts index 76835b33..1a38ab08 100644 --- a/src/conversion.spec.ts +++ b/src/conversion.spec.ts @@ -114,6 +114,15 @@ describe('hexStringToDataView', () => { expect(result.getUint8(2)).toEqual(200); }); + it('should work without spaces', () => { + const value = '0005C8'; + const result = hexStringToDataView(value); + expect(result.byteLength).toEqual(3); + expect(result.getUint8(0)).toEqual(0); + expect(result.getUint8(1)).toEqual(5); + expect(result.getUint8(2)).toEqual(200); + }); + it('should convert an empty hex string to a DataView', () => { const value = ''; const result = hexStringToDataView(value); diff --git a/src/conversion.ts b/src/conversion.ts index a39c1597..8698e275 100644 --- a/src/conversion.ts +++ b/src/conversion.ts @@ -35,13 +35,28 @@ export function numberToUUID(value: number): string { return `0000${value.toString(16).padStart(4, '0')}-0000-1000-8000-00805f9b34fb`; } -export function hexStringToDataView(value: string): DataView { - const numbers: number[] = value - .trim() - .split(' ') - .filter((e) => e !== '') - .map((s) => parseInt(s, 16)); - return numbersToDataView(numbers); +/** + * Convert a string of hex into a DataView of raw bytes. + * Note: characters other than [0-9a-fA-F] are ignored + * @param hex string of values, e.g. "00 01 02" or "000102" + * @return DataView of raw bytes + */ +export function hexStringToDataView(hex: string): DataView { + const bin = []; + let i, + c, + isEmpty = 1, + buffer = 0; + for (i = 0; i < hex.length; i++) { + c = hex.charCodeAt(i); + if ((c > 47 && c < 58) || (c > 64 && c < 71) || (c > 96 && c < 103)) { + buffer = (buffer << 4) ^ ((c > 64 ? c + 9 : c) & 15); + if ((isEmpty ^= 1)) { + bin.push(buffer & 0xff); + } + } + } + return numbersToDataView(bin); } export function dataViewToHexString(value: DataView): string {