Skip to content

Commit

Permalink
✨ Implementing signal protocol for desktop device
Browse files Browse the repository at this point in the history
  • Loading branch information
guiyanakuang committed Feb 1, 2024
1 parent 6077187 commit 93f9562
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import io.ktor.http.URLBuilder
interface ClipClient {

suspend fun post(
urlBuilder: URLBuilder.(URLBuilder) -> Unit,
message: ByteArray,
timeout: Long = 1000L,
urlBuilder: URLBuilder.(URLBuilder) -> Unit
): HttpResponse

suspend fun get(
urlBuilder: URLBuilder.(URLBuilder) -> Unit
timeout: Long = 1000L,
urlBuilder: URLBuilder.(URLBuilder) -> Unit,
): HttpResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.clipevery.ui.DesktopThemeDetector
import com.clipevery.ui.ThemeDetector
import com.clipevery.utils.DesktopQRCodeGenerator
import com.clipevery.utils.QRCodeGenerator
import com.clipevery.utils.TelnetUtils
import org.koin.core.KoinApplication
import org.koin.core.context.GlobalContext
import org.koin.dsl.module
Expand Down Expand Up @@ -79,6 +80,7 @@ object Dependencies {
single<Lazy<ClipServer>> { lazy { get<ClipServer>() } }
single<ClipBonjourService> { DesktopClipBonjourService(get(), get()).registerService() }
single<DeviceRefresher> { DesktopDeviceRefresher(get<ClientHandlerManager>()) }
single<TelnetUtils> { TelnetUtils(get<ClipClient>()) }

// signal component
single<IdentityKeyStore> { getClipIdentityKeyStoreFactory(get(), get()).createIdentityKeyStore() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.clipevery.net
import com.clipevery.app.AppInfo
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.timeout
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
Expand All @@ -16,20 +17,28 @@ class DesktopClipClient(private val appInfo: AppInfo): ClipClient {

@OptIn(InternalAPI::class)
override suspend fun post(
urlBuilder: URLBuilder.(URLBuilder) -> Unit,
message: ByteArray,
timeout: Long,
urlBuilder: URLBuilder.(URLBuilder) -> Unit
): HttpResponse {
return client.post {
header("appInstanceId", appInfo.appInstanceId)
timeout {
requestTimeoutMillis = timeout
}
body = message
}
}

override suspend fun get(
urlBuilder: URLBuilder.(URLBuilder) -> Unit
timeout: Long,
urlBuilder: URLBuilder.(URLBuilder) -> Unit,
): HttpResponse {
return client.get {
header("appInstanceId", appInfo.appInstanceId)
timeout {
requestTimeoutMillis = timeout
}
url {
urlBuilder(this)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.clipevery.net

import com.clipevery.Dependencies
import com.clipevery.dao.sync.SyncRuntimeInfo
import com.clipevery.dao.sync.SyncState
import com.clipevery.utils.TelnetUtils

class ConnectedState: ConnectState {
override suspend fun autoResolve(syncRuntimeInfo: SyncRuntimeInfo) {
Expand Down Expand Up @@ -33,14 +36,21 @@ class ConnectingState(private val clientHandler: ClientHandler): ConnectState {
}

override suspend fun next(): Boolean {
TODO("Not yet implemented")
return true
}

}

class DisconnectedState(private val clientHandler: ClientHandler): ConnectState {

private val telnetUtils = Dependencies.koinApplication.koin.get<TelnetUtils>()

override suspend fun autoResolve(syncRuntimeInfo: SyncRuntimeInfo) {
TODO("Not yet implemented")
telnetUtils.switchHost(syncRuntimeInfo.hostInfoList, syncRuntimeInfo.port)?.let { hostInfo ->
clientHandler.updateSyncStateWithHostInfo(SyncState.Companion.CONNECTING, hostInfo, syncRuntimeInfo.port)
} ?: run {
clientHandler.updateSyncState(SyncState.Companion.UNMATCHED)
}
}

override suspend fun next(): Boolean {
Expand All @@ -51,22 +61,22 @@ class DisconnectedState(private val clientHandler: ClientHandler): ConnectState

class UnmatchedState: ConnectState {
override suspend fun autoResolve(syncRuntimeInfo: SyncRuntimeInfo) {
TODO("Not yet implemented")
// do nothing
}

override suspend fun next(): Boolean {
TODO("Not yet implemented")
return false
}

}

class UnverifiedState(private val clientHandler: ClientHandler): ConnectState {
override suspend fun autoResolve(syncRuntimeInfo: SyncRuntimeInfo) {
TODO("Not yet implemented")
// do nothing
}

override suspend fun next(): Boolean {
TODO("Not yet implemented")
return false
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class DesktopSyncClientApi(private val clipClient: ClipClient): SyncClientApi {

override suspend fun getPreKeyBundle(toUrl: URLBuilder.(URLBuilder) -> Unit): PreKeyBundle? {
try {
val response = clipClient.get(toUrl)
val response = clipClient.get(urlBuilder = toUrl)
if (response.status.value != 200) {
return null
}
Expand All @@ -29,7 +29,7 @@ class DesktopSyncClientApi(private val clipClient: ClipClient): SyncClientApi {
toUrl: URLBuilder.(URLBuilder) -> Unit): Boolean {
try {
val ciphertextMessage = sessionCipher.encrypt("exchange".toByteArray(Charsets.UTF_8))
val response = clipClient.post(toUrl, ciphertextMessage.serialize())
val response = clipClient.post(ciphertextMessage.serialize(), urlBuilder = toUrl)
if (response.status.value != 200) {
return false
}
Expand Down
43 changes: 0 additions & 43 deletions composeApp/src/desktopMain/kotlin/com/clipevery/utils/NetUtils.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.clipevery.utils

import com.clipevery.dao.sync.HostInfo
import com.clipevery.net.ClipClient
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.async
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext

class TelnetUtils(private val clipClient: ClipClient) {

private val logger = KotlinLogging.logger {}

suspend fun switchHost(hostInfoList: List<HostInfo>, port: Int, timeout: Long = 500L): HostInfo? {
if (hostInfoList.isEmpty()) {
return null
}
val deferredArray = withContext(ioDispatcher) {
hostInfoList.map { hostInfo ->
async {
if (telnet(hostInfo, port, timeout)) hostInfo else null
}
}
}

var result: HostInfo? = null
while (deferredArray.isNotEmpty() && result == null) {
select {
deferredArray.forEach { deferred ->
deferred.onAwait { hostInfo ->
if (hostInfo != null) {
result = hostInfo
deferredArray.forEach { it.cancel() }
}
}
}
}
}
return result
}

private suspend fun telnet(hostInfo: HostInfo, port: Int, timeout: Long): Boolean {
return try {
val httpResponse = clipClient.get(timeout = timeout) { urlBuilder ->
urlBuilder.port = port
urlBuilder.host = hostInfo.hostAddress
}
httpResponse.status.value == 200
} catch (e: Exception) {
logger.debug(e) { "telnet $hostInfo fail" }
false
}
}
}

0 comments on commit 93f9562

Please sign in to comment.