@@ -16,7 +16,7 @@ import com.ismartcoding.plain.db.AppDatabase
1616import com.ismartcoding.plain.db.DPeer
1717import com.ismartcoding.plain.enums.DeviceType
1818import com.ismartcoding.plain.enums.NearbyMessageType
19- import com.ismartcoding.plain.events.PairingCancelledEvent
19+ import com.ismartcoding.plain.events.PairingCanceledEvent
2020import com.ismartcoding.plain.events.PairingFailedEvent
2121import com.ismartcoding.plain.events.PairingSuccessEvent
2222import com.ismartcoding.plain.helpers.PhoneHelper
@@ -25,11 +25,14 @@ import com.ismartcoding.plain.helpers.TimeHelper
2525import com.ismartcoding.plain.chat.ChatCacheManager
2626import java.util.concurrent.ConcurrentHashMap
2727import android.util.Base64
28+ import com.ismartcoding.plain.data.DPairingResult
29+ import com.ismartcoding.plain.events.EventType
30+ import com.ismartcoding.plain.events.WebSocketEvent
2831import kotlin.math.abs
2932
3033object NearbyPairManager {
3134 private val activePairingSessions = ConcurrentHashMap <String , DPairingSession >()
32-
35+
3336 // Maximum allowed time difference for timestamp validation (5 minutes)
3437 private const val MAX_TIMESTAMP_DIFF_MS = 5 * 60 * 1000L
3538
@@ -64,7 +67,7 @@ object NearbyPairManager {
6467
6568 // Generate ECDH key pair for this pairing session
6669 val keyPair = CryptoHelper .generateECDHKeyPair()
67-
70+
6871 // Get our raw Ed25519 signature public key (32 bytes)
6972 val signaturePublicKey = SignatureHelper .getRawPublicKeyBase64Async()
7073 // Create pairing session
@@ -80,7 +83,7 @@ object NearbyPairManager {
8083 val currentTimestamp = System .currentTimeMillis()
8184 val ecdhPublicKeyBytes = CryptoHelper .getPublicKeyBytes(keyPair)
8285 val ecdhPublicKey = Base64 .encodeToString(ecdhPublicKeyBytes, Base64 .NO_WRAP )
83-
86+
8487 val request = DPairingRequest (
8588 fromId = TempData .clientId,
8689 fromName = deviceName,
@@ -96,48 +99,54 @@ object NearbyPairManager {
9699 sendPairingMessage(NearbyMessageType .PAIR_REQUEST , JsonHelper .jsonEncode(request), bestIp)
97100 } catch (e: Exception ) {
98101 LogCat .e(" Error starting pairing: ${e.message} " )
99- sendEvent(PairingFailedEvent (device.id, " Failed to send pairing request" ))
102+ val event = PairingFailedEvent (device.id, " Failed to send pairing request" )
103+ sendEvent(event)
104+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
100105 }
101106 }
102107
103- suspend fun respondToPairing (request : DPairingRequest , fromIp : String , accepted : Boolean ) {
108+ suspend fun respondToPairing (request : DPairingRequest , accepted : Boolean ) {
104109 try {
105110 // Verify timestamp to prevent replay attacks
106111 val currentTime = System .currentTimeMillis()
107112 if (abs(currentTime - request.timestamp) > MAX_TIMESTAMP_DIFF_MS ) {
108113 LogCat .e(" Pairing request timestamp is too old or in the future" )
109- sendEvent(PairingFailedEvent (request.fromId, " Invalid timestamp" ))
114+ val event = PairingFailedEvent (request.fromId, " Invalid timestamp" )
115+ sendEvent(event)
116+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
110117 return
111118 }
112-
119+
113120 // Verify signature
114121 if (! verifyPairingRequestSignature(request)) {
115122 LogCat .e(" Pairing request signature verification failed" )
116- sendEvent(PairingFailedEvent (request.fromId, " Signature verification failed" ))
123+ val event = PairingFailedEvent (request.fromId, " Signature verification failed" )
124+ sendEvent(event)
125+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
117126 return
118127 }
119-
128+
120129 LogCat .d(" Pairing request signature verified successfully" )
121-
130+
122131 if (accepted) {
123132 // Generate ECDH key pair for this pairing session
124133 val keyPair = CryptoHelper .generateECDHKeyPair()
125-
134+
126135 // Get our raw Ed25519 signature public key (32 bytes)
127136 val signaturePublicKey = SignatureHelper .getRawPublicKeyBase64Async()
128137 // Create pairing session
129138 val session = DPairingSession (
130139 deviceId = request.fromId,
131140 deviceName = request.fromName,
132- deviceIp = fromIp,
141+ deviceIp = request. fromIp,
133142 keyPair = keyPair,
134143 )
135144 activePairingSessions[request.fromId] = session
136-
145+
137146 val responseTimestamp = System .currentTimeMillis()
138147 val ecdhPublicKeyBytes = CryptoHelper .getPublicKeyBytes(keyPair)
139148 val ecdhPublicKey = Base64 .encodeToString(ecdhPublicKeyBytes, Base64 .NO_WRAP )
140-
149+
141150 val response = DPairingResponse (
142151 fromId = TempData .clientId,
143152 toId = request.fromId,
@@ -156,26 +165,27 @@ object NearbyPairManager {
156165 val encryptKey = CryptoHelper .computeECDHSharedKey(keyPair.private, requestEcdhPublicKey)
157166 if (encryptKey != null ) {
158167 // Store peer in database with signature public key
159- val peerIps = (listOf (fromIp) + request.ips).distinct()
168+ val peerIps = (listOf (request. fromIp) + request.ips).distinct()
160169 storePeerInDatabase(
161- request.fromId,
162- request.fromName,
163- peerIps,
164- request.port,
165- request.deviceType,
170+ request.fromId,
171+ request.fromName,
172+ peerIps,
173+ request.port,
174+ request.deviceType,
166175 encryptKey,
167176 request.signaturePublicKey
168177 )
169- sendPairingMessage(NearbyMessageType .PAIR_RESPONSE , JsonHelper .jsonEncode(response), fromIp)
170- sendEvent(PairingSuccessEvent (request.fromId, request.fromName, fromIp, encryptKey))
178+ sendPairingMessage(NearbyMessageType .PAIR_RESPONSE , JsonHelper .jsonEncode(response), request.fromIp)
179+ sendEvent(PairingSuccessEvent (request.fromId, request.fromName, request.fromIp, encryptKey))
180+ sendEvent(WebSocketEvent (EventType .PAIRING_SUCCESS , JsonHelper .jsonEncode(DPairingResult (response.fromId))))
171181 } else {
172182 throw Exception (" Failed to compute shared key" )
173183 }
174184 } else {
175185 // Send rejection response with signature for security
176186 val signaturePublicKey = SignatureHelper .getRawPublicKeyBase64Async()
177187 val rejectionTimestamp = System .currentTimeMillis()
178-
188+
179189 val response = DPairingResponse (
180190 fromId = TempData .clientId,
181191 toId = request.fromId,
@@ -186,15 +196,17 @@ object NearbyPairManager {
186196 accepted = false ,
187197 timestamp = rejectionTimestamp,
188198 )
189-
199+
190200 response.signature = SignatureHelper .signTextAsync(response.toSignatureData())
191201
192- sendPairingMessage(NearbyMessageType .PAIR_RESPONSE , JsonHelper .jsonEncode(response), fromIp)
202+ sendPairingMessage(NearbyMessageType .PAIR_RESPONSE , JsonHelper .jsonEncode(response), request. fromIp)
193203 LogCat .d(" Signed pairing rejection response sent to ${request.fromName} " )
194204 }
195205 } catch (e: Exception ) {
196206 LogCat .e(" Error responding to pairing: ${e.message} " )
197- sendEvent(PairingFailedEvent (request.fromId, " Failed to respond to pairing request" ))
207+ val event = PairingFailedEvent (request.fromId, " Failed to respond to pairing request" )
208+ sendEvent(event)
209+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
198210 } finally {
199211 // Clean up session if not accepted
200212 if (! accepted) {
@@ -224,7 +236,9 @@ object NearbyPairManager {
224236 }
225237
226238 activePairingSessions.remove(deviceId)
227- sendEvent(PairingFailedEvent (deviceId, " Pairing cancelled by user" ))
239+ val event = PairingFailedEvent (deviceId, " Pairing cancelled by user" )
240+ sendEvent(event)
241+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
228242 LogCat .d(" Pairing cancelled for device: $deviceId " )
229243 }
230244
@@ -241,17 +255,21 @@ object NearbyPairManager {
241255 val currentTime = System .currentTimeMillis()
242256 if (abs(currentTime - response.timestamp) > MAX_TIMESTAMP_DIFF_MS ) {
243257 LogCat .e(" Pairing response timestamp is too old or in the future" )
244- sendEvent(PairingFailedEvent (response.fromId, " Invalid timestamp" ))
258+ val event = PairingFailedEvent (response.fromId, " Invalid timestamp" )
259+ sendEvent(event)
260+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
245261 return
246262 }
247-
263+
248264 // Verify signature for all responses (acceptance and rejection)
249265 if (! verifyPairingResponseSignature(response)) {
250266 LogCat .e(" Pairing response signature verification failed" )
251- sendEvent(PairingFailedEvent (response.fromId, " Signature verification failed" ))
267+ val event = PairingFailedEvent (response.fromId, " Signature verification failed" )
268+ sendEvent(event)
269+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
252270 return
253271 }
254-
272+
255273 LogCat .d(" Pairing response signature verified successfully" )
256274
257275 if (response.accepted) {
@@ -261,26 +279,31 @@ object NearbyPairManager {
261279 // Store peer in database with signature public key
262280 val peerIps = (listOf (senderIP) + response.ips).distinct()
263281 storePeerInDatabase(
264- response.fromId,
265- session.deviceName,
266- peerIps,
267- response.port,
268- response.deviceType,
282+ response.fromId,
283+ session.deviceName,
284+ peerIps,
285+ response.port,
286+ response.deviceType,
269287 encryptKey,
270288 response.signaturePublicKey
271289 )
272290 sendEvent(PairingSuccessEvent (response.fromId, session.deviceName, senderIP, encryptKey))
291+ sendEvent(WebSocketEvent (EventType .PAIRING_SUCCESS , JsonHelper .jsonEncode(DPairingResult (response.fromId))))
273292 LogCat .d(" Pairing completed successfully with ${session.deviceName} " )
274293 } else {
275294 throw Exception (" Failed to compute shared key" )
276295 }
277296 } else {
278- sendEvent(PairingFailedEvent (response.fromId, " Pairing request was rejected" ))
297+ val event = PairingFailedEvent (response.fromId, " Pairing request was rejected" )
298+ sendEvent(event)
299+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
279300 LogCat .d(" Verified pairing rejection from ${session.deviceName} " )
280301 }
281302 } catch (e: Exception ) {
282303 LogCat .e(" Error processing pairing response: ${e.message} " )
283- sendEvent(PairingFailedEvent (response.fromId, " Failed to process pairing response" ))
304+ val event = PairingFailedEvent (response.fromId, " Failed to process pairing response" )
305+ sendEvent(event)
306+ sendEvent(WebSocketEvent (EventType .PAIRING_FAILED , JsonHelper .jsonEncode(DPairingResult (event.deviceId, event.reason))))
284307 }
285308
286309 // Clean up session
@@ -289,8 +312,8 @@ object NearbyPairManager {
289312
290313 fun handlePairingCancel (cancel : DPairingCancel ) {
291314 LogCat .d(" Pairing cancelled by remote device: ${cancel.fromId} " )
292- sendEvent(PairingCancelledEvent (cancel.fromId))
293-
315+ sendEvent(PairingCanceledEvent (cancel.fromId))
316+ sendEvent( WebSocketEvent ( EventType . PAIRING_CANCELED , JsonHelper .jsonEncode( DPairingResult (cancel.fromId))))
294317 // Clean up session if exists
295318 activePairingSessions.remove(cancel.fromId)
296319 }
0 commit comments