Commit graph

51 commits

Author SHA1 Message Date
eddy
71b13c51ee Replace volatile cancel flag with coroutine Job cancellation in EspFlasher 2026-07-26 22:58:08 +02:00
eddy
ece115fbd6 Update README with RECEIVER_EXPORTED note 2026-07-26 22:51:54 +02:00
eddy
2dd3f00de4 Use RECEIVER_EXPORTED for USB system broadcasts on API 34+ 2026-07-26 22:51:36 +02:00
eddy
11ba8f3b8f Update README with sequence counter persistence 2026-07-26 22:34:23 +02:00
eddy
af3810554e Persist sequence counter in EncryptedSharedPreferences 2026-07-26 22:33:43 +02:00
eddy
bbf01946ac Convert Thread+Handler to lifecycle-aware coroutines, add GPL-3.0 license 2026-07-26 20:43:37 +02:00
eddy
4a9a15bca5 Update README with all code review changes 2026-07-26 17:29:23 +02:00
eddy
a80abc5c0c Merge branch 'fix/espflasher-cancellable': cancellable flasher + dialog fixes + key exchange reliability 2026-07-26 17:24:40 +02:00
eddy
d21a560d0c Shorten key exchange error toast text 2026-07-26 17:23:55 +02:00
eddy
1d17251341 Add retry hint to key exchange errors and cancel button to flash dialog
1. Append 'probeer het opnieuw' to key exchange error toasts.
2. Add 'Annuleren' button to flash progress dialog that calls
   flasher.cancel() and cleanupAction().
2026-07-26 17:20:49 +02:00
eddy
5acde17efb Fix flash dialog stacking and key exchange reliability
1. Dismiss waiting dialog when flash progress dialog starts,
   preventing orphaned spinner dialog after flash completes.
2. Increase key exchange retries from 3 to 5 and initial delay
   from 500ms to 1000ms to give ESP more time to be ready
   after USB permission grant.
2026-07-26 17:14:50 +02:00
eddy
e116f2b493 Make EspFlasher cancellable to prevent resource leak
Add @Volatile cancelled flag with cancel() method. Check flag at 3
checkpoints (after initial sleep, after transport setup, before each
flash step) so the thread exits early and closes the USB connection
in the finally block. SettingsActivity.onDestroy now calls cancel().

Reviewed in codereview.md (recommendation 3.2).
2026-07-26 17:02:06 +02:00
eddy
14118f4c05 Merge branch 'fix/replace-deprecated-progressdialog': replace ProgressDialog with AlertDialog 2026-07-26 16:59:41 +02:00
eddy
aa13359125 Replace deprecated ProgressDialog with AlertDialog + custom layouts
Use AlertDialog with dialog_waiting.xml (spinner + message) and
dialog_progress.xml (horizontal ProgressBar + message) instead of
the deprecated ProgressDialog. Progress updates now use findViewById
on the dialog view.

Reviewed in codereview.md (recommendation 2.4).
2026-07-26 16:57:07 +02:00
eddy
d2faaf8fbc Merge branch 'fix/handler-postdelayed-cancel': cancel pending BLE scans on pause 2026-07-26 16:53:22 +02:00
eddy
ce0c119416 Use named Runnable for scheduled BLE scans and cancel on pause
Replace anonymous lambdas in handler.postDelayed with a shared
scanRunnable so that handler.removeCallbacks(scanRunnable) in onPause
cancels any pending scan. Prevents duplicate or orphaned scan starts
during rapid activity transitions.

Reviewed in codereview.md (recommendation 2.2).
2026-07-26 16:51:53 +02:00
eddy
5861b1055c Merge branch 'fix/ble-permission-handling': handle denied BLE permissions 2026-07-26 16:48:21 +02:00
eddy
ff091169f6 Handle denied BLE permissions with user feedback
When BLUETOOTH_SCAN, BLUETOOTH_CONNECT or ACCESS_FINE_LOCATION
permissions are denied, show 'Permissies geweigerd — open Instellingen'
and disable the relay button instead of silently hanging.

