diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3812e21f01d6b4fd6a9bff691d0c7c3d027e3bce..b80cf777f65f2e542727c97e801d2d1903d6c2e8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,9 +37,6 @@ test:
     QUASSEL_CONTAINER: "quasselcore:4242"
     SSL_CERT_DATA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZIekNDQXdlZ0F3SUJBZ0lVRVYvUmY1RGNlUWhqdWwyeXFIRGEvT0RyNStZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0hqRWNNQm9HQTFVRUF3d1RjWFZoYzNObGJIUmxjM1F1YVc1MllXeHBaREFnRncweU1UQXlNRGd5TXpNNQpNalJhR0E4eU1USXhNREV4TlRJek16a3lORm93SGpFY01Cb0dBMVVFQXd3VGNYVmhjM05sYkhSbGMzUXVhVzUyCllXeHBaRENDQWlJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNMk9aVGFlNEJLenBwQjIKbkVGMmUyb0dMbDFZR2FDZnFhVmVRaktOUzNEWThmaklHOWxiNmhEekUxTW5lb0d0b3U3VGFmV3ZxaDJ1MVE4cAphUk5wY1crZnRUcWNVZWNHUHVNVFFISFNXalpKRG9jdkgydkc1eE00OHU1RHFMTlVUaHM3ZUVvUGlzN0FHcHVzCjd4bTh3bWpHb2tLWVpiNldkWEdMY09FUExiMlpTUUg0TVJIa0FNdE0xekYzL0wwYll6UkxpbDkrYVE2eTBQMy8KNGhSZlVpeTVaa0lpUFMzK2ZKUE9qL1phQUJ1a0x0ZmZXaWNiZUF2TkRrN0pwMkZ2b1ZmWFBYVTE0Q1hRYVYxNgpCaGZDYldIYW1VTTRkMFpXdTg1SjhKYm9uK0pSZm1JdmcrMHo0bmxaYUo2YTIzbmtPNGxqTDZJS2R3bEVVWWJNCnVaNGJpbUN4bWsyZzN0MjdEeERCWUFldWRDUVlRanZsQTdmWWY5YjJ0QmpkdlZ4N0JSeUZrMDlaK3pTV0tYVngKNjdpTDVuVVJZOGlPWStzSEY3dC9EL3ZiRHgxL25Ea1haUHhFbDFqWmxEbnUyeENrTThzWWdvY1hzZmpJYVczZgoxZGNHSjZscjUwZzY5TGdUQkJ1VDJEYjNMaTkxbkFyM3MrVjVVcmYxQ2NUTUZpcE5BZGVZM3FONzE3THpLc0pTCkZwakF0dlVKUWhQaW45SXMwQVprWUNyRHhJWVpKWWdRWmFlRENyWFJRd29TOHFJaFhXYmdIWFJQVVR3Z0d6bVMKb2c2MityZnduM0pEUkRLT0d1aVlJYzh1aEJqMG1PT1YvR0JlZ0ZnMWNza2FYK0VNdmR1cngyanlOYzZ5MUpPRwpXSkhaMmN0TVNIbGttNC92RzlsRUhvVzQzSGNQQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJRNHVLckltalV2CkMvVEpoYjAzSTNZSkVqZ2pyakFmQmdOVkhTTUVHREFXZ0JRNHVLckltalV2Qy9USmhiMDNJM1lKRWpnanJqQVAKQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNNYWhxWTFMVmRTbnk5b2M3QgpBR0VUNVkvc1RkQUxaTi9Hc2xZV2pZOWlUTng0a1h6NnUwNjU0b25EVEJIWjZjKzgrZG1aZFdGcmRQVHpKdUpYCmRCNEhxUXlZMnRMaFIzTjQ0RjZMUmt4OTdrbGJ2U3NjTUpyM1FLNDM0NEF0a3NiRXExQkdlRENtNVZlYVpaSlUKQStXU05YakpDMjJFRlI1UkxmdGRBclJxdUpWY3dDRFBXSTFsZi9pdi9pWEpQcHY4TXE1cUZwWTFBZ3VzaS9zdgpQTE82R1ZWMnFQQURvSTBOamdZQ2pKMlZWSlFlSGFzRkNDK2l6aWhtclRRVU1jNEcvSkY5Z0hNUE9MUm9mbTMyCnpNUThMSytpV2E5cCtWb1JiZG5zV0R3NEZ5OXMzNFZYdVQ1aWpBejZLYlE4djlhbFF2NlZmWXduWkNyeWx5S0gKSzN2V2c3ZzF0bHlNWlA3RXh6SGZ1NDI4bFZtWVpiZjFrN0dVQSsyUXU5cy9qUGZzendRemREbnI1ZE45akNVagpDSXFMWG9IWmV5SWMyQ3h1MUtNYVp2a1BWbjIvekpVYTNSTkxmY2wrMXBHT0N3YVVkNWxjR3RkenJrLzlsWndSCnpIaDludlFXUEFFMnIzNkU3Q3RPZ3kwdU1JZkZkTm5EYzFpWUtVMkk5MDc1aytibzd5NHp2TzlnUXgzZG1reFUKU0NtMnNLUXprU2NTQlB0Q0J0c2ZLZk40Y3NMNUZ5TGV2bDdDNWl6cFFxdENocUxadTlYWEROVlRFN2pkNDhXeQpWcklsQkdId0dra3NtWVVxQUxJODFidkJhT1oxR3l0QUlNYlJOUThZUkxJUHZqdUwvQ29yZnFyUjVrRUtYcXNkCmVadzN3NXFHQVNnNlhrMGQ5T3hLeWpIS2VRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
     SSL_KEY_DATA: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRE5qbVUybnVBU3M2YVEKZHB4QmRudHFCaTVkV0JtZ242bWxYa0l5alV0dzJQSDR5QnZaVytvUTh4TlRKM3FCcmFMdTAybjFyNm9kcnRVUApLV2tUYVhGdm43VTZuRkhuQmo3akUwQngwbG8yU1E2SEx4OXJ4dWNUT1BMdVE2aXpWRTRiTzNoS0Q0ck93QnFiCnJPOFp2TUpveHFKQ21HVytsblZ4aTNEaER5MjltVWtCK0RFUjVBRExUTmN4ZC95OUcyTTBTNHBmZm1rT3N0RDkKLytJVVgxSXN1V1pDSWowdC9ueVR6by8yV2dBYnBDN1gzMW9uRzNnTHpRNU95YWRoYjZGWDF6MTFOZUFsMEdsZAplZ1lYd20xaDJwbERPSGRHVnJ2T1NmQ1c2Si9pVVg1aUw0UHRNK0o1V1dpZW10dDU1RHVKWXkraUNuY0pSRkdHCnpMbWVHNHBnc1pwTm9ON2R1dzhRd1dBSHJuUWtHRUk3NVFPMzJIL1c5clFZM2IxY2V3VWNoWk5QV2ZzMGxpbDEKY2V1NGkrWjFFV1BJam1QckJ4ZTdmdy83Mnc4ZGY1dzVGMlQ4UkpkWTJaUTU3dHNRcERQTEdJS0hGN0g0eUdsdAozOVhYQmllcGErZElPdlM0RXdRYms5ZzI5eTR2ZFp3Szk3UGxlVkszOVFuRXpCWXFUUUhYbU42amU5ZXk4eXJDClVoYVl3TGIxQ1VJVDRwL1NMTkFHWkdBcXc4U0dHU1dJRUdXbmd3cTEwVU1LRXZLaUlWMW00QjEwVDFFOElCczUKa3FJT3R2cTM4Sjl5UTBReWpocm9tQ0hQTG9RWTlKampsZnhnWG9CWU5YTEpHbC9oREwzYnE4ZG84alhPc3RTVApobGlSMmRuTFRFaDVaSnVQN3h2WlJCNkZ1TngzRHdJREFRQUJBb0lDQUExeGFKRWY1c3VTVUN4V2RYV2FpQXV4CkI4czIvY1lSYXdqVGwwU3pGT0gyYml5MCtZMUhnNUZFTkZsVjFaOHhlZHhnbXlka0s1M3hWeUc4dFpCOWJ0dTcKK0NBekpQQVU2bnZ6UUYyeFFoRVd5Z3B4UEg3UjdUN0dsS3ljWkNZR04yMTBnRE5udk00MHBnalVVSGJBYjM1bQpyeW5ueVkralMxNzNuWlE2WitWa1p1L29DVjJBS2NVaDYxamkzZmFJR2Y3TGllc2cyMElJMDc0b3crSk5NWlNYCk0yYlQwbWgxb2pRUWFEM1dPUGVWenpKeSt2UmZ5WVFNRHdsOENxUkdwcWlWL1FEeld3dGVDK1gvR3ZMbTFqeDIKRFZ2bUQzYmNLVUFlZWN1RXZ5QVA4RkgvaFlNM3gzSGtOUUZhWTB3ZmJ2MVhMVUJOcXVkQ1BvcXdUTnRZTmI1cQo3L0lEUkphQVh2MDE2WEhkMzFBSFB0Ynp3bEx6dEZpVHljbm1ML2V4ZnhaREVYYUFVMjhDNVB1WXlMUU5xbTdzCms2QTlCdE5Zd2NmZmJzMlR5QXE2UmdPUXMyRVJXZEhoazhzeVBOd0JkZ2VYVFhTTHF1YlREODd5U2IvWWZMaFkKL1kvbzkxYTBONEFoSkpEZjVqSnhKZmt6RGZSd0lpam81bW5VU3BrRXRkNDh1UjE0c3RzeU9MdEZXUmwrb2NsTgpvOHJDandhdUhHN0FlRnB6MWhkaDJDUWUwdE1vaGYvVG51U3BEeG9rSlF3bVRJS2dYZ3Vic2xmSHRub1cwS0ZrCmpLaHdRcDJyMysxbW54T1MxbnFGVXRuUytHcTZ2WVkvbFQ3cTZuQ2pLS21sbDdjMW4xNFB0eCtVNklvQnJhcDEKcXQ4MWx0eG5UdllJb3JNR3VncGhBb0lCQVFELzQ1VGZTZDUrRytWYzJpOXJHazdYTjNhZ1drbVF0Sld0MTlORgpPN3NGZlBSM2JBOUpxSTNZQWw3ejRodnU0blRRSEVXTU5VeERwRGpnNS9DRW95YXI0V2N4TUFRODVKMHcyTGNNCklYU09JQ2JwV0haeDFUaUFqLzZpQWpnaGZ6WW1RUGNvUVhzNUdMbGdOOElMNitqSkdja2FjVWRKSkw4Ykc1VUgKb2JqWWc4WmMxRDNxUEVuTG9iTDhLdC9peFMrbHFzVncrRkRGMzVhbElVTUQwWkpyU1U5NG5ETnF0K0hDRTF4UApNRm1XWXN5NjhleTAzZ3hDMnZ3Myt0VERzMCs1MjFsLzB5dyttL0Y0cC85MkJIbmsvSnRhOGVnRi80bW9KbCtUCmo0QUhuWWlDcEgrNk8xS1FrdW04MkhrQkdiTUhrYjZpMGdNTXRMYkM1akh4R29aNUFvSUJBUUROcFRsWE9sZDEKOWt6b0lJNlpEVVFVRXVISWQ5QWRqQ243YmVraTJQWk9pcTMxajY3UWlTb0NJQUt0RmhyYjhQakp5YXhOWUthcwpOWGs2UDZNRDVnVkRRb2plQnYyNGNheUdHOTVGQ1VZdElWRDJIcm03L3NSMndrSlJ2QlJPbFlha1JDL3prUGtHCmR5RXRmaXZJVUJ6bkJNNE1JM2d4NzQxbEJWRnBwQWRxdzJxTjZKUU1vVVZBb0VQa2lFZzd3eStmUVVXejJPTXIKdloxeUlQeEpEaDJjN1BLSVVmN3g2SDA4VEhPWFZaK3NXem9GSFo0Q1ZzSGJzMjNaM0lSTHdUNFlEK1RkRCtpbgp6TWExbTE0ZTN4dHQ1cUxwRSt6ZVNCL1dVV1pLcFNsZ1VLaFJ5bGdkcWIvcjg1UklXN29hV2s3SUNmQ2E5WUwxCmplRHlMeVl1YmFmSEFvSUJBRGhiaFZSUVRxSnp2bVplMzRhMU9wd0g2U2FUL0JQVTBncUJ1RlJOUFhtTjljRy8KbVBaZUd5OXlCanVzbHY1Yi9lSS85OGxUaThKeUR0enArSDBkK2N4dFRtNzA1bG9LOTl2a1B4eDYyZExibmZaUwp0M09HeEhUOFFkYW9xbmdtTG1UcWRnVDF0dy95TkJITzlmdnVMMHpyVXZGeDlZTlVob3FQM3BqWnMzNXNOMm9HCmpNUmtGdFMwZmxrdmtETy92aWk4bmRPdHZReDNuQlF1YVRZVUdDMXM3Z1hnVVNxMTZSRDNkcU15UU9qd1JhcisKMFdWY2FsTG5MQm1nTXZBUE5BWDVHNU1kaldjVXJYQW5nQW9jSGtTaXBneTNycVJ6alh2dFI2dVdOVnA0QmJMUQpUQmxXSzI4UURFNjlWcGs1Y2NhL0FMK1hoWGhzN0x1c08xK2d1ZkVDZ2dFQWZHZ0xBMkVSRGhUZHU4UU9ZRHJ0ClIzT1EwYlRoMnk0eks1NzNYaFNCRlV6Q0puOHcwNGxYTjRmajlwQWIyWml5K1dnZTY0U2Y4Q2c5V1dhc0dLeXIKM0YvQTZ3aXhyMFpkaDVnT1pCZFRNL1FteFc0YkVNYjBWWi81ZlBiYUZoeFJJc2o1ZFZEcnhlU0YxcjZ3Zi9NdgpPUGJvSytHOVVnQkl1cWQzOC8rK1dQRTFZZm9rcm10VnVOMzdsS0o1aUdYeFJsZTNjakN3WllMRllBamlkdE9xClNJZnp4VkpOZUUwY2prRDE0TVIwMzFFbERYazRZTlBaWFM3ME1zczc0WlJiR3pWcVQrM1M0c2g0SWQrSEZnZ0UKMFB5bzYzWVpZdk9oQndlaGFXRDNZZ1FKZjhsNGV5RjVNS1hmdTlKNkNIMC9rYmFwcnlUOWY4M0FHdU01SnZkQgpld0tDQVFBb1ZOK0hTL2FmMk8wOSszVEV0Rnl2NG01b3pGQjZudGFDMTJkaHJib1Z3QXc4TVBMR1hhRXFzZlBWCkw5T1JNQTZsUkwrNE9JbzJGY002VGtsRC9yTUFIci9iOVZ0V291OEVSZWlPUjF6U2d0N29wQ1o2d0xoQm5wOVAKbXN6MXB1UC9PK1NSM2xyY1FPbUVUTUs3b2Zxb2V3ZW01cnowQ21UcmFYcXJlSHV6UlgrYy9uV1FxbUpEeU44bwpOZUhoRzVzSXRJaGVQcE5RK1FjUGV6VnlsKyt2TUhGd09POTJvK2kvejd6SDZ1Y1IrZzhPUjQxdlhqSVFFUDVuCjV3OU9HeU94VmZnRmhPNlErQ1VmSXMzSk1sd28vWGt3RnAyTkpvaE5XQVB6UEl5SjVWOVhwSmt1YVdKMTdjL1UKandmWU56Tmx2MXBEeVZHQWFJOG9NRFhSTWFaMwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg=="
