Skip to content
Snippets Groups Projects
Verified Commit 50f1d3dd authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement cleaner code for backend configuration

parent 2a320461
Branches
Tags
No related merge requests found
Showing
with 388 additions and 47 deletions
...@@ -119,15 +119,14 @@ class EndToEndTest { ...@@ -119,15 +119,14 @@ class EndToEndTest {
HandshakeMessage.ClientInit( HandshakeMessage.ClientInit(
clientVersion = "Quasseldroid test", clientVersion = "Quasseldroid test",
buildDate = "Never", buildDate = "Never",
clientFeatures = connectionFeatureSet.legacyFeatures(), featureSet = connectionFeatureSet
featureList = connectionFeatureSet.featureList()
), ),
connectionFeatureSet connectionFeatureSet
) )
} }
println("Reading clientInit response") println("Reading clientInit response")
read { read {
HandshakeMessageSerializer.deserialize(it, connectionFeatureSet) println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
} }
println("Writing invalid core init") println("Writing invalid core init")
write { write {
...@@ -168,7 +167,7 @@ class EndToEndTest { ...@@ -168,7 +167,7 @@ class EndToEndTest {
} }
println("Reading valid clientInit response") println("Reading valid clientInit response")
read { read {
HandshakeMessageSerializer.deserialize(it, connectionFeatureSet) println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
} }
println("Writing invalid clientLogin") println("Writing invalid clientLogin")
write { write {
...@@ -206,11 +205,11 @@ class EndToEndTest { ...@@ -206,11 +205,11 @@ class EndToEndTest {
} }
println("Reading valid clientLogin response") println("Reading valid clientLogin response")
read { read {
HandshakeMessageSerializer.deserialize(it, connectionFeatureSet) println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
} }
println("Reading valid session init") println("Reading valid session init")
read { read {
HandshakeMessageSerializer.deserialize(it, connectionFeatureSet) println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
} }
} }
......
...@@ -16,7 +16,7 @@ import de.justjanne.bitflags.of ...@@ -16,7 +16,7 @@ import de.justjanne.bitflags.of
* Model representing the set of negotiated or supported features for a * Model representing the set of negotiated or supported features for a
* connection or library * connection or library
*/ */
class FeatureSet internal constructor( data class FeatureSet internal constructor(
private val features: Set<QuasselFeature>, private val features: Set<QuasselFeature>,
private val additional: Set<QuasselFeatureName> = emptySet() private val additional: Set<QuasselFeatureName> = emptySet()
) { ) {
......
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* 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.models
data class BackendInfo(
val entries: List<SetupEntry>,
val isDefault: Boolean,
val displayName: String,
val description: String,
val backendId: String,
)
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* 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.models
import de.justjanne.libquassel.protocol.models.types.QtType
import de.justjanne.libquassel.protocol.util.triples
import de.justjanne.libquassel.protocol.variant.QVariantList
import de.justjanne.libquassel.protocol.variant.QVariantMap
import de.justjanne.libquassel.protocol.variant.QVariant_
import de.justjanne.libquassel.protocol.variant.into
import de.justjanne.libquassel.protocol.variant.qVariant
object BackendInfoSerializer {
fun serialize(data: BackendInfo): QVariantMap = mapOf(
"SetupKeys" to qVariant<QStringList>(
data.entries.map(SetupEntry::key),
QtType.QStringList
),
"SetupDefaults" to qVariant<QVariantMap>(
data.entries.map { it.key to it.defaultValue }.toMap<String, QVariant_>(),
QtType.QVariantMap
),
"SetupData" to qVariant<QVariantList>(
data.entries.flatMap {
listOf<QVariant_>(
qVariant(it.key, QtType.QString),
qVariant(it.displayName, QtType.QString),
it.defaultValue
)
},
QtType.QVariantList
),
"IsDefault" to qVariant(
data.isDefault,
QtType.Bool
),
"DisplayName" to qVariant(
data.displayName,
QtType.QString
),
"Description" to qVariant(
data.description,
QtType.QString
),
"BackendId" to qVariant(
data.backendId,
QtType.QString
),
)
private fun parseSetupEntries(
data: QVariantList?,
defaults: QVariantMap
): List<SetupEntry> = data?.triples { key, displayName, defaultValue ->
SetupEntry(key.into(""), displayName.into(""), defaultValue)
} ?: defaults.map { (key, value) -> SetupEntry(key, key, value) }
fun deserialize(data: QVariantMap) = BackendInfo(
entries = parseSetupEntries(
data["SetupData"].into<QVariantList>(),
data["SetupDefaults"].into<QVariantMap>().orEmpty()
),
isDefault = data["IsDefault"].into<Boolean>(false),
displayName = data["DisplayName"].into<String>(""),
description = data["Description"].into<String>(""),
backendId = data["BackendId"].into<String>("")
)
}
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
package de.justjanne.libquassel.protocol.models package de.justjanne.libquassel.protocol.models
import de.justjanne.libquassel.protocol.features.LegacyFeatures import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.features.QuasselFeatureName
import de.justjanne.libquassel.protocol.variant.QVariantList import de.justjanne.libquassel.protocol.variant.QVariantList
import de.justjanne.libquassel.protocol.variant.QVariantMap import de.justjanne.libquassel.protocol.variant.QVariantMap
...@@ -35,14 +34,9 @@ sealed class HandshakeMessage { ...@@ -35,14 +34,9 @@ sealed class HandshakeMessage {
*/ */
val buildDate: String?, val buildDate: String?,
/** /**
* Set of legacy features supported * Enabled client features for this connection
*/ */
val clientFeatures: LegacyFeatures, val featureSet: FeatureSet,
/**
* List of supported features. If a feature can also be represented as
* legacy feature, it is included in both.
*/
val featureList: List<QuasselFeatureName>
) : HandshakeMessage() ) : HandshakeMessage()
/** /**
...@@ -60,21 +54,16 @@ sealed class HandshakeMessage { ...@@ -60,21 +54,16 @@ sealed class HandshakeMessage {
* If the core needs to be configured, this contains metadata about the * If the core needs to be configured, this contains metadata about the
* supported storage backends * supported storage backends
*/ */
val backendInfo: QVariantList, val backendInfo: List<BackendInfo>,
/** /**
* If the core needs to be configured, this contains metadata about the * If the core needs to be configured, this contains metadata about the
* supported authentication backends * supported authentication backends
*/ */
val authenticatorInfo: QVariantList, val authenticatorInfo: List<BackendInfo>,
/**
* Set of legacy features supported
*/
val coreFeatures: LegacyFeatures,
/** /**
* List of supported features. If a feature can also be represented as * Enabled features for this connection
* legacy feature, it is included in both.
*/ */
val featureList: List<QuasselFeatureName> val featureSet: FeatureSet,
) : HandshakeMessage() ) : HandshakeMessage()
/** /**
......
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* 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.models
import de.justjanne.libquassel.protocol.variant.QVariant_
data class SetupEntry(
val key: String,
val displayName: String,
val defaultValue: QVariant_,
)
...@@ -12,12 +12,15 @@ package de.justjanne.libquassel.protocol.serializers.handshake ...@@ -12,12 +12,15 @@ package de.justjanne.libquassel.protocol.serializers.handshake
import de.justjanne.bitflags.of import de.justjanne.bitflags.of
import de.justjanne.bitflags.toBits import de.justjanne.bitflags.toBits
import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.features.LegacyFeature import de.justjanne.libquassel.protocol.features.LegacyFeature
import de.justjanne.libquassel.protocol.features.QuasselFeatureName import de.justjanne.libquassel.protocol.features.QuasselFeatureName
import de.justjanne.libquassel.protocol.models.BackendInfoSerializer
import de.justjanne.libquassel.protocol.models.HandshakeMessage import de.justjanne.libquassel.protocol.models.HandshakeMessage
import de.justjanne.libquassel.protocol.models.QStringList import de.justjanne.libquassel.protocol.models.QStringList
import de.justjanne.libquassel.protocol.models.types.QtType import de.justjanne.libquassel.protocol.models.types.QtType
import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
import de.justjanne.libquassel.protocol.variant.QVariantList
import de.justjanne.libquassel.protocol.variant.QVariantMap import de.justjanne.libquassel.protocol.variant.QVariantMap
import de.justjanne.libquassel.protocol.variant.into import de.justjanne.libquassel.protocol.variant.into
import de.justjanne.libquassel.protocol.variant.qVariant import de.justjanne.libquassel.protocol.variant.qVariant
...@@ -30,20 +33,45 @@ object ClientInitAckSerializer : HandshakeSerializer<HandshakeMessage.ClientInit ...@@ -30,20 +33,45 @@ object ClientInitAckSerializer : HandshakeSerializer<HandshakeMessage.ClientInit
override fun serialize(data: HandshakeMessage.ClientInitAck) = mapOf( override fun serialize(data: HandshakeMessage.ClientInitAck) = mapOf(
"MsgType" to qVariant(type, QtType.QString), "MsgType" to qVariant(type, QtType.QString),
"CoreFeatures" to qVariant(data.coreFeatures.toBits(), QtType.UInt), "CoreFeatures" to qVariant(data.featureSet.legacyFeatures().toBits(), QtType.UInt),
"StorageBackends" to qVariant(data.backendInfo, QtType.QVariantList), "StorageBackends" to qVariant<QVariantList>(
"Authenticator" to qVariant(data.authenticatorInfo, QtType.QVariantList), data.backendInfo.map {
qVariant<QVariantMap>(
BackendInfoSerializer.serialize(it),
QtType.QVariantMap
)
},
QtType.QVariantList
),
"Authenticator" to qVariant<QVariantList>(
data.authenticatorInfo.map {
qVariant<QVariantMap>(
BackendInfoSerializer.serialize(it),
QtType.QVariantMap
)
},
QtType.QVariantList
),
"Configured" to qVariant(data.coreConfigured, QtType.Bool), "Configured" to qVariant(data.coreConfigured, QtType.Bool),
"FeatureList" to qVariant(data.featureList, QtType.QStringList) "FeatureList" to qVariant(
data.featureSet.featureList().map(QuasselFeatureName::name),
QtType.QStringList
)
) )
override fun deserialize(data: QVariantMap) = HandshakeMessage.ClientInitAck( override fun deserialize(data: QVariantMap) = HandshakeMessage.ClientInitAck(
coreConfigured = data["Configured"].into(), coreConfigured = data["Configured"].into(),
backendInfo = data["StorageBackends"].into(emptyList()), backendInfo = data["StorageBackends"].into<QVariantList>()?.mapNotNull {
authenticatorInfo = data["Authenticators"].into(emptyList()), it.into<QVariantMap>()
coreFeatures = LegacyFeature.of(data["CoreFeatures"].into<UInt>()), }?.map(BackendInfoSerializer::deserialize).orEmpty(),
featureList = data["FeatureList"].into<QStringList>(emptyList()) authenticatorInfo = data["Authenticator"].into<QVariantList>()?.mapNotNull {
it.into<QVariantMap>()
}?.map(BackendInfoSerializer::deserialize).orEmpty(),
featureSet = FeatureSet.build(
LegacyFeature.of(data["CoreFeatures"].into<UInt>()),
data["FeatureList"].into<QStringList>(emptyList())
.filterNotNull() .filterNotNull()
.map(::QuasselFeatureName), .map(::QuasselFeatureName)
)
) )
} }
...@@ -12,9 +12,11 @@ package de.justjanne.libquassel.protocol.serializers.handshake ...@@ -12,9 +12,11 @@ package de.justjanne.libquassel.protocol.serializers.handshake
import de.justjanne.bitflags.of import de.justjanne.bitflags.of
import de.justjanne.bitflags.toBits import de.justjanne.bitflags.toBits
import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.features.LegacyFeature import de.justjanne.libquassel.protocol.features.LegacyFeature
import de.justjanne.libquassel.protocol.features.QuasselFeatureName import de.justjanne.libquassel.protocol.features.QuasselFeatureName
import de.justjanne.libquassel.protocol.models.HandshakeMessage import de.justjanne.libquassel.protocol.models.HandshakeMessage
import de.justjanne.libquassel.protocol.models.QStringList
import de.justjanne.libquassel.protocol.models.types.QtType import de.justjanne.libquassel.protocol.models.types.QtType
import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
import de.justjanne.libquassel.protocol.variant.QVariantMap import de.justjanne.libquassel.protocol.variant.QVariantMap
...@@ -31,9 +33,9 @@ object ClientInitSerializer : HandshakeSerializer<HandshakeMessage.ClientInit> { ...@@ -31,9 +33,9 @@ object ClientInitSerializer : HandshakeSerializer<HandshakeMessage.ClientInit> {
"MsgType" to qVariant(type, QtType.QString), "MsgType" to qVariant(type, QtType.QString),
"ClientVersion" to qVariant(data.clientVersion, QtType.QString), "ClientVersion" to qVariant(data.clientVersion, QtType.QString),
"ClientDate" to qVariant(data.buildDate, QtType.QString), "ClientDate" to qVariant(data.buildDate, QtType.QString),
"Features" to qVariant(data.clientFeatures.toBits(), QtType.UInt), "Features" to qVariant(data.featureSet.legacyFeatures().toBits(), QtType.UInt),
"FeatureList" to qVariant( "FeatureList" to qVariant(
data.featureList.map(QuasselFeatureName::name), data.featureSet.featureList().map(QuasselFeatureName::name),
QtType.QStringList QtType.QStringList
), ),
) )
...@@ -41,7 +43,11 @@ object ClientInitSerializer : HandshakeSerializer<HandshakeMessage.ClientInit> { ...@@ -41,7 +43,11 @@ object ClientInitSerializer : HandshakeSerializer<HandshakeMessage.ClientInit> {
override fun deserialize(data: QVariantMap) = HandshakeMessage.ClientInit( override fun deserialize(data: QVariantMap) = HandshakeMessage.ClientInit(
clientVersion = data["ClientVersion"].into(), clientVersion = data["ClientVersion"].into(),
buildDate = data["ClientDate"].into(), buildDate = data["ClientDate"].into(),
clientFeatures = LegacyFeature.of(data["Features"].into<UInt>()), featureSet = FeatureSet.build(
featureList = data["FeatureList"].into(emptyList<String>()).map(::QuasselFeatureName), LegacyFeature.of(data["Features"].into<UInt>()),
data["FeatureList"].into<QStringList>(emptyList())
.filterNotNull()
.map(::QuasselFeatureName)
)
) )
} }
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* 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.util
fun <T> Iterable<T>.pairs(): List<Pair<T, T>> {
return pairs { a, b -> Pair(a, b) }
}
inline fun <T, R> Iterable<T>.pairs(transform: (a: T, b: T) -> R): List<R> {
val iterator = iterator()
val result = mutableListOf<R>()
while (true) {
if (!iterator.hasNext()) return result
val first = iterator.next()
if (!iterator.hasNext()) return result
val second = iterator.next()
result.add(transform(first, second))
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* 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.util
fun <T> Iterable<T>.triples(): List<Triple<T, T, T>> {
return triples { a, b, c -> Triple(a, b, c) }
}
inline fun <T, R> Iterable<T>.triples(transform: (a: T, b: T, c: T) -> R): List<R> {
val iterator = iterator()
val result = mutableListOf<R>()
while (true) {
if (!iterator.hasNext()) return result
val first = iterator.next()
if (!iterator.hasNext()) return result
val second = iterator.next()
if (!iterator.hasNext()) return result
val third = iterator.next()
result.add(transform(first, second, third))
}
}
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
package de.justjanne.libquassel.protocol.variant package de.justjanne.libquassel.protocol.variant
import de.justjanne.libquassel.protocol.util.pairs
/** /**
* Simple alias for a generic QVariantList type * Simple alias for a generic QVariantList type
*/ */
...@@ -19,6 +21,6 @@ typealias QVariantList = List<QVariant_> ...@@ -19,6 +21,6 @@ typealias QVariantList = List<QVariant_>
* Transform a QVariantList of interleaved keys and values into a QVariantMap * Transform a QVariantList of interleaved keys and values into a QVariantMap
*/ */
fun QVariantList.toVariantMap(): QVariantMap = fun QVariantList.toVariantMap(): QVariantMap =
zipWithNext { key, value -> pairs { key, value ->
Pair(key.into(""), value) Pair(key.into(""), value)
}.toMap() }.toMap()
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.justjanne.libquassel.protocol.serializers.handshake
import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.models.BackendInfo
import de.justjanne.libquassel.protocol.models.HandshakeMessage
import de.justjanne.libquassel.protocol.models.SetupEntry
import de.justjanne.libquassel.protocol.models.types.QtType
import de.justjanne.libquassel.protocol.testutil.handshakeSerializerTest
import de.justjanne.libquassel.protocol.variant.qVariant
import org.junit.jupiter.api.Test
class ClientInitAckSerializerTest {
@Test
fun testSimple() = handshakeSerializerTest(
HandshakeMessage.ClientInitAck(
coreConfigured = false,
backendInfo = emptyList(),
authenticatorInfo = emptyList(),
featureSet = FeatureSet.none()
),
// byteBufferOf(0x00u)
)
@Test
fun testRealistic() = handshakeSerializerTest(
HandshakeMessage.ClientInitAck(
coreConfigured = false,
backendInfo = listOf(
BackendInfo(
entries = emptyList(),
isDefault = true,
displayName = "SQLite",
description = "SQLite is a file-based database engine that does not " +
"require any setup. It is suitable for small and medium-sized " +
"databases that do not require access via network. Use SQLite if " +
"your Quassel Core should store its data on the same machine it is " +
"running on, and if you only expect a few users to use your core.",
backendId = "SQLite"
),
BackendInfo(
entries = listOf(
SetupEntry(
"Username",
"Username",
qVariant("quassel", QtType.QString)
),
SetupEntry(
"Password",
"Password",
qVariant<String?>(null, QtType.QString)
),
SetupEntry(
"Hostname",
"Hostname",
qVariant("localhost", QtType.QString)
),
SetupEntry(
"Port",
"Port",
qVariant(5432, QtType.Int)
),
SetupEntry(
"Database",
"Database",
qVariant("quassel", QtType.QString)
)
),
isDefault = false,
displayName = "PostgreSQL",
description = "PostgreSQL Turbo Bomber HD!",
backendId = "PostgreSQL"
)
),
authenticatorInfo = listOf(
BackendInfo(
entries = emptyList(),
isDefault = true,
displayName = "Database",
description = "Do not authenticate against any remote service, but " +
"instead save a hashed and salted password in the database " +
"selected in the next step.",
backendId = "Database"
),
BackendInfo(
entries = listOf(
SetupEntry(
"Hostname",
"Hostname",
qVariant("ldap://localhost", QtType.QString),
),
SetupEntry(
"Port",
"Port",
qVariant(389, QtType.Int),
),
SetupEntry(
"BindDN",
"Bind DN",
qVariant<String?>(null, QtType.QString),
),
SetupEntry(
"BindPassword",
"Bind Password",
qVariant<String?>(null, QtType.QString),
),
SetupEntry(
"BaseDN",
"Base DN",
qVariant<String?>(null, QtType.QString),
),
SetupEntry(
"Filter",
"Filter",
qVariant<String?>(null, QtType.QString),
),
SetupEntry(
"UidAttribute",
"UID Attribute",
qVariant("uid", QtType.QString),
)
),
isDefault = false,
displayName = "LDAP",
description = "Authenticate users using an LDAP server.",
backendId = "LDAP"
)
),
featureSet = FeatureSet.none()
),
// byteBufferOf(0x00u)
)
}
...@@ -18,9 +18,7 @@ ...@@ -18,9 +18,7 @@
*/ */
package de.justjanne.libquassel.protocol.serializers.handshake package de.justjanne.libquassel.protocol.serializers.handshake
import de.justjanne.bitflags.none
import de.justjanne.libquassel.protocol.features.FeatureSet import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.features.LegacyFeature
import de.justjanne.libquassel.protocol.models.HandshakeMessage import de.justjanne.libquassel.protocol.models.HandshakeMessage
import de.justjanne.libquassel.protocol.testutil.byteBufferOf import de.justjanne.libquassel.protocol.testutil.byteBufferOf
import de.justjanne.libquassel.protocol.testutil.handshakeSerializerTest import de.justjanne.libquassel.protocol.testutil.handshakeSerializerTest
...@@ -32,8 +30,7 @@ class ClientInitSerializerTest { ...@@ -32,8 +30,7 @@ class ClientInitSerializerTest {
HandshakeMessage.ClientInit( HandshakeMessage.ClientInit(
clientVersion = "Quasseldroid test", clientVersion = "Quasseldroid test",
buildDate = "Never", buildDate = "Never",
clientFeatures = LegacyFeature.none(), featureSet = FeatureSet.none()
featureList = emptyList()
), ),
byteBufferOf( byteBufferOf(
0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x0Cu, 0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x0Cu,
...@@ -71,8 +68,8 @@ class ClientInitSerializerTest { ...@@ -71,8 +68,8 @@ class ClientInitSerializerTest {
clientVersion = "Quasseldroid <a href=\"https://git.kuschku.de/justJanne/QuasselDroid-ng/commit/" + clientVersion = "Quasseldroid <a href=\"https://git.kuschku.de/justJanne/QuasselDroid-ng/commit/" +
"b622ad63056b6054b06e09f8e1f1ef2b0c3aaf9a\">v1.3.3</a>", "b622ad63056b6054b06e09f8e1f1ef2b0c3aaf9a\">v1.3.3</a>",
buildDate = "2020-04-27T22:21:17Z", buildDate = "2020-04-27T22:21:17Z",
clientFeatures = FeatureSet.all().legacyFeatures(), featureSet = FeatureSet.all()
featureList = FeatureSet.all().featureList() ),
) // byteBufferOf(0x00u)
) )
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment