From 50f1d3dd07f65d2e8dd57d96bfbc5e07d263eb91 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Wed, 17 Feb 2021 00:22:41 +0100
Subject: [PATCH] Implement cleaner code for backend configuration

---
 .../libquassel/client/EndToEndTest.kt         |  11 +-
 .../protocol/features/FeatureSet.kt           |   2 +-
 .../libquassel/protocol/models/BackendInfo.kt |  19 +++
 .../protocol/models/BackendInfoSerializer.kt  |  76 +++++++++
 .../protocol/models/HandshakeMessage.kt       |  25 +--
 .../libquassel/protocol/models/SetupEntry.kt  |  19 +++
 .../handshake/ClientInitAckSerializer.kt      |  48 ++++--
 .../handshake/ClientInitSerializer.kt         |  14 +-
 .../libquassel/protocol/util/pairs.kt         |  27 ++++
 .../libquassel/protocol/util/triples.kt       |  29 ++++
 .../protocol/variant/QVariantList.kt          |   4 +-
 .../handshake/ClientInitAckSerializerTest.kt  | 150 ++++++++++++++++++
 .../handshake/ClientInitSerializerTest.kt     |  11 +-
 13 files changed, 388 insertions(+), 47 deletions(-)
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfo.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfoSerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SetupEntry.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/triples.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializerTest.kt

