diff --git a/android/app/src/main/java/com/eddy/atomrelay/UsbKeyReceiver.kt b/android/app/src/main/java/com/eddy/atomrelay/UsbKeyReceiver.kt deleted file mode 100644 index 63aa09b..0000000 --- a/android/app/src/main/java/com/eddy/atomrelay/UsbKeyReceiver.kt +++ /dev/null @@ -1,318 +0,0 @@ -package com.eddy.atomrelay - -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.hardware.usb.UsbConstants -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbDeviceConnection -import android.hardware.usb.UsbEndpoint -import android.hardware.usb.UsbInterface -import android.hardware.usb.UsbManager -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.widget.Toast -import java.nio.charset.StandardCharsets -import java.security.SecureRandom - -class UsbKeyReceiver : BroadcastReceiver() { - - companion object { - private const val TAG = "UsbKeyReceiver" - private const val ACTION_USB_PERMISSION = "com.eddy.atomrelay.USB_PERMISSION" - private const val ESP_VENDOR_ID = 0x303a - private const val KEY_TIMEOUT_MS = 5000 - private const val MAX_RETRIES = 5 - private const val RETRY_DELAY_MS = 1000L - private const val CDC_DATA_INTERFACE_CLASS = 0x0A - private const val SET_KEY_TIMEOUT_MS = 3000 - } - - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - UsbManager.ACTION_USB_DEVICE_ATTACHED -> { - val device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) ?: return - Log.d(TAG, "USB device attached: vid=0x${device.vendorId.toString(16)} pid=0x${device.productId.toString(16)}") - if (device.vendorId != ESP_VENDOR_ID) return - - val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager - val currentDevice = findEspDevice(usbManager) ?: device - if (usbManager.hasPermission(currentDevice)) { - Thread { readKeyRaw(context, usbManager, currentDevice) }.start() - } else { - requestPermission(context, usbManager, currentDevice) - } - } - ACTION_USB_PERMISSION -> { - if (!intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { - Log.w(TAG, "USB permission denied") - Handler(Looper.getMainLooper()).post { - Toast.makeText(context, "USB-toegang geweigerd", Toast.LENGTH_SHORT).show() - } - return - } - Log.d(TAG, "USB permission granted") - val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager - val device = findEspDevice(usbManager) - if (device != null) { - Thread { readKeyRaw(context, usbManager, device) }.start() - } else { - Log.e(TAG, "ESP device not found after permission grant") - Handler(Looper.getMainLooper()).post { - Toast.makeText(context, "ESP niet (meer) gevonden", Toast.LENGTH_SHORT).show() - } - } - } - } - } - - private fun findEspDevice(usbManager: UsbManager): UsbDevice? { - return usbManager.deviceList.values.firstOrNull { it.vendorId == ESP_VENDOR_ID } - } - - private fun generateRandomHexKey(): String { - val random = SecureRandom() - val bytes = ByteArray(32) - random.nextBytes(bytes) - return bytes.joinToString("") { "%02X".format(it) } - } - - private fun requestPermission(context: Context, usbManager: UsbManager, device: UsbDevice) { - val flags = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - } else { - PendingIntent.FLAG_UPDATE_CURRENT - } - val permIntent = PendingIntent.getBroadcast( - context, 0, - Intent(context, UsbKeyReceiver::class.java) - .setAction(ACTION_USB_PERMISSION), - flags - ) - usbManager.requestPermission(device, permIntent) - Log.d(TAG, "Requesting USB permission for ${device.deviceName}") - } - - private fun readKeyRaw(context: Context, usbManager: UsbManager, device: UsbDevice) { - val handler = Handler(Looper.getMainLooper()) - - for (attempt in 1..MAX_RETRIES) { - Log.d(TAG, "Read attempt $attempt/$MAX_RETRIES") - var connection: UsbDeviceConnection? = null - - try { - val currentDevice = findEspDevice(usbManager) ?: device - connection = usbManager.openDevice(currentDevice) - if (connection == null) { - Log.w(TAG, "Attempt $attempt: openDevice returned null") - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - continue - } - handler.post { Toast.makeText(context, "Kan USB-apparaat niet openen", Toast.LENGTH_SHORT).show() } - return - } - - val dataInterface = findCdcDataInterface(currentDevice) - if (dataInterface == null) { - Log.w(TAG, "Attempt $attempt: no CDC data interface") - connection.close() - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - continue - } - handler.post { Toast.makeText(context, "Geen CDC interface gevonden op ESP", Toast.LENGTH_SHORT).show() } - return - } - - if (!connection.claimInterface(dataInterface, true)) { - Log.w(TAG, "Attempt $attempt: failed to claim interface") - connection.close() - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - continue - } - handler.post { Toast.makeText(context, "Kan USB interface niet claimen", Toast.LENGTH_SHORT).show() } - return - } - - val epOut = findBulkEndpoint(dataInterface, UsbConstants.USB_DIR_OUT) - val epIn = findBulkEndpoint(dataInterface, UsbConstants.USB_DIR_IN) - if (epOut == null || epIn == null) { - Log.w(TAG, "Attempt $attempt: missing endpoints") - connection.close() - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - continue - } - handler.post { Toast.makeText(context, "USB endpoints niet gevonden", Toast.LENGTH_SHORT).show() } - return - } - - Log.d(TAG, "CDC interface claimed, endpoints ready (out max=${epOut.maxPacketSize}, in max=${epIn.maxPacketSize})") - - val controlIntf = findCdcControlInterface(currentDevice) - if (controlIntf != null) { - connection.claimInterface(controlIntf, true) - connection.controlTransfer( - UsbConstants.USB_DIR_OUT or UsbConstants.USB_TYPE_CLASS or 0x01, - 0x20, - 0, - controlIntf.id, - byteArrayOf(0x00, 0xC2.toByte(), 0x01, 0x00, 0x00, 0x00, 0x08), - 7, - 500 - ) - connection.controlTransfer( - UsbConstants.USB_DIR_OUT or UsbConstants.USB_TYPE_CLASS or 0x01, - 0x22, - 3, - controlIntf.id, - null, - 0, - 500 - ) - Log.d(TAG, "CDC control interface configured (line coding + DTR/RTS)") - } else { - Log.w(TAG, "No CDC control interface found, trying without") - } - - Thread.sleep(300) - - val cmd = "KEY?\n".toByteArray(StandardCharsets.US_ASCII) - val written = connection.bulkTransfer(epOut, cmd, cmd.size, 1000) - Log.d(TAG, "Sent KEY? (${written} bytes)") - - Thread.sleep(300) - - val buf = ByteArray(512) - val sb = StringBuilder() - val startTime = System.currentTimeMillis() - - while (System.currentTimeMillis() - startTime < KEY_TIMEOUT_MS) { - val len = connection.bulkTransfer(epIn, buf, buf.size, 500) - if (len > 0) { - sb.append(String(buf, 0, len, StandardCharsets.US_ASCII)) - if ((sb.contains("HMAC_KEY:") || sb.contains("NO_KEY")) && sb.contains("\n")) { - break - } - } - } - - val response = sb.toString() - Log.d(TAG, "Serial response: ${response.take(200)}") - - val keyLine = response.lines().firstOrNull { it.startsWith("HMAC_KEY:") } - val noKey = response.lines().any { it.trim() == "NO_KEY" } - - if (keyLine != null) { - val hexKey = keyLine.removePrefix("HMAC_KEY:").trim() - if (hexKey.length == 64 && hexKey.all { it in "0123456789ABCDEFabcdef" }) { - val upperKey = hexKey.uppercase() - val prefs = PrefsHelper.getPrefs(context) - prefs.edit().putString(PrefsHelper.PREF_KEY, upperKey).apply() - Log.d(TAG, "HMAC key saved (${upperKey.length} chars)") - handler.post { - Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show() - } - return - } else { - Log.e(TAG, "Invalid key format: length=${hexKey.length}") - handler.post { Toast.makeText(context, "Ongeldig sleutel formaat", Toast.LENGTH_SHORT).show() } - return - } - } else if (noKey) { - Log.d(TAG, "ESP has no key — generating and provisioning") - val newKey = generateRandomHexKey() - Log.d(TAG, "Generated key (${newKey.length} chars)") - - val setKeyCmd = "SET_KEY:$newKey\n".toByteArray(StandardCharsets.US_ASCII) - val written = connection.bulkTransfer(epOut, setKeyCmd, setKeyCmd.size, 1000) - Log.d(TAG, "Sent SET_KEY (${written} bytes)") - - Thread.sleep(300) - - val okBuf = ByteArray(256) - val okSb = StringBuilder() - val okStart = System.currentTimeMillis() - while (System.currentTimeMillis() - okStart < SET_KEY_TIMEOUT_MS) { - val len = connection.bulkTransfer(epIn, okBuf, okBuf.size, 500) - if (len > 0) { - okSb.append(String(okBuf, 0, len, StandardCharsets.US_ASCII)) - if (okSb.contains("OK") && okSb.contains("\n")) break - } - } - Log.d(TAG, "SET_KEY response: ${okSb.toString().take(100)}") - - if (okSb.contains("OK")) { - Log.d(TAG, "Key provisioned successfully, saving to prefs") - val prefs = PrefsHelper.getPrefs(context) - prefs.edit().putString(PrefsHelper.PREF_KEY, newKey).apply() - handler.post { - Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show() - } - return - } else { - Log.w(TAG, "SET_KEY failed, no OK response") - connection.close() - connection = null - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - } - } - } else { - Log.w(TAG, "Attempt $attempt: no HMAC_KEY in response, will retry") - connection.close() - connection = null - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - } - } - } catch (e: Exception) { - Log.e(TAG, "Attempt $attempt error: ${e.message}", e) - if (attempt < MAX_RETRIES) { - Thread.sleep(RETRY_DELAY_MS) - } else { - handler.post { Toast.makeText(context, "Fout bij uitlezen: ${e.message}", Toast.LENGTH_SHORT).show() } - } - } finally { - try { connection?.close() } catch (e: Exception) {} - } - } - - Log.e(TAG, "All $MAX_RETRIES attempts failed") - handler.post { Toast.makeText(context, "Sleutel niet gevonden na $MAX_RETRIES pogingen", Toast.LENGTH_SHORT).show() } - } - - private fun findCdcDataInterface(device: UsbDevice): UsbInterface? { - for (i in 0 until device.interfaceCount) { - val intf = device.getInterface(i) - if (intf.interfaceClass == CDC_DATA_INTERFACE_CLASS) { - return intf - } - } - return null - } - - private fun findCdcControlInterface(device: UsbDevice): UsbInterface? { - for (i in 0 until device.interfaceCount) { - val intf = device.getInterface(i) - if (intf.interfaceClass == 0x02) { - return intf - } - } - return null - } - - private fun findBulkEndpoint(intf: UsbInterface, direction: Int): UsbEndpoint? { - for (i in 0 until intf.endpointCount) { - val ep = intf.getEndpoint(i) - if (ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK && ep.direction == direction) { - return ep - } - } - return null - } -} \ No newline at end of file