Reviewed in codereview.md (recommendation 2.1).
2026-07-26 16:46:15 +02:00
eddy
3802b5b07d Merge branch 'fix/gatt-status-logging': add GATT status code logging 2026-07-26 16:44:57 +02:00
eddy
c62cbaa1c2 Add GATT status code logging in callbacks
Log warning when status != GATT_SUCCESS in onConnectionStateChange,
onServicesDiscovered, and onMtuChanged. Service discovery failure now
shows 'Servicefout' and returns early to prevent MTU request on failed
discovery.

Reviewed in codereview.md (recommendation 5.2).
2026-07-26 16:44:21 +02:00
eddy
fd4ba1a922 Merge branch 'fix/upgrade-kotlin': upgrade Kotlin 1.9.24 → 2.0.21 2026-07-26 16:42:49 +02:00
eddy
36856dabc6 Upgrade Kotlin from 1.9.24 to 2.0.21
Kotlin 2.0 uses the K2 compiler for better build performance.
Compatible with AGP 8.5.2 which was upgraded previously.

Reviewed in codereview.md (recommendation 7.3).
2026-07-26 16:40:21 +02:00
eddy
8eb91786d7 Merge branch 'fix/upgrade-agp': upgrade AGP 8.2.2 → 8.5.2 2026-07-26 16:37:36 +02:00
eddy
e712020a64 Upgrade AGP from 8.2.2 to 8.5.2
AGP 8.5.2 is compatible with Gradle 8.14 and Kotlin 1.9.24.
Includes bug fixes and improved Kotlin support.

Reviewed in codereview.md (recommendation 7.1).
2026-07-26 16:34:34 +02:00
eddy
a82f595f5d Merge branch 'fix/on-characteristic-write-callback': add BLE write feedback 2026-07-26 16:32:06 +02:00
eddy
9b7adabc59 Add onCharacteristicWrite callback for BLE write feedback
Implement onCharacteristicWrite in gattCallback to get confirmation
that packets are submitted to the local BLE stack. Logs success/failure
and shows 'Communicatiefout' on write failure. seq++ stays in
sendRelayCommand to ensure unique sequence numbers for rapid ON/OFF.