-    CONFIG_FROM_ENVIRONMENT: "true"
-    DB_BACKEND: "SQLite"
-    AUTH_AUTHENTICATOR: "Database"
     SSL_REQUIRED: "true"
   script:
     - "./gradlew check -x connectedCheck coberturaTestReport --stacktrace"
diff --git a/libquassel/src/test/kotlin/info/quasseldroid/libquassel/EndToEndTest.kt b/libquassel/src/test/kotlin/info/quasseldroid/libquassel/EndToEndTest.kt
index 80c90cd9c16f73cbad53a9f474b609f755770e9f..e3f8988dcb7704dd6e0206ec8f2e686605e0b660 100644
--- a/libquassel/src/test/kotlin/info/quasseldroid/libquassel/EndToEndTest.kt
+++ b/libquassel/src/test/kotlin/info/quasseldroid/libquassel/EndToEndTest.kt
@@ -28,15 +28,18 @@ import info.quasseldroid.libquassel.testutil.TestX509TrustManager
 import info.quasseldroid.protocol.connection.*
 import info.quasseldroid.protocol.features.FeatureSet
 import info.quasseldroid.protocol.io.ChainedByteBuffer
-import info.quasseldroid.protocol.messages.handshake.ClientInit
+import info.quasseldroid.protocol.messages.handshake.*
 import info.quasseldroid.protocol.serializers.HandshakeSerializers
 import info.quasseldroid.protocol.serializers.handshake.ClientInitSerializer
