diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ChannelConnection.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ChannelConnection.kt new file mode 100644 index 0000000000000000000000000000000000000000..c467c3b6e15469fa24cebd0b4663bd4f29095f77 --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ChannelConnection.kt @@ -0,0 +1,56 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.io.ChainedByteBuffer +import de.justjanne.libquassel.protocol.io.CoroutineChannel +import java.nio.ByteBuffer +import javax.net.ssl.SSLContext + +class ChannelConnection( + private val channel: CoroutineChannel, +): Connection { + private val sizeBuffer = ByteBuffer.allocateDirect(4) + private val buffer = ChainedByteBuffer() + + override suspend fun enableTLS(sslContext: SSLContext) = channel.enableTLS(sslContext) + + override suspend fun enableCompression() = channel.enableCompression() + + override suspend fun send(sizePrefix: Boolean, handler: (buffer: ChainedByteBuffer) -> Unit) { + handler(buffer) + if (sizePrefix) { + sizeBuffer.clear() + sizeBuffer.putInt(buffer.size) + sizeBuffer.flip() + channel.write(sizeBuffer) + sizeBuffer.clear() + } + channel.write(buffer) + channel.flush() + buffer.clear() + } + + override suspend fun <T> read(buffer: ByteBuffer, handler: (buffer: ByteBuffer) -> T): T { + channel.read(buffer) + buffer.flip() + return handler(buffer) + } + + override suspend fun <T> read(handler: (buffer: ByteBuffer) -> T): T { + sizeBuffer.clear() + channel.read(sizeBuffer) + sizeBuffer.flip() + val content = ByteBuffer.allocateDirect(sizeBuffer.getInt()) + channel.read(content) + content.flip() + return handler(content) + } +} diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientHandshakeHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientHandshakeHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..46d655dbfd6afcb589a5be2b348deacf7199691c --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientHandshakeHandler.kt @@ -0,0 +1,58 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.exceptions.HandshakeException +import de.justjanne.libquassel.protocol.features.FeatureSet +import de.justjanne.libquassel.protocol.models.HandshakeMessage +import de.justjanne.libquassel.protocol.serializers.HandshakeMessageSerializer + +class ClientHandshakeHandler( + private val clientInit: HandshakeMessage.ClientInit, + private val clientLogin: HandshakeMessage.ClientLogin, +) : ConnectionHandler<HandshakeState> { + private suspend fun Connection.request(message: HandshakeMessage): HandshakeMessage { + send(sizePrefix = true) { + HandshakeMessageSerializer.serialize(it, message, FeatureSet.none()) + } + return read { HandshakeMessageSerializer.deserialize(it, FeatureSet.none()) } + } + + private suspend fun Connection.clientInit(message: HandshakeMessage.ClientInit): Result<HandshakeMessage.ClientInitAck> = + when (val result = request(message)) { + is HandshakeMessage.ClientInitAck -> Result.success(result) + is HandshakeMessage.ClientInitReject -> Result.failure(HandshakeException.InitException(result.errorString ?: "Unknown error")) + else -> Result.failure(HandshakeException.InitException("Unknown error")) + } + + private suspend fun Connection.clientLogin(message: HandshakeMessage.ClientLogin): Result<HandshakeMessage.ClientLoginAck> = + when (val result = request(message)) { + is HandshakeMessage.ClientLoginAck -> Result.success(result) + is HandshakeMessage.ClientLoginReject -> Result.failure(HandshakeException.InitException(result.errorString ?: "Unknown error")) + else -> Result.failure(HandshakeException.InitException("Unknown error")) + } + + private suspend fun Connection.coreSetup(message: HandshakeMessage.CoreSetupData): Result<HandshakeMessage.CoreSetupAck> = + when (val result = request(message)) { + is HandshakeMessage.CoreSetupAck -> Result.success(result) + is HandshakeMessage.CoreSetupReject -> Result.failure(HandshakeException.InitException(result.errorString ?: "Unknown error")) + else -> Result.failure(HandshakeException.InitException("Unknown error")) + } + + override suspend fun handle(connection: Connection) = runCatching { + val init = connection.clientInit(clientInit).getOrThrow() + connection.clientLogin(clientLogin).getOrThrow() + val sessionInit = when (val result = connection.read { HandshakeMessageSerializer.deserialize(it, FeatureSet.none()) }) { + is HandshakeMessage.SessionInit -> Result.success(result) + else -> Result.failure(HandshakeException.InitException("Unknown error")) + }.getOrThrow() + HandshakeState(init, sessionInit) + } +} diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..30e83a8b8b5ea3610cb1eabcba7a8e8f89750573 --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt @@ -0,0 +1,53 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.api.ObjectName +import de.justjanne.libquassel.protocol.api.dispatcher.RpcDispatcher +import de.justjanne.libquassel.protocol.api.dispatcher.SyncHandler +import de.justjanne.libquassel.protocol.models.SignalProxyMessage +import de.justjanne.libquassel.protocol.models.types.QtType +import de.justjanne.libquassel.protocol.serializers.SignalProxyMessageSerializer +import de.justjanne.libquassel.protocol.variant.qVariant +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +class ClientSessionHandler( + private val handshake: HandshakeState, + private val sync: SyncHandler, + private val rpc: RpcDispatcher, +) : ConnectionHandler<Unit> { + val toInit = MutableStateFlow<List<Pair<String, String>>?>(null) + + override suspend fun handle(connection: Connection) = runCatching { + suspend fun Connection.send(message: SignalProxyMessage) = send(true) { + SignalProxyMessageSerializer.serialize(it, message, handshake.clientInitAck.featureSet) + } + + toInit.value = listOf( + Pair("AliasManager", "") + ) + connection.send(SignalProxyMessage.InitRequest("AliasManager", "")) + + while (true) { + when (val message = connection.read { SignalProxyMessageSerializer.deserialize(it, handshake.clientInitAck.featureSet) }) { + is SignalProxyMessage.HeartBeat -> connection.send(SignalProxyMessage.HeartBeatReply(message.timestamp)) + is SignalProxyMessage.HeartBeatReply -> Unit + is SignalProxyMessage.InitData -> { + sync.invoke(message.className, ObjectName(message.objectName), "update", listOf(qVariant(message.initData, QtType.QVariantMap))) + toInit.update { it?.minus(Pair(message.className, message.objectName)) } + } + is SignalProxyMessage.InitRequest -> Unit + is SignalProxyMessage.Rpc -> rpc.invoke(message.slotName, message.params) + is SignalProxyMessage.Sync -> sync.invoke(message.className, ObjectName(message.objectName), message.slotName, message.params) + } + } + } +} diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/Connection.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/Connection.kt new file mode 100644 index 0000000000000000000000000000000000000000..667b009d3f6a95411fd6cef5363b5677afab47aa --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/Connection.kt @@ -0,0 +1,23 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.io.ChainedByteBuffer +import java.nio.ByteBuffer +import javax.net.ssl.SSLContext + +interface Connection { + suspend fun enableTLS(sslContext: SSLContext) + suspend fun enableCompression() + + suspend fun send(sizePrefix: Boolean, handler: (buffer: ChainedByteBuffer) -> Unit) + suspend fun <T> read(buffer: ByteBuffer, handler: (buffer: ByteBuffer) -> T): T + suspend fun <T> read(handler: (buffer: ByteBuffer) -> T): T +} diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ConnectionHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ConnectionHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..268146b3cb18dc5c8dbfa13d2c9e1a2dc38f4f34 --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ConnectionHandler.kt @@ -0,0 +1,14 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +interface ConnectionHandler<T> { + suspend fun handle(connection: Connection): Result<T> +} diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ConnectionSetupState.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ConnectionSetupState.kt new file mode 100644 index 0000000000000000000000000000000000000000..afdb6486e80014eb6a46b48ec8425b333a6678be --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ConnectionSetupState.kt @@ -0,0 +1,16 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.connection.CoreHeader + +data class ConnectionSetupState( + val coreHeader: CoreHeader, +) diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/HandshakeState.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/HandshakeState.kt new file mode 100644 index 0000000000000000000000000000000000000000..019a9add5c9a33c855d497bafbe6b36bbc37dd92 --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/HandshakeState.kt @@ -0,0 +1,17 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.models.HandshakeMessage + +data class HandshakeState( + val clientInitAck: HandshakeMessage.ClientInitAck, + val sessionInit: HandshakeMessage.SessionInit, +) diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/MagicHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/MagicHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..779e191442697da90b7c9769e37c31e66b8296f0 --- /dev/null +++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/MagicHandler.kt @@ -0,0 +1,39 @@ +/* + * libquassel + * Copyright (c) 2025 Janne Mareike Koschinski + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.connection + +import de.justjanne.libquassel.protocol.connection.ClientHeader +import de.justjanne.libquassel.protocol.connection.ClientHeaderSerializer +import de.justjanne.libquassel.protocol.connection.CoreHeader +import de.justjanne.libquassel.protocol.connection.CoreHeaderSerializer +import de.justjanne.libquassel.protocol.connection.ProtocolFeature +import de.justjanne.libquassel.protocol.features.FeatureSet +import java.nio.ByteBuffer +import javax.net.ssl.SSLContext + +class MagicHandler( + private val clientHeader: ClientHeader, +) : ConnectionHandler<ConnectionSetupState> { + override suspend fun handle(connection: Connection) = runCatching { + connection.send(sizePrefix = false) { + ClientHeaderSerializer.serialize(it, clientHeader, FeatureSet.none()) + } + val protocol: CoreHeader = connection.read(ByteBuffer.allocateDirect(4)) { + CoreHeaderSerializer.deserialize(it, FeatureSet.none()) + } + if (protocol.features.contains(ProtocolFeature.TLS)) { + connection.enableTLS(SSLContext.getDefault()) + } + if (protocol.features.contains(ProtocolFeature.Compression)) { + connection.enableCompression() + } + ConnectionSetupState(protocol) + } +} diff --git a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt index 369e2fc22b3ba6889a2ddbeb690f9ca6d078dc60..ac19ec3ac76873b577e121ff8ef1e5150f4114d5 100644 --- a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt +++ b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt @@ -15,6 +15,7 @@ import dagger.Binds import dagger.Component import dagger.Module import dagger.Provides +import de.justjanne.bitflags.of import de.justjanne.libquassel.backend.AliasManagerPersister import de.justjanne.libquassel.backend.BacklogManagerPersister import de.justjanne.libquassel.backend.BufferSyncerPersister @@ -31,8 +32,11 @@ import de.justjanne.libquassel.backend.IrcUserPersister import de.justjanne.libquassel.backend.NetworkConfigPersister import de.justjanne.libquassel.backend.NetworkPersister import de.justjanne.libquassel.backend.RpcPersister +import de.justjanne.libquassel.connection.ChannelConnection +import de.justjanne.libquassel.connection.ClientHandshakeHandler +import de.justjanne.libquassel.connection.ClientSessionHandler +import de.justjanne.libquassel.connection.MagicHandler import de.justjanne.libquassel.persistence.AliasEntity -import de.justjanne.libquassel.persistence.AliasRepository import de.justjanne.libquassel.persistence.AppDatabase import de.justjanne.libquassel.protocol.api.ObjectName import de.justjanne.libquassel.protocol.api.client.AliasManagerClientApi @@ -68,7 +72,14 @@ import de.justjanne.libquassel.protocol.api.server.IgnoreListManagerServerApi import de.justjanne.libquassel.protocol.api.server.IrcListHelperServerApi import de.justjanne.libquassel.protocol.api.server.NetworkConfigServerApi import de.justjanne.libquassel.protocol.api.server.NetworkServerApi +import de.justjanne.libquassel.protocol.connection.ClientHeader +import de.justjanne.libquassel.protocol.connection.ProtocolFeature +import de.justjanne.libquassel.protocol.connection.ProtocolMeta +import de.justjanne.libquassel.protocol.connection.ProtocolVersion import de.justjanne.libquassel.protocol.exceptions.RpcInvocationFailedException +import de.justjanne.libquassel.protocol.features.FeatureSet +import de.justjanne.libquassel.protocol.io.CoroutineChannel +import de.justjanne.libquassel.protocol.models.HandshakeMessage import de.justjanne.libquassel.protocol.models.ids.BufferId import de.justjanne.libquassel.protocol.models.ids.MsgId import de.justjanne.libquassel.protocol.models.types.QtType @@ -77,17 +88,24 @@ import de.justjanne.libquassel.protocol.variant.QVariant_ import de.justjanne.libquassel.protocol.variant.qVariant import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.net.InetSocketAddress +import java.nio.channels.ClosedByInterruptException import javax.inject.Inject import javax.inject.Singleton import kotlin.test.assertFails import kotlin.test.assertFailsWith import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds @Singleton class ProxyImpl @Inject constructor() : Proxy { @@ -123,6 +141,7 @@ interface ClientModule { @Module class DatabaseModule { + @Singleton @Provides fun database(): AppDatabase = Room.inMemoryDatabaseBuilder<AppDatabase>() @@ -163,7 +182,7 @@ interface ClientComponent { class QuasselApiTest { @Test - fun test() = runBlocking{ + fun test() = runBlocking { val client = DaggerClientComponent.builder().build() client.sync().invoke( "AliasManager", @@ -191,7 +210,6 @@ class QuasselApiTest { client.api().certManager.requestUpdate(ObjectName(""), emptyMap()) client.api().highlightRuleManager.requestRemoveHighlightRule(5) client.api().identity.requestUpdate(ObjectName(""), emptyMap()) - println("hi!") } @Test @@ -223,4 +241,38 @@ class QuasselApiTest { delay(10.milliseconds) job.cancelAndJoin() } + + @Test + fun testConnection() = runBlocking { + val di = DaggerClientComponent.builder().build() + val channel = CoroutineChannel() + withTimeout(500.milliseconds) { + channel.connect(InetSocketAddress("decentralised.chat", 4242), keepAlive = true) + } + val connection = ChannelConnection(channel) + val connectionSetup = withTimeout(500.milliseconds) { + MagicHandler( + ClientHeader( + features = ProtocolFeature.of(ProtocolFeature.Compression, ProtocolFeature.TLS), + versions = listOf(ProtocolMeta(ProtocolVersion.Datastream, 0u)) + ), + ).handle(connection) + }.getOrThrow() + val handshake = withTimeout(500.milliseconds) { + ClientHandshakeHandler( + HandshakeMessage.ClientInit(clientVersion = "", buildDate = "", featureSet = FeatureSet.none()), + HandshakeMessage.ClientLogin(Const.user, Const.pass), + ).handle(connection) + }.getOrThrow() + val sessionHandler = ClientSessionHandler(handshake, di.sync(), di.rpc()) + withTimeout(2.seconds) { + launch { + sessionHandler.handle(connection) + } + sessionHandler.toInit.first { it != null && it.isEmpty() } + channel.close() + } + println(di.db().alias().getAll()) + Unit + } } diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt deleted file mode 100644 index d4dadc7ff3d87f64960842dac647bfaa28af34a0..0000000000000000000000000000000000000000 --- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * libquassel - * Copyright (c) 2021 Janne Mareike Koschinski - * - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at https://mozilla.org/MPL/2.0/. - */ - -package de.justjanne.libquassel.protocol.session - -import java.nio.ByteBuffer - -interface ConnectionHandler { - suspend fun init(channel: MessageChannel): Boolean - - suspend fun done() - - suspend fun read(buffer: ByteBuffer): Boolean -} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CoreState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CoreState.kt deleted file mode 100644 index 49acd8ffedc22a5092814dc7c09e6fb516724a83..0000000000000000000000000000000000000000 --- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CoreState.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * libquassel - * Copyright (c) 2021 Janne Mareike Koschinski - * - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at https://mozilla.org/MPL/2.0/. - */ - -package de.justjanne.libquassel.protocol.session - -import de.justjanne.libquassel.protocol.models.setup.BackendInfo - -sealed class CoreState { - object Configured : CoreState() - - data class Unconfigured( - val databases: List<BackendInfo>, - val authenticators: List<BackendInfo>, - ) : CoreState() -} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/HandshakeHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/HandshakeHandler.kt deleted file mode 100644 index dde3a9f80441396a0a12515e5b07cdf13a665c49..0000000000000000000000000000000000000000 --- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/HandshakeHandler.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * libquassel - * Copyright (c) 2021 Janne Mareike Koschinski - * - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at https://mozilla.org/MPL/2.0/. - */ - -package de.justjanne.libquassel.protocol.session - -import de.justjanne.libquassel.protocol.exceptions.HandshakeException -import de.justjanne.libquassel.protocol.features.FeatureSet -import de.justjanne.libquassel.protocol.variant.QVariantMap - -interface HandshakeHandler : ConnectionHandler { - /** - * Register client and start connection - */ - @Throws(HandshakeException.InitException::class) - suspend fun init( - /** - * Human readable (HTML formatted) version of the client - */ - clientVersion: String, - /** - * Build timestamp of the client - */ - buildDate: String, - /** - * Enabled client features for this connection - */ - featureSet: FeatureSet, - ): CoreState - - /** - * Login to core with authentication data - */ - @Throws(HandshakeException.LoginException::class) - suspend fun login( - /** - * Username of the core account - */ - username: String, - /** - * Password of the core account - */ - password: String, - ) - - /** - * Configure core for the first time - */ - @Throws(HandshakeException.SetupException::class) - suspend fun configureCore( - /** - * Username of a new core account to be created - */ - adminUsername: String, - /** - * Password of a new core account to be created - */ - adminPassword: String, - /** - * Chosen storage backend id - */ - backend: String, - /** - * Storage backend configuration data - */ - backendConfiguration: QVariantMap, - /** - * Chosen authenticator backend id - */ - authenticator: String, - /** - * Authenticator backend configuration data - */ - authenticatorConfiguration: QVariantMap, - ) -} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt deleted file mode 100644 index 8ca8b05838ebd02a09ec933951a4d665e210a39f..0000000000000000000000000000000000000000 --- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * libquassel - * Copyright (c) 2021 Janne Mareike Koschinski - * - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at https://mozilla.org/MPL/2.0/. - */ - -package de.justjanne.libquassel.protocol.session - -import de.justjanne.libquassel.protocol.features.FeatureSet -import de.justjanne.libquassel.protocol.io.ChainedByteBuffer -import de.justjanne.libquassel.protocol.io.CoroutineChannel -import de.justjanne.libquassel.protocol.models.HandshakeMessage -import de.justjanne.libquassel.protocol.models.SignalProxyMessage -import de.justjanne.libquassel.protocol.serializers.HandshakeMessageSerializer -import de.justjanne.libquassel.protocol.serializers.SignalProxyMessageSerializer -import de.justjanne.libquassel.protocol.util.log.trace -import kotlinx.coroutines.coroutineScope -import org.slf4j.LoggerFactory -import java.io.Closeable -import java.nio.ByteBuffer - -class MessageChannel( - val channel: CoroutineChannel, -) : Closeable { - var negotiatedFeatures = FeatureSet.none() - - private var handlers = mutableListOf<ConnectionHandler>() - - fun register(handler: ConnectionHandler) { - handlers.add(handler) - } - - private val sendBuffer = ThreadLocal.withInitial(::ChainedByteBuffer) - private val sizeBuffer = ThreadLocal.withInitial { ByteBuffer.allocateDirect(4) } - - suspend fun init() { - setupHandlers() - } - - private suspend fun readAmount(): Int { - val sizeBuffer = sizeBuffer.get() - - sizeBuffer.clear() - channel.read(sizeBuffer) - sizeBuffer.flip() - val size = sizeBuffer.int - sizeBuffer.clear() - return size - } - - suspend fun read() { - val amount = readAmount() - val messageBuffer = ByteBuffer.allocateDirect(minOf(amount, 65 * 1024 * 1024)) - channel.read(messageBuffer) - messageBuffer.flip() - dispatch(messageBuffer) - } - - private suspend fun setupHandlers(): List<ConnectionHandler> { - val removed = mutableListOf<ConnectionHandler>() - while (true) { - val handler = handlers.firstOrNull() - logger.trace { "Setting up handler $handler" } - if (handler?.init(this) != true) { - break - } - logger.trace { "Handler $handler is done" } - removed.add(handlers.removeFirst()) - } - if (handlers.isEmpty()) { - logger.trace { "All handlers done" } - channel.close() - } - return removed - } - - private suspend fun dispatch(message: ByteBuffer) { - val handlerDone = - try { - handlers.first().read(message) - } catch (e: Exception) { - logger.warn("Error while handling message: ", e) - false - } - if (handlerDone) { - val removed = listOf(handlers.removeFirst()) + setupHandlers() - for (handler in removed) { - handler.done() - } - } - } - - suspend fun emit(message: HandshakeMessage) = - emit { - logger.trace { "Writing handshake message $message" } - HandshakeMessageSerializer.serialize(it, message, negotiatedFeatures) - } - - suspend fun emit(message: SignalProxyMessage) = - emit { - logger.trace { "Writing signal proxy message $message" } - SignalProxyMessageSerializer.serialize(it, message, negotiatedFeatures) - } - - suspend fun emit( - sizePrefix: Boolean = true, - f: (ChainedByteBuffer) -> Unit, - ) = coroutineScope { - val sendBuffer = sendBuffer.get() - val sizeBuffer = sizeBuffer.get() - - f(sendBuffer) - if (sizePrefix) { - sizeBuffer.clear() - sizeBuffer.putInt(sendBuffer.size) - sizeBuffer.flip() - channel.write(sizeBuffer) - sizeBuffer.clear() - } - channel.write(sendBuffer) - channel.flush() - sendBuffer.clear() - } - - override fun close() { - channel.close() - } - - companion object { - private val logger = LoggerFactory.getLogger(MessageChannel::class.java) - } -} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannelReader.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannelReader.kt deleted file mode 100644 index 28dbe476aa76688fb873269440f940c422cc57cf..0000000000000000000000000000000000000000 --- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannelReader.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * libquassel - * Copyright (c) 2021 Janne Mareike Koschinski - * - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at https://mozilla.org/MPL/2.0/. - */ - -package de.justjanne.libquassel.protocol.session - -import de.justjanne.libquassel.protocol.util.log.info -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancel -import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.slf4j.LoggerFactory -import java.io.Closeable -import java.nio.channels.ClosedChannelException -import java.util.concurrent.Executors - -class MessageChannelReader( - private val channel: MessageChannel, -) : Closeable { - private val executor = Executors.newSingleThreadExecutor() - private val dispatcher = executor.asCoroutineDispatcher() - private val scope = CoroutineScope(dispatcher) - private var job: Job? = null - - fun start() { - job = - scope.launch { - try { - channel.init() - while (isActive && channel.channel.state().connected) { - channel.read() - } - } catch (e: ClosedChannelException) { - logger.info { "Channel closed" } - close() - } - } - } - - override fun close() { - channel.close() - runBlocking { job?.cancelAndJoin() } - scope.cancel() - dispatcher.cancel() - executor.shutdown() - } - - companion object { - private val logger = LoggerFactory.getLogger(MessageChannelReader::class.java) - } -} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ProxyMessageHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ProxyMessageHandler.kt deleted file mode 100644 index 9854bd28f0db0d4b1b74f24e5e40fcb816209475..0000000000000000000000000000000000000000 --- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ProxyMessageHandler.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * libquassel - * Copyright (c) 2021 Janne Mareike Koschinski - * - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at https://mozilla.org/MPL/2.0/. - */ - -package de.justjanne.libquassel.protocol.session - -import de.justjanne.libquassel.protocol.models.SignalProxyMessage - -interface ProxyMessageHandler : ConnectionHandler { - suspend fun emit(message: SignalProxyMessage) - - suspend fun dispatch(message: SignalProxyMessage) -}