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