1package com.peachpasswords.sync23import kotlinx.coroutines.CancellationException4import org.json.JSONObject5678910111213141516171819internal suspend fun runCloudRelayInitiatorSession(20 socket: RelayBinarySocket,21 known: PairedDevice,22 rendezvousId: String,23 ourStaticPrivateKey: ByteArray,24 ourStaticPublicKey: ByteArray,25 localIdentity: LocalDeviceIdentity,26 vaultFactory: () -> SyncVaultAdapter?,27 onSyncComplete: () -> Unit,28 report: (SyncOutcomeSummary) -> Unit = {},29) {30 PeachSyncLog.i(31 "cloud-relay: dial — peerType=${relayLogDetail(known.deviceType ?: "unset")}, " +32 "trust=${relayLogDetail(known.trustSource ?: "unset")}",33 )34 try {35 val responderKeyHex = known.longTermPublicKeyHex36 ?: throw IllegalArgumentException("known peer missing static key")37 val responderKey = hexToPubkey(responderKeyHex)3839 40 41 socket.send(42 JSONObject()43 .put("type", "PEACH_RECONNECT")44 .put("rendezvous_id", rendezvousId)45 .toString()46 .toByteArray(Charsets.UTF_8),47 )48 val session = noiseKKInitiator(49 ourStaticPrivateKey,50 ourStaticPublicKey,51 responderKey,52 socket::send,53 socket::receive,54 )55 56 57 58 try {59 sendSyncFrame(60 encodePairIdentityFrame(localIdentity),61 session::encrypt,62 socket::send,63 )64 val theirIdentity = parsePairIdentityFrame(65 receiveSyncFrame(socket::receive, session::decrypt),66 )67 require(identityMatchesAuthenticatedPeer(known, theirIdentity, responderKey)) {68 "peer identity does not match authenticated KK key"69 }7071 val vault = vaultFactory()72 if (vault == null) {73 report(SyncOutcomeSummary(reason = SyncOutcomeReason.VAULT_LOCKED))74 } else {75 vault.bindAuthenticatedPeer(known)76 val outcome = runSyncInitiator(77 SyncSession(78 remoteStaticPublicKey = responderKey.copyOf(),79 send = { plaintext -> sendSyncFrame(plaintext, session::encrypt, socket::send) },80 receive = { receiveSyncFrame(socket::receive, session::decrypt) },81 close = { socket.close() },82 ),83 vault,84 )85 if (outcome.status == SyncOutcomeStatus.COMPLETED) {86 onSyncComplete()87 }88 report(outcome.toSummary())89 }90 } catch (e: CancellationException) {91 throw e92 } catch (e: Exception) {93 94 95 report(SyncOutcomeSummary(reason = classifySyncError(e.message ?: e.toString()), detail = e.message))96 throw e97 } finally {98 session.close()99 }100 PeachSyncLog.i("cloud-relay: dial ok")101 } catch (error: CancellationException) {102 logCloudRelayDialFailure(error)103 throw error104 } catch (error: Exception) {105 logCloudRelayDialFailure(error)106 throw error107 }108}109110private fun logCloudRelayDialFailure(error: Throwable) {111 val reason = relayLogReason(error)112 val outcome = classifySyncError(reason)113 val message = "cloud-relay: dial failed — reason=$reason"114 when {115 error is CancellationException -> PeachSyncLog.w(message)116 outcome == SyncOutcomeReason.RELAY_UNREACHABLE || outcome == SyncOutcomeReason.SYNC_TIMEOUT ->117 PeachSyncLog.w(message)118 else -> PeachSyncLog.e(message)119 }120}