Reviewed in codereview.md (recommendation #5).
2026-07-26 16:31:07 +02:00
eddy
596a9d54d4 Merge branch 'fix/line-coding-parity-byte': fix CDC parity byte 2026-07-26 16:25:10 +02:00
eddy
68b15ce7f1 Fix CDC line coding parity byte
Change parity from 0x08 (SPACE) to 0x00 (NONE) to match 115200 8N1
specification. ESP32-S3 ignores this setting but the value was
technically incorrect.

Reviewed in codereview.md (recommendation #4).
2026-07-26 16:23:55 +02:00
eddy
ea0718218c Merge branch 'fix/remove-usb-key-receiver-duplication': remove dead code 2026-07-26 16:21:59 +02:00
eddy
3616f7162f Remove dead UsbKeyReceiver code
UsbKeyReceiver was never registered in AndroidManifest or dynamically,
making it 318 lines of dead code duplicating SettingsActivity key-exchange
logic and UsbUtils USB helpers.

Reviewed in codereview.md (recommendation #3).
2026-07-26 16:19:14 +02:00
eddy
3b5831364d Merge branch 'fix/encrypted-shared-preferences': encrypt HMAC key at rest 2026-07-26 16:09:12 +02:00
eddy
b5d42d53ba Use EncryptedSharedPreferences for HMAC key storage
Replace plain-text SharedPreferences with AES256-GCM encrypted prefs
via androidx.security:security-crypto. Adds PrefsHelper utility to
centralize encrypted prefs access across MainActivity, SettingsActivity
and UsbKeyReceiver.

Reviewed in codereview.md (recommendation #2).
2026-07-26 16:09:10 +02:00
eddy
4a70f94a51 Merge branch 'fix/remove-key-logging': remove HMAC key logging from logcat 2026-07-26 16:04:24 +02:00
eddy
eb337d2157 Remove HMAC key logging from logcat
Replace full key values in Log.d() calls with length-only metadata
to prevent key material leakage via logcat access.

Reviewed in codereview.md (recommendation #1).
2026-07-26 16:03:06 +02:00
eddy
81abf2d824 Replace BLE encryption with HMAC-only auth, add buildnr, fix post-flash flow
BLE bonding/encryption removed — HMAC-SHA256 on application level provides
authentication + replay protection. Eliminates bonding state bugs, write
queue issues, and GATT_INSUFFICIENT_AUTHENTICATION complexity.

Firmware:
- Remove BLESecurity, setRespEncryptionKey, ESP_GATT_PERM_WRITE_ENCRYPTED
- Characteristic permission: ESP_GATT_PERM_WRITE (no encryption required)
- Characteristic property: PROPERTY_WRITE_NR (Write Without Response)
  Enables direct ON/OFF sequences without ACK wait time

Android:
- Remove bondReceiver, createBond, bonding state handling
- Remove write queue / pendingRelayState / writeInProgress
- Use WRITE_TYPE_NO_RESPONSE for minimal latency
- Post-flash dialog auto-dismisses on USB detach (currentAction = NONE)
- Separate progressDialog (flash progress) from dialog (post-flash info)
- Detach during active action now calls cleanupAction()

Build number:
- BuildConfig.BUILD_NUMBER in build.gradle as yyyyMMddHHmmss timestamp
- buildFeatures.buildConfig enabled
- Displayed in SettingsActivity bottom (12sp, secondary color)

Gradle:
- Wrapper upgraded 8.5 → 8.14
- minSdk 24 → 26 (Android 8+)

README updated to reflect HMAC-only security model.
2026-07-26 15:46:28 +02:00
eddy
0ff2c59b6d Add esp32-flash-lib dependency to build.gradle (was missing from git) 2026-07-26 14:21:45 +02:00
eddy
06a99793a2 Revert UsbSerialTransport.read() to return 0 on timeout (library expects this) v2.0 2026-07-26 13:33:17 +02:00
eddy
8ef971c051 Add code review fixes implementation plan 2026-07-26 12:54:11 +02:00
eddy
21aa62c2d7 Disable allowBackup, fix download-modus instruction text 2026-07-26 12:53:49 +02:00
eddy
4954c91d77 Fix MainActivity: receiver flag, handler cleanup, onPause, boolean state 2026-07-26 12:53:18 +02:00
eddy
475d4d9162 Fix SettingsActivity: use UsbUtils, add lifecycle guard 2026-07-26 12:52:01 +02:00
eddy
2a86fa5d39 Fix EspFlasher: connection leak (try/finally), progress mapping, use UsbUtils 2026-07-26 12:51:02 +02:00
eddy
2e08f9a340 Fix UsbSerialTransport.read() to throw IOException on error 2026-07-26 12:50:22 +02:00
eddy
5fbe0712e8 Add UsbUtils utility object for shared USB functions 2026-07-26 12:49:37 +02:00
eddy
ff419d9f36 Update README for USB key provisioning architecture
- Unified toast message to 'Beveiligingssleutel gelezen' throughout
- Updated key management section with bidirectional provisioning flow
- Added provisioning flow diagram (ASCII sequence)
- Updated security analysis: key no longer in APK, extracted from
  SharedPreferences requiring root; added USB key extraction risk
  and NVS wiping notes
- Added serial monitor example for unprovisioned ESP (no-key case)
- Added factory reset instructions (pio run -t erase)
- Added setup scenarios: existing ESP + new phone, new ESP + existing phone
- Updated projectstructuur: removed stale usb-serial-for-android references
- Updated technische notities: raw USB API justification, CDC control
  interface setup, retry behavior
2026-07-26 09:40:38 +02:00
eddy
8e53abe8b4 Unify provisioning toast message
Both provisioning paths (reading existing key and generating new key)
now show the same neutral toast: 'Beveiligingssleutel gelezen'
2026-07-26 09:38:19 +02:00
eddy
9a84b05b8a Replace compile-time HMAC key with USB provisioning
ESP firmware:
- Store HMAC key in NVS via Preferences library instead of compile-time
  hmac_key.h
- KEY? responds with HMAC_KEY:<hex> (if provisioned) or NO_KEY
- New SET_KEY:<64-hex> command stores key in NVS and responds OK
- Boot messages updated to reflect provisioning state

Android app:
- UsbKeyReceiver detects NO_KEY response and auto-provisions:
  generates random 32-byte key via SecureRandom, sends SET_KEY,
  waits for OK, saves key to SharedPreferences
- Read loop now breaks on both HMAC_KEY: and NO_KEY responses

Cleanup:
- Removed tools/generate_hmac_key.py and src/hmac_key.h
- Removed hmac_key.h from .gitignore
- README updated for new provisioning flow
2026-07-26 09:30:21 +02:00
eddy
c707c21d7f Rewrite USB key reader: raw USB API + retry logic
- Replaced usb-serial-for-android library with raw Android USB API
  (UsbDeviceConnection.bulkTransfer) — the library's testConnection()
  sent a get_status CDC request that the ESP32-S3 USB-JTAG/serial
  controller does not support, causing read failures
- Added CDC control interface setup (line coding + DTR/RTS) to enable
  data flow before sending KEY? command
- Added retry logic: 5 attempts, 1s delay between retries, to handle
  other apps grabbing the USB device first (JTAG/serial debug unit
  triggers multiple system dialogs before our app gets permission)
- Re-lookup ESP device on each retry to handle device path changes
- Removed usb-serial-for-android dependency and JitPack repository
2026-07-26 09:02:41 +02:00
eddy
4518aec34e Add USB auto-read key transfer + UI redesign
USB auto-read:
- ESP responds to 'KEY?\n' over USB serial with 'HMAC_KEY:<hex>'
- Android UsbKeyReceiver (manifest BroadcastReceiver) catches USB_DEVICE_ATTACHED
- Filters on Espressif vendor 0x303a, requests USB permission
- Opens CDC-ACM serial (115200), sends KEY?, parses response, saves to SharedPreferences
- Works even when app not in foreground
- usb-serial-for-android v3.7.3 via JitPack

UI redesign:
- activity_main.xml: FrameLayout with settings icon top-right, relay button center, status bottom-left (14sp)
- Scan & Connect button moved to SettingsActivity
- SettingsActivity: hint text 'Voer de HMAC sleutel in of sluit de relaismodule aan op de telefoon'
- SettingsActivity: scan button saves key + returns RESULT_OK to trigger scanning
- MainActivity: removed scanButton, auto-scan on onResume when key is set
- Back button removed from settings (ActionBar up navigation only)

README updated with USB protocol docs, new UI description, and updated setup guide
2026-07-26 08:39:28 +02:00
eddy
7f68e9ad70 Add BLE security: Just Works pairing + HMAC-SHA256 authentication
Two-layer security for the BLE relay:

1. BLE Just Works pairing (link-layer encryption)
   - ESP32 requires bonding before accepting writes
   - Characteristic permissions set to ESP_GATT_PERM_WRITE_ENCRYPTED
   - IO capability NO_INPUT_NO_OUTPUT (no passkey needed)
   - LE Secure Connections with bonding (ESP_LE_AUTH_REQ_SC_BOND)

2. HMAC-SHA256 authentication (application-level)
   - 32-byte shared secret in src/hmac_key.h (generated, gitignored)
   - Each command signed: HMAC-SHA256(key, command || sequence)
   - 37-byte packet format: [cmd:1][seq:4][hmac:32]
   - Sequence counter prevents replay (strictly increasing, starts at 1)
   - mbedtls on ESP, javax.crypto.Mac on Android

Key management:
- tools/generate_hmac_key.py generates random key + writes src/hmac_key.h
- ESP prints hex key to serial 5 seconds after boot
- Android app: SettingsActivity with eye-toggle password field
- Key stored in SharedPreferences, supports multiple ESP modules

Other changes:
- Android app: bonding flow (createBond -> BOND_BONDED -> discover)
- Android app: MTU negotiation (request 256)
- Android app: settings button (gear icon) in main screen
- Fix: sequence counter starts at 1 to prevent first-packet rejection
- Updated README with full security documentation
2026-07-26 08:18:52 +02:00