@@ -59,6 +59,7 @@ class BleGattServer(private val context: Context) {
5959 private val connectedDevices = mutableSetOf<BluetoothDevice >()
6060 private val characteristicQueues = mutableMapOf<UUID , ConcurrentLinkedQueue <ByteArray >>()
6161 private val isSending = mutableMapOf<UUID , Boolean >()
62+ private val preparedWrites = java.util.concurrent.ConcurrentHashMap <String , java.io.ByteArrayOutputStream >()
6263
6364 var isAuthenticated = false
6465 private set
@@ -280,7 +281,6 @@ class BleGattServer(private val context: Context) {
280281 fun resumeAdvertising () {
281282 if (! isAdvertisingPaused) return
282283 if (gattServer == null ) return
283- if (_connectionState .value == BleConnectionState .DISCONNECTED ) return
284284 Log .d(TAG , " BLE advertising resumed" )
285285 isAdvertisingPaused = false
286286 startAdvertising()
@@ -308,6 +308,7 @@ class BleGattServer(private val context: Context) {
308308 Log .d(TAG , " Device connected: ${device.address} " )
309309 connectedDevices.add(device)
310310 _connectionState .value = BleConnectionState .CONNECTED
311+ stopAdvertising()
311312 } else if (newState == BluetoothProfile .STATE_DISCONNECTED ) {
312313 Log .d(TAG , " Device disconnected: ${device.address} " )
313314 connectedDevices.remove(device)
@@ -376,8 +377,7 @@ class BleGattServer(private val context: Context) {
376377 offset : Int ,
377378 value : ByteArray
378379 ) {
379- Log .d(TAG , " Write request for ${characteristic.uuid} , length: ${value.size} " )
380-
380+ Log .d(TAG , " Write request for characteristic=${characteristic.uuid} , fromDevice=${device.address} , requestId=$requestId , preparedWrite=$preparedWrite , responseNeeded=$responseNeeded , offset=$offset , valueLength=${value.size} " )
381381 if (characteristic.uuid != BleConstants .CHAR_AUTH_TOKEN && ! isAuthenticated) {
382382 Log .w(
383383 TAG ,
@@ -395,6 +395,15 @@ class BleGattServer(private val context: Context) {
395395 return
396396 }
397397
398+ if (preparedWrite) {
399+ val key = " ${device.address} _${characteristic.uuid} "
400+ val bos = preparedWrites.getOrPut(key) { java.io.ByteArrayOutputStream () }
401+ bos.write(value)
402+ if (responseNeeded) {
403+ gattServer?.sendResponse(device, requestId, BluetoothGatt .GATT_SUCCESS , offset, value)
404+ }
405+ return
406+ }
398407 when (characteristic.uuid) {
399408 BleConstants .CHAR_AUTH_TOKEN -> handleAuthRequest(device, value)
400409 BleConstants .CHAR_MAC_BATTERY -> handleMacBattery(value)
@@ -504,25 +513,75 @@ class BleGattServer(private val context: Context) {
504513 // This is crucial for sequential chunk sending
505514 processNextInQueues()
506515 }
516+
517+ override fun onExecuteWrite (device : BluetoothDevice , requestId : Int , execute : Boolean ) {
518+ Log .d(TAG , " onExecuteWrite: device=${device.address} , requestId=$requestId , execute=$execute " )
519+ if (execute) {
520+ val keys = preparedWrites.keys().toList()
521+ for (key in keys) {
522+ if (key.startsWith(device.address)) {
523+ val bos = preparedWrites.remove(key) ? : continue
524+ val value = bos.toByteArray()
525+ val uuidStr = key.substring(device.address.length + 1 )
526+ val uuid = UUID .fromString(uuidStr)
527+ val characteristic = findCharacteristic(uuid)
528+ if (characteristic != null ) {
529+ Log .d(TAG , " Executing prepared write for characteristic=$uuid , valueLength=${value.size} " )
530+ scope.launch {
531+ when (uuid) {
532+ BleConstants .CHAR_AUTH_TOKEN -> handleAuthRequest(device, value)
533+ BleConstants .CHAR_MAC_BATTERY -> handleMacBattery(value)
534+ BleConstants .CHAR_NOTIFICATION_ACTION -> handleChunkedWrite(uuid, value) { handleNotificationAction(it.toByteArray(Charsets .UTF_8 )) }
535+ BleConstants .CHAR_MEDIA_CONTROL -> handleChunkedWrite(uuid, value) { handleMediaControl(it.toByteArray(Charsets .UTF_8 )) }
536+ BleConstants .CHAR_MAC_MEDIA_STATE -> handleChunkedWrite(uuid, value) { handleMacMediaState(it) }
537+ BleConstants .CHAR_CLIPBOARD_DATA_WRITE -> handleChunkedWrite(uuid, value) {
538+ Log .d(TAG , " Received clipboard from Mac via BLE: ${it.take(50 )} " )
539+ ClipboardSyncManager .handleClipboardUpdate(context, it)
540+ }
541+ BleConstants .CHAR_DEVICE_NAME -> handleChunkedWrite(uuid, value) {
542+ Log .d(TAG , " Received Mac Device Name: $it " )
543+ MacDeviceStatusManager .updateMacStatus(context, name = it)
544+ }
545+ BleConstants .CHAR_NOTIFICATION_DISMISS -> handleChunkedWrite(uuid, value) { handleNotificationDismiss(it) }
546+ }
547+ }
548+ }
549+ }
550+ }
551+ } else {
552+ val keys = preparedWrites.keys().toList()
553+ for (key in keys) {
554+ if (key.startsWith(device.address)) {
555+ preparedWrites.remove(key)
556+ }
557+ }
558+ }
559+ gattServer?.sendResponse(device, requestId, BluetoothGatt .GATT_SUCCESS , 0 , null )
560+ }
507561 }
508562
509563 private fun handleAuthRequest (device : BluetoothDevice , token : ByteArray ) {
510564 scope.launch {
511565 val deviceData = dataStoreManager.getLastConnectedDevice().first()
512566 val storedKey = deviceData?.symmetricKey
513- Log .d(
514- TAG ,
515- " Handling auth request from ${device.address} . Device in DB: ${deviceData?.name} , hasKey: ${storedKey != null } "
516- )
517-
567+ Log .d(TAG , " Handling auth request from ${device.address} . DB device details: name=${deviceData?.name} , ip=${deviceData?.ipAddress} , port=${deviceData?.port} , storedKeyExists=${storedKey != null } " )
518568 if (storedKey != null ) {
569+ Log .d(TAG , " Stored symmetric key found: $storedKey " )
519570 val expectedToken = BleTransportBridge .deriveAuthToken(storedKey)
520571 val receivedTokenStr = String (token, Charsets .UTF_8 )
521-
522- Log .d(TAG , " Expected token: $expectedToken " )
523- Log .d(TAG , " Received token: $receivedTokenStr " )
524-
525- if (token.contentEquals(expectedToken.toByteArray(Charsets .UTF_8 ))) {
572+ val expectedTokenBytes = expectedToken.toByteArray(Charsets .UTF_8 )
573+ val expectedTokenBase64 = android.util.Base64 .encodeToString(expectedTokenBytes, android.util.Base64 .NO_WRAP )
574+ val receivedTokenBase64 = android.util.Base64 .encodeToString(token, android.util.Base64 .NO_WRAP )
575+
576+ Log .d(TAG , " Expected token string: '$expectedToken '" )
577+ Log .d(TAG , " Expected token base64: $expectedTokenBase64 " )
578+ Log .d(TAG , " Received token string: '$receivedTokenStr '" )
579+ Log .d(TAG , " Received token base64: $receivedTokenBase64 " )
580+
581+ val isMatch = token.contentEquals(expectedTokenBytes)
582+ Log .d(TAG , " Performing byte-by-byte authentication token comparison. Match result: $isMatch " )
583+
584+ if (isMatch) {
526585 Log .i(TAG , " BLE Auth Success!" )
527586 isAuthenticated = true
528587 _connectionState .value = BleConnectionState .AUTHENTICATED
@@ -533,11 +592,8 @@ class BleGattServer(private val context: Context) {
533592 BleTransportBridge .sendDeviceName()
534593 startHeartbeat()
535594 } else {
536- Log .w(TAG , " BLE Auth Failed! Token mismatch." )
537- sendNotification(
538- BleConstants .CHAR_AUTH_RESULT ,
539- byteArrayOf(BleConstants .AUTH_FAILED )
540- )
595+ Log .w(TAG , " BLE Auth Failed! Token mismatch (byte-level mismatch)." )
596+ sendNotification(BleConstants .CHAR_AUTH_RESULT , byteArrayOf(BleConstants .AUTH_FAILED ))
541597 }
542598 } else {
543599 Log .w(TAG , " BLE Auth Failed! No symmetric key found for last connected device." )
0 commit comments