+import info.quasseldroid.protocol.serializers.handshake.ClientLoginSerializer
+import info.quasseldroid.protocol.serializers.handshake.CoreSetupDataSerializer
 import info.quasseldroid.protocol.serializers.primitive.HandshakeMapSerializer
 import info.quasseldroid.protocol.serializers.primitive.IntSerializer
 import info.quasseldroid.protocol.variant.into
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.fail
 import org.junit.jupiter.api.Test
 import java.net.InetSocketAddress
 import java.nio.ByteBuffer
@@ -58,8 +61,11 @@ class EndToEndTest {
   private val sendBuffer = ChainedByteBuffer(direct = true)
   private val channel = CoroutineChannel()
 
+  private val username = "AzureDiamond"
+  private val password = "hunter2"
+
   @Test
-  fun testConnect() = runBlocking {
+  fun testConnect(): Unit = runBlocking {
     channel.connect(
       InetSocketAddress(
         quassel.address,
@@ -118,12 +124,107 @@ class EndToEndTest {
         connectionFeatureSet
       )
     }
+    println("Reading clientInit response")
+    read {
+      val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
+      val msgType: String = data["MsgType"].into("")
+      HandshakeSerializers[msgType]?.deserialize(data)
+        as? ClientInitAck
+        ?: fail("Could not deserialize message $data")
+    }
+    println("Writing invalid core init")
+    write {
+      HandshakeMapSerializer.serialize(
+        it,
+        CoreSetupDataSerializer.serialize(CoreSetupData(
+          adminUser = username,
+          adminPassword = password,
+          backend = "MongoDB",
+          setupData = emptyMap(),
+          authenticator = "OAuth2",
+          authSetupData = emptyMap(),
+        )),
+        connectionFeatureSet
+      )
+    }
+    println("Reading invalid clientInit response")
+    read {
+      val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
+      val msgType: String = data["MsgType"].into("")
+      assertEquals(
+        CoreSetupReject("Could not setup storage!"),
+        HandshakeSerializers[msgType]?.deserialize(data)
+      )
+    }
+    println("Writing valid core init")
+    write {
+      HandshakeMapSerializer.serialize(
+        it,
+        CoreSetupDataSerializer.serialize(CoreSetupData(
+          adminUser = username,
+          adminPassword = password,
+          backend = "SQLite",
+          setupData = emptyMap(),
+          authenticator = "Database",
+          authSetupData = emptyMap(),
+        )),
+        connectionFeatureSet
+      )
+    }
+    println("Reading valid clientInit response")
+    read {
+      val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
+      val msgType: String = data["MsgType"].into("")
+      HandshakeSerializers[msgType]?.deserialize(data)
+        as? CoreSetupAck
+        ?: fail("Could not deserialize message $data")
+    }
+    println("Writing invalid clientLogin")
+    write {
+      HandshakeMapSerializer.serialize(
+        it,
+        ClientLoginSerializer.serialize(ClientLogin(
+          user = "acidburn",
+          password = "ineverweardresses"
+        )),
+        connectionFeatureSet
+      )
+    }
+    println("Reading invalid clientLogin response")
+    read {
+      val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
+      val msgType: String = data["MsgType"].into("")
+      assertEquals(
+        ClientLoginReject("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database."),
+        HandshakeSerializers[msgType]?.deserialize(data)
+      )
+    }
+    println("Writing valid clientLogin")
+    write {
+      HandshakeMapSerializer.serialize(
+        it,
+        ClientLoginSerializer.serialize(ClientLogin(
+          user = username,
+          password = password
+        )),
+        connectionFeatureSet
+      )
+    }
+    println("Reading valid clientLogin response")
+    read {
+      val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
+      val msgType: String = data["MsgType"].into("")
+      HandshakeSerializers[msgType]?.deserialize(data)
+        as? ClientLoginAck
+        ?: fail("Could not deserialize message $data")
+    }
+    println("Reading valid session init")
     read {
       val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
-      println(data)
       val msgType: String = data["MsgType"].into("")
-      val message: Any? = HandshakeSerializers[msgType]?.deserialize(data)
-      println(message)
+      HandshakeSerializers[msgType]?.deserialize(data)
+        as? SessionInit
+        ?: fail("Could not deserialize message $data")
     }
   }
 
diff --git a/libquassel/src/test/kotlin/info/quasseldroid/libquassel/testutil/QuasselCoreContainer.kt b/libquassel/src/test/kotlin/info/quasseldroid/libquassel/testutil/QuasselCoreContainer.kt
index 8cd3afe409617bc866298da8f507b03591dce646..2e879a871ea91069ca32cf3ab6b3411fda8e0298 100644
--- a/libquassel/src/test/kotlin/info/quasseldroid/libquassel/testutil/QuasselCoreContainer.kt
+++ b/libquassel/src/test/kotlin/info/quasseldroid/libquassel/testutil/QuasselCoreContainer.kt
@@ -40,9 +40,6 @@ class QuasselCoreContainer : GenericContainer<QuasselCoreContainer>(
       "/quasseltest.key",
       BindMode.READ_WRITE)
     withEnv("SSL_KEY_FILE", "/quasseltest.key")
-    withEnv("CONFIG_FROM_ENVIRONMENT", "true")
-    withEnv("DB_BACKEND", "SQLite")
-    withEnv("AUTH_AUTHENTICATOR", "Database")
     withEnv("SSL_REQUIRED", "true")
   }
 
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/HandshakeSerializers.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/HandshakeSerializers.kt
index 4cd8fcbff88875854da4f90a391ee2f8440546a9..1c3188866040dab08310d17e515458a0e646cbae 100644
--- a/protocol/src/main/java/info/quasseldroid/protocol/serializers/HandshakeSerializers.kt
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/HandshakeSerializers.kt
@@ -19,10 +19,7 @@
 
 package info.quasseldroid.protocol.serializers
 
-import info.quasseldroid.protocol.serializers.handshake.ClientInitAckSerializer
-import info.quasseldroid.protocol.serializers.handshake.ClientInitRejectSerializer
-import info.quasseldroid.protocol.serializers.handshake.ClientInitSerializer
-import info.quasseldroid.protocol.serializers.handshake.HandshakeSerializer
+import info.quasseldroid.protocol.serializers.handshake.*
 import info.quasseldroid.protocol.serializers.primitive.*
 import java.util.*
 
@@ -31,6 +28,16 @@ object HandshakeSerializers {
     ClientInitSerializer,
     ClientInitAckSerializer,
     ClientInitRejectSerializer,
+
+    CoreSetupDataSerializer,
+    CoreSetupAckSerializer,
+    CoreSetupRejectSerializer,
+
+    ClientLoginSerializer,
+    ClientLoginAckSerializer,
+    ClientLoginRejectSerializer,
+
+    SessionInitSerializer,
   ).associateBy(HandshakeSerializer<*>::type)
 
   operator fun get(type: String) = serializers[type]
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitAckSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitAckSerializer.kt
index c44f4cc3a863c99e44d3e5ed3f1b4c50364d7954..63ed22bf95813567d2d535102ed8967addf55ba9 100644
--- a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitAckSerializer.kt
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitAckSerializer.kt
@@ -31,6 +31,7 @@ object ClientInitAckSerializer : HandshakeSerializer<ClientInitAck> {
   override val javaType: Class<out ClientInitAck> = ClientInitAck::class.java
 
   override fun serialize(data: 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),
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitRejectSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitRejectSerializer.kt
index 249bf58543aaf2da0abe0e844a1dc13dc506a601..6ec646272bb1204bc7a996f44c902e6e616af679 100644
--- a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitRejectSerializer.kt
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitRejectSerializer.kt
@@ -30,6 +30,7 @@ object ClientInitRejectSerializer : HandshakeSerializer<ClientInitReject> {
   override val javaType: Class<out ClientInitReject> = ClientInitReject::class.java
 
   override fun serialize(data: ClientInitReject) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
     "Error" to qVariant(data.errorString, QtType.QString)
   )
 
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitSerializer.kt
index ee1321262f1210749fbccae7ce9670d7165d2cf7..887c0c796d30713a9857b1860252c042a4c5a155 100644
--- a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitSerializer.kt
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientInitSerializer.kt
@@ -44,12 +44,10 @@ object ClientInitSerializer : HandshakeSerializer<ClientInit> {
     ),
   )
 
-  override fun deserialize(data: QVariantMap): ClientInit {
-    return ClientInit(
-      clientVersion = data["ClientVersion"].into(),
-      buildDate = data["ClientDate"].into(),
-      clientFeatures = LegacyFeature.of(data["Features"].into<UInt>()),
-      featureList = data["FeatureList"].into(emptyList<String>()).map(::QuasselFeatureName),
-    )
-  }
+  override fun deserialize(data: QVariantMap) = ClientInit(
+    clientVersion = data["ClientVersion"].into(),
+    buildDate = data["ClientDate"].into(),
+    clientFeatures = LegacyFeature.of(data["Features"].into<UInt>()),
+    featureList = data["FeatureList"].into(emptyList<String>()).map(::QuasselFeatureName),
+  )
 }
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginAckSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginAckSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bce204c76235ea528aa6184e7b89d15cdaf2bb8f
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginAckSerializer.kt
@@ -0,0 +1,39 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import de.justjanne.bitflags.of
+import de.justjanne.bitflags.toBits
+import info.quasseldroid.protocol.features.LegacyFeature
+import info.quasseldroid.protocol.features.QuasselFeatureName
+import info.quasseldroid.protocol.messages.handshake.ClientInitAck
+import info.quasseldroid.protocol.messages.handshake.ClientLoginAck
+import info.quasseldroid.protocol.variant.*
+
+object ClientLoginAckSerializer : HandshakeSerializer<ClientLoginAck> {
+  override val type: String = "ClientLoginAck"
+  override val javaType: Class<out ClientLoginAck> = ClientLoginAck::class.java
+
+  override fun serialize(data: ClientLoginAck) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
+  )
+
+  override fun deserialize(data: QVariantMap) = ClientLoginAck
+}
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginRejectSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginRejectSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1be13b9fcdc711f8a7321fdf6a7c9992cff076e6
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginRejectSerializer.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import info.quasseldroid.protocol.messages.handshake.ClientLoginReject
+import info.quasseldroid.protocol.variant.QVariantMap
+import info.quasseldroid.protocol.variant.QtType
+import info.quasseldroid.protocol.variant.into
+import info.quasseldroid.protocol.variant.qVariant
+
+object ClientLoginRejectSerializer : HandshakeSerializer<ClientLoginReject> {
+  override val type: String = "ClientLoginReject"
+  override val javaType: Class<out ClientLoginReject> = ClientLoginReject::class.java
+
+  override fun serialize(data: ClientLoginReject) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
+    "Error" to qVariant(data.errorString, QtType.QString)
+  )
+
+  override fun deserialize(data: QVariantMap) = ClientLoginReject(
+    errorString = data["Error"].into()
+  )
+}
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0756629ac3806edd8fe60a78b5d7ab79e7b85eb4
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/ClientLoginSerializer.kt
@@ -0,0 +1,42 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import info.quasseldroid.protocol.messages.handshake.ClientLogin
+import info.quasseldroid.protocol.variant.QVariantMap
+import info.quasseldroid.protocol.variant.QtType
+import info.quasseldroid.protocol.variant.into
+import info.quasseldroid.protocol.variant.qVariant
+
+object ClientLoginSerializer : HandshakeSerializer<ClientLogin> {
+  override val type: String = "ClientLogin"
+  override val javaType: Class<out ClientLogin> = ClientLogin::class.java
+
+  override fun serialize(data: ClientLogin) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
+    "User" to qVariant(data.user, QtType.QString),
+    "Password" to qVariant(data.password, QtType.QString)
+  )
+
+  override fun deserialize(data: QVariantMap) = ClientLogin(
+    user = data["User"].into(),
+    password = data["Password"].into(),
+  )
+}
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupAckSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupAckSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0cad1097d91e0c52337bf2ff2b6e4943ebf85007
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupAckSerializer.kt
@@ -0,0 +1,36 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import info.quasseldroid.protocol.messages.handshake.CoreSetupAck
+import info.quasseldroid.protocol.variant.QVariantMap
+import info.quasseldroid.protocol.variant.QtType
+import info.quasseldroid.protocol.variant.qVariant
+
+object CoreSetupAckSerializer : HandshakeSerializer<CoreSetupAck> {
+  override val type: String = "CoreSetupAck"
+  override val javaType: Class<out CoreSetupAck> = CoreSetupAck::class.java
+
+  override fun serialize(data: CoreSetupAck) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString)
+  )
+
+  override fun deserialize(data: QVariantMap) = CoreSetupAck
+}
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupDataSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupDataSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e9dd0a8fd94146645c12c8c756a97903389e826d
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupDataSerializer.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import info.quasseldroid.protocol.messages.handshake.CoreSetupData
+import info.quasseldroid.protocol.variant.QVariantMap
+import info.quasseldroid.protocol.variant.QtType
+import info.quasseldroid.protocol.variant.into
+import info.quasseldroid.protocol.variant.qVariant
+
+object CoreSetupDataSerializer : HandshakeSerializer<CoreSetupData> {
+  override val type: String = "CoreSetupData"
+  override val javaType: Class<out CoreSetupData> = CoreSetupData::class.java
+
+  override fun serialize(data: CoreSetupData) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
+    "SetupData" to qVariant(mapOf(
+      "AdminUser" to qVariant(data.adminUser, QtType.QString),
+      "AdminPasswd" to qVariant(data.adminPassword, QtType.QString),
+      "Backend" to qVariant(data.backend, QtType.QString),
+      "ConnectionProperties" to qVariant(data.setupData, QtType.QVariantMap),
+      "Authenticator" to qVariant(data.authenticator, QtType.QString),
+      "AuthProperties" to qVariant(data.authSetupData, QtType.QVariantMap),
+    ), QtType.QVariantMap)
+  )
+
+  override fun deserialize(data: QVariantMap) =
+    data["SetupData"].into<QVariantMap>().let {
+      CoreSetupData(
+        adminUser = it?.get("AdminUser").into(),
+        adminPassword = it?.get("AdminPasswd").into(),
+        backend = it?.get("Backend").into(),
+        setupData = it?.get("ConnectionProperties").into(),
+        authenticator = it?.get("Authenticator").into(),
+        authSetupData = it?.get("AuthProperties").into()
+      )
+    }
+}
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupRejectSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupRejectSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a350a6c77ca390270ab18ed648d8428558734047
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/CoreSetupRejectSerializer.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import info.quasseldroid.protocol.messages.handshake.ClientInitReject
+import info.quasseldroid.protocol.messages.handshake.CoreSetupReject
+import info.quasseldroid.protocol.variant.QVariantMap
+import info.quasseldroid.protocol.variant.QtType
+import info.quasseldroid.protocol.variant.into
+import info.quasseldroid.protocol.variant.qVariant
+
+object CoreSetupRejectSerializer : HandshakeSerializer<CoreSetupReject> {
+  override val type: String = "CoreSetupReject"
+  override val javaType: Class<out CoreSetupReject> = CoreSetupReject::class.java
+
+  override fun serialize(data: CoreSetupReject) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
+    "Error" to qVariant(data.errorString, QtType.QString)
+  )
+
+  override fun deserialize(data: QVariantMap) = CoreSetupReject(
+    errorString = data["Error"].into()
+  )
+}
diff --git a/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/SessionInitSerializer.kt b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/SessionInitSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dbc49f1e57dfa84fd01c927c02defed7863fd7dd
--- /dev/null
+++ b/protocol/src/main/java/info/quasseldroid/protocol/serializers/handshake/SessionInitSerializer.kt
@@ -0,0 +1,48 @@
+/*
+ * 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 info.quasseldroid.protocol.serializers.handshake
+
+import info.quasseldroid.protocol.messages.handshake.SessionInit
+import info.quasseldroid.protocol.variant.QVariantMap
+import info.quasseldroid.protocol.variant.QtType
+import info.quasseldroid.protocol.variant.into
+import info.quasseldroid.protocol.variant.qVariant
+
+object SessionInitSerializer : HandshakeSerializer<SessionInit> {
+  override val type: String = "SessionInit"
+  override val javaType: Class<out SessionInit> = SessionInit::class.java
+
+  override fun serialize(data: SessionInit) = mapOf(
+    "MsgType" to qVariant(type, QtType.QString),
+    "SessionState" to qVariant(mapOf(
+      "BufferInfos" to qVariant(data.bufferInfos, QtType.QVariantList),
+      "NetworkIds" to qVariant(data.networkIds, QtType.QVariantList),
+      "Identities" to qVariant(data.identities, QtType.QVariantList),
+    ), QtType.QVariantMap)
+  )
+
+  override fun deserialize(data: QVariantMap) = data["SessionState"].into<QVariantMap>().let {
+    SessionInit(
+      bufferInfos = it?.get("BufferInfos").into(),
+      networkIds = it?.get("NetworkIds").into(),
+      identities = it?.get("Identities").into(),
+    )
+  }
+}