diff --git a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/EndToEndTest.kt b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/EndToEndTest.kt
index 43dd7d2..b43b134 100644
--- a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/EndToEndTest.kt
+++ b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/EndToEndTest.kt
@@ -119,15 +119,14 @@ class EndToEndTest {
         HandshakeMessage.ClientInit(
           clientVersion = "Quasseldroid test",
           buildDate = "Never",
-          clientFeatures = connectionFeatureSet.legacyFeatures(),
-          featureList = connectionFeatureSet.featureList()
+          featureSet = connectionFeatureSet
         ),
         connectionFeatureSet
       )
     }
     println("Reading clientInit response")
     read {
-      HandshakeMessageSerializer.deserialize(it, connectionFeatureSet)
+      println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
     }
     println("Writing invalid core init")
     write {
@@ -168,7 +167,7 @@ class EndToEndTest {
     }
     println("Reading valid clientInit response")
     read {
-      HandshakeMessageSerializer.deserialize(it, connectionFeatureSet)
+      println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
     }
     println("Writing invalid clientLogin")
     write {
@@ -206,11 +205,11 @@ class EndToEndTest {
     }
     println("Reading valid clientLogin response")
     read {
-      HandshakeMessageSerializer.deserialize(it, connectionFeatureSet)
+      println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
     }
     println("Reading valid session init")
     read {
-      HandshakeMessageSerializer.deserialize(it, connectionFeatureSet)
+      println(HandshakeMessageSerializer.deserialize(it, connectionFeatureSet))
     }
   }
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/features/FeatureSet.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/features/FeatureSet.kt
index 355b4be..e60daba 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/features/FeatureSet.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/features/FeatureSet.kt
@@ -16,7 +16,7 @@ import de.justjanne.bitflags.of
  * Model representing the set of negotiated or supported features for a
  * connection or library
  */
-class FeatureSet internal constructor(
+data class FeatureSet internal constructor(
   private val features: Set<QuasselFeature>,
   private val additional: Set<QuasselFeatureName> = emptySet()
 ) {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfo.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfo.kt
new file mode 100644
index 0000000..80654ef
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfo.kt
@@ -0,0 +1,19 @@
+/*
+ * 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,
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfoSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfoSerializer.kt
new file mode 100644
index 0000000..09c150d
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/BackendInfoSerializer.kt
@@ -0,0 +1,76 @@
+/*
+ * 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>("")
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/HandshakeMessage.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/HandshakeMessage.kt
index 7a2732d..87d7b95 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/HandshakeMessage.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/HandshakeMessage.kt
@@ -10,8 +10,7 @@
 
 package de.justjanne.libquassel.protocol.models
 
-import de.justjanne.libquassel.protocol.features.LegacyFeatures
-import de.justjanne.libquassel.protocol.features.QuasselFeatureName
+import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 
@@ -35,14 +34,9 @@ sealed class HandshakeMessage {
      */
     val buildDate: String?,
     /**
-     * Set of legacy features supported
+     * Enabled client features for this connection
      */
-    val clientFeatures: LegacyFeatures,
-    /**
-     * List of supported features. If a feature can also be represented as
-     * legacy feature, it is included in both.
-     */
-    val featureList: List<QuasselFeatureName>
+    val featureSet: FeatureSet,
   ) : HandshakeMessage()
 
   /**
@@ -60,21 +54,16 @@ sealed class HandshakeMessage {
      * If the core needs to be configured, this contains metadata about the
      * supported storage backends
      */
-    val backendInfo: QVariantList,
+    val backendInfo: List<BackendInfo>,
     /**
      * If the core needs to be configured, this contains metadata about the
      * supported authentication backends
      */
-    val authenticatorInfo: QVariantList,
-    /**
-     * Set of legacy features supported
-     */
-    val coreFeatures: LegacyFeatures,
+    val authenticatorInfo: List<BackendInfo>,
     /**
-     * List of supported features. If a feature can also be represented as
-     * legacy feature, it is included in both.
+     * Enabled features for this connection
      */
-    val featureList: List<QuasselFeatureName>
+    val featureSet: FeatureSet,
   ) : HandshakeMessage()
 
   /**
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SetupEntry.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SetupEntry.kt
new file mode 100644
index 0000000..6675aac
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SetupEntry.kt
@@ -0,0 +1,19 @@
+/*
+ * 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_,
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt
index 4829e45..5366f5c 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt
@@ -12,12 +12,15 @@ package de.justjanne.libquassel.protocol.serializers.handshake
 
 import de.justjanne.bitflags.of
 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.QuasselFeatureName
+import de.justjanne.libquassel.protocol.models.BackendInfoSerializer
 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.serializers.HandshakeSerializer
+import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
@@ -30,20 +33,45 @@ object ClientInitAckSerializer : HandshakeSerializer<HandshakeMessage.ClientInit
 
   override fun serialize(data: HandshakeMessage.ClientInitAck) = mapOf(
     "MsgType" to qVariant(type, QtType.QString),
-    "CoreFeatures" to qVariant(data.coreFeatures.toBits(), QtType.UInt),
-    "StorageBackends" to qVariant(data.backendInfo, QtType.QVariantList),
-    "Authenticator" to qVariant(data.authenticatorInfo, QtType.QVariantList),
+    "CoreFeatures" to qVariant(data.featureSet.legacyFeatures().toBits(), QtType.UInt),
+    "StorageBackends" to qVariant<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),
-    "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(
     coreConfigured = data["Configured"].into(),
-    backendInfo = data["StorageBackends"].into(emptyList()),
-    authenticatorInfo = data["Authenticators"].into(emptyList()),
-    coreFeatures = LegacyFeature.of(data["CoreFeatures"].into<UInt>()),
-    featureList = data["FeatureList"].into<QStringList>(emptyList())
-      .filterNotNull()
-      .map(::QuasselFeatureName),
+    backendInfo = data["StorageBackends"].into<QVariantList>()?.mapNotNull {
+      it.into<QVariantMap>()
+    }?.map(BackendInfoSerializer::deserialize).orEmpty(),
+    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()
+        .map(::QuasselFeatureName)
+    )
   )
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt
index 072879c..deaba2b 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt
@@ -12,9 +12,11 @@ package de.justjanne.libquassel.protocol.serializers.handshake
 
 import de.justjanne.bitflags.of
 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.QuasselFeatureName
 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.serializers.HandshakeSerializer
 import de.justjanne.libquassel.protocol.variant.QVariantMap
@@ -31,9 +33,9 @@ object ClientInitSerializer : HandshakeSerializer<HandshakeMessage.ClientInit> {
     "MsgType" to qVariant(type, QtType.QString),
     "ClientVersion" to qVariant(data.clientVersion, 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(
-      data.featureList.map(QuasselFeatureName::name),
+      data.featureSet.featureList().map(QuasselFeatureName::name),
       QtType.QStringList
     ),
   )
@@ -41,7 +43,11 @@ object ClientInitSerializer : HandshakeSerializer<HandshakeMessage.ClientInit> {
   override fun deserialize(data: QVariantMap) = HandshakeMessage.ClientInit(
     clientVersion = data["ClientVersion"].into(),
     buildDate = data["ClientDate"].into(),
-    clientFeatures = LegacyFeature.of(data["Features"].into<UInt>()),
-    featureList = data["FeatureList"].into(emptyList<String>()).map(::QuasselFeatureName),
+    featureSet = FeatureSet.build(
+      LegacyFeature.of(data["Features"].into<UInt>()),
+      data["FeatureList"].into<QStringList>(emptyList())
+        .filterNotNull()
+        .map(::QuasselFeatureName)
+    )
   )
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt
new file mode 100644
index 0000000..6ede523
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt
@@ -0,0 +1,27 @@
+/*
+ * 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))
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/triples.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/triples.kt
new file mode 100644
index 0000000..850300a
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/triples.kt
@@ -0,0 +1,29 @@
+/*
+ * 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))
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt
index ee2d3b5..b40f600 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt
@@ -10,6 +10,8 @@
 
 package de.justjanne.libquassel.protocol.variant
 
+import de.justjanne.libquassel.protocol.util.pairs
+
 /**
  * Simple alias for a generic QVariantList type
  */
@@ -19,6 +21,6 @@ typealias QVariantList = List<QVariant_>
  * Transform a QVariantList of interleaved keys and values into a QVariantMap
  */
 fun QVariantList.toVariantMap(): QVariantMap =
-  zipWithNext { key, value ->
+  pairs { key, value ->
     Pair(key.into(""), value)
   }.toMap()
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializerTest.kt
new file mode 100644
index 0000000..ccd7435
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitAckSerializerTest.kt
@@ -0,0 +1,150 @@
+/*
+ * 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)
+  )
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt
index 3af0e4b..13c974c 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt
@@ -18,9 +18,7 @@
  */
 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.LegacyFeature
 import de.justjanne.libquassel.protocol.models.HandshakeMessage
 import de.justjanne.libquassel.protocol.testutil.byteBufferOf
 import de.justjanne.libquassel.protocol.testutil.handshakeSerializerTest
@@ -32,8 +30,7 @@ class ClientInitSerializerTest {
     HandshakeMessage.ClientInit(
       clientVersion = "Quasseldroid test",
       buildDate = "Never",
-      clientFeatures = LegacyFeature.none(),
-      featureList = emptyList()
+      featureSet = FeatureSet.none()
     ),
     byteBufferOf(
       0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x0Cu,
@@ -71,8 +68,8 @@ class ClientInitSerializerTest {
       clientVersion = "Quasseldroid <a href=\"https://git.kuschku.de/justJanne/QuasselDroid-ng/commit/" +
         "b622ad63056b6054b06e09f8e1f1ef2b0c3aaf9a\">v1.3.3</a>",
       buildDate = "2020-04-27T22:21:17Z",
-      clientFeatures = FeatureSet.all().legacyFeatures(),
-      featureList = FeatureSet.all().featureList()
-    )
+      featureSet = FeatureSet.all()
+    ),
+    // byteBufferOf(0x00u)
   )
 }
-- 
GitLab