diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccIpDetectionMode.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccIpDetectionMode.kt
index ce8224fea6f7cd104208f3b0648044ee255d467c..04234b65491145fc41d40a15cc8c73713ef627a7 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccIpDetectionMode.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccIpDetectionMode.kt
@@ -35,7 +35,8 @@ enum class DccIpDetectionMode(
   Manual(0x01u);
 
   companion object {
-    private val values = values().associateBy(DccIpDetectionMode::value)
+    private val values = enumValues<DccIpDetectionMode>()
+      .associateBy(DccIpDetectionMode::value)
 
     /**
      * Obtain from underlying representation
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccPortSelectionMode.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccPortSelectionMode.kt
index 32ad22642fa65d692ff1e58738fe5e7319affee0..e3a5f38b459e2fff9f581376d93d39ee697d7522 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccPortSelectionMode.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/DccPortSelectionMode.kt
@@ -35,7 +35,8 @@ enum class DccPortSelectionMode(
   Manual(0x01u);
 
   companion object {
-    private val values = values().associateBy(DccPortSelectionMode::value)
+    private val values = enumValues<DccPortSelectionMode>()
+      .associateBy(DccPortSelectionMode::value)
 
     /**
      * Obtain from underlying representation
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/NetworkLayerProtocol.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/NetworkLayerProtocol.kt
index 1e54f7b0a905202f29152d108f7eb5206250d424..0588c5b685dea74e7db3eecfb63620476286feb0 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/NetworkLayerProtocol.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/NetworkLayerProtocol.kt
@@ -53,7 +53,8 @@ enum class NetworkLayerProtocol(
   UnknownNetworkLayerProtocol(0xFFu);
 
   companion object {
-    private val values = values().associateBy(NetworkLayerProtocol::value)
+    private val values = enumValues<NetworkLayerProtocol>()
+      .associateBy(NetworkLayerProtocol::value)
 
     /**
      * Obtain from underlying representation
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SignalProxyMessage.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SignalProxyMessage.kt
index dab50bc213512d3142287740539ea2a030a2c507..25930f827e46ed4333fac3d055ccb4b4191b12af 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SignalProxyMessage.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/SignalProxyMessage.kt
@@ -4,11 +4,29 @@ import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import org.threeten.bp.Instant
 
+/**
+ * Model classes for messages exchanged with the signal proxy
+ */
 sealed class SignalProxyMessage {
+  /**
+   * Sync message, representing an RPC call on a specific object
+   */
   data class Sync(
+    /**
+     * Type of the specified object
+     */
     val className: String,
+    /**
+     * Name/ID of the specified object
+     */
     val objectName: String,
+    /**
+     * Name of the function/slot to be called on the object
+     */
     val slotName: String,
+    /**
+     * Parameters to the function call
+     */
     val params: QVariantList
   ) : SignalProxyMessage() {
     override fun toString(): String {
@@ -16,8 +34,17 @@ sealed class SignalProxyMessage {
     }
   }
 
-  data class RpcCall(
+  /**
+   * RPC message, representing an RPC call on the global client object
+   */
+  data class Rpc(
+    /**
+     * Name of the function/slot to be called on the object
+     */
     val slotName: String,
+    /**
+     * Parameters to the function call
+     */
     val params: QVariantList
   ) : SignalProxyMessage() {
     override fun toString(): String {
@@ -25,8 +52,18 @@ sealed class SignalProxyMessage {
     }
   }
 
+  /**
+   * Init request message, representing a request to get the current state of a
+   * specified object
+   */
   data class InitRequest(
+    /**
+     * Type of the specified object
+     */
     val className: String,
+    /**
+     * Name/ID of the specified object
+     */
     val objectName: String
   ) : SignalProxyMessage() {
     override fun toString(): String {
@@ -34,9 +71,22 @@ sealed class SignalProxyMessage {
     }
   }
 
+  /**
+   * Init data message, representing a message with the current state of a
+   * specified object
+   */
   data class InitData(
+    /**
+     * Type of the specified object
+     */
     val className: String,
+    /**
+     * Name/ID of the specified object
+     */
     val objectName: String,
+    /**
+     * Current state of the specified object
+     */
     val initData: QVariantMap
   ) : SignalProxyMessage() {
     override fun toString(): String {
@@ -44,7 +94,13 @@ sealed class SignalProxyMessage {
     }
   }
 
+  /**
+   * Heart beat message to keep the connection alive and measure latency
+   */
   data class HeartBeat(
+    /**
+     * Local timestamp of the moment the message was sent
+     */
     val timestamp: Instant
   ) : SignalProxyMessage() {
     override fun toString(): String {
@@ -52,7 +108,13 @@ sealed class SignalProxyMessage {
     }
   }
 
+  /**
+   * Heart beat reply message as response to [HeartBeat]
+   */
   data class HeartBeatReply(
+    /**
+     * Local timestamp of the moment the original heartbeat was sent
+     */
     val timestamp: Instant
   ) : SignalProxyMessage() {
     override fun toString(): String {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/TimeSpec.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/TimeSpec.kt
index 7627c2be9c4d9cb68998ff12cb938703a7c73c71..b738b66e502a7e6ae10d444074485786505a4dde 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/TimeSpec.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/TimeSpec.kt
@@ -58,7 +58,8 @@ enum class TimeSpec(
   OffsetFromUTC(3);
 
   companion object {
-    private val map = values().associateBy(TimeSpec::value)
+    private val map = enumValues<TimeSpec>()
+      .associateBy(TimeSpec::value)
 
     /**
      * Obtain a zone specification by its underlying representation
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferActivity.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferActivity.kt
index 4737c747b77d8f825a515c0a7b4e1ab95b0e085e..286e2214bac825eaa64ba507c77106d76c7c317a 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferActivity.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferActivity.kt
@@ -45,7 +45,8 @@ enum class BufferActivity(
   Highlight(0x04u);
 
   companion object : Flags<UInt, BufferActivity> {
-    private val values = values().associateBy(BufferActivity::value)
+    private val values = enumValues<BufferActivity>()
+      .associateBy(BufferActivity::value)
     override val all: BufferActivities = values.values.toEnumSet()
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferType.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferType.kt
index 1cc549b06ce372032bbd177a6fab05aa146d2858..40e2300ff9ecad3142e99ce7588c2dc6b5ea6d2e 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferType.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/BufferType.kt
@@ -50,7 +50,8 @@ enum class BufferType(
   Group(0x08u);
 
   companion object : Flags<UShort, BufferType> {
-    private val values = values().associateBy(BufferType::value)
+    private val values = enumValues<BufferType>()
+      .associateBy(BufferType::value)
     override val all: BufferTypes = values.values.toEnumSet()
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageFlag.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageFlag.kt
index f2c897ae4da4103c3d30cc166da49c417c66d682..96768399bea13577f9ce210cdb7de1a63efe82ff 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageFlag.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageFlag.kt
@@ -65,7 +65,8 @@ enum class MessageFlag(
   Backlog(0x80u);
 
   companion object : Flags<UInt, MessageFlag> {
-    private val values = values().associateBy(MessageFlag::value)
+    private val values = enumValues<MessageFlag>()
+      .associateBy(MessageFlag::value)
     override val all: MessageFlags = values.values.toEnumSet()
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageType.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageType.kt
index 653bc953679a98c9129e87fef02c9412d00adc99..765cf2aeba88befdd65917c389d3d3996828b608 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageType.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/flags/MessageType.kt
@@ -125,7 +125,8 @@ enum class MessageType(
   Markerline(0x40000u);
 
   companion object : Flags<UInt, MessageType> {
-    private val values = values().associateBy(MessageType::value)
+    private val values = enumValues<MessageType>()
+      .associateBy(MessageType::value)
     override val all: MessageTypes = values.values.toEnumSet()
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QtType.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QtType.kt
index 793adeafcb35365a9968f382446e2603c79b1b67..0ece62be3fed882879055d70afe04467499e52df 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QtType.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QtType.kt
@@ -204,7 +204,8 @@ enum class QtType(
   }
 
   companion object {
-    private val values = values().associateBy(QtType::id)
+    private val values = enumValues<QtType>()
+      .associateBy(QtType::id)
 
     /**
      * Obtain a QtType by its underlying representation
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QuasselType.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QuasselType.kt
index 40fc1089add2c75d6d94397f3d08dd332e9aecb3..9e6d03f2e2ef82231557d41bc106e846a058d465 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QuasselType.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/types/QuasselType.kt
@@ -151,7 +151,8 @@ enum class QuasselType(
   }
 
   companion object {
-    private val values = values().associateBy(QuasselType::typeName)
+    private val values = enumValues<QuasselType>()
+      .associateBy(QuasselType::typeName)
 
     /**
      * Obtain a QtType by its standardized name
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/HandshakeSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/HandshakeSerializer.kt
index 357bc67bc04acfa1de21c3432776d143c9dfd6c1..2890ffa22b7fec62cb3574cfe33413f017661579 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/HandshakeSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/HandshakeSerializer.kt
@@ -19,12 +19,13 @@
 
 package de.justjanne.libquassel.protocol.serializers
 
+import de.justjanne.libquassel.protocol.models.HandshakeMessage
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 
 /**
  * High-level serializer for handshake messages.
  */
-interface HandshakeSerializer<T> {
+interface HandshakeSerializer<T : HandshakeMessage> {
   /**
    * The underlying handshake message type this serializer can (de-)serialize.
    * Used for type-safe serializer autodiscovery.
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/NoSerializerForTypeException.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/NoSerializerForTypeException.kt
index 7d1f6705894cced48dc83132acdc9ebe7696ee6b..996e5072594483ca222cf2b98b385768c1b16270 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/NoSerializerForTypeException.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/NoSerializerForTypeException.kt
@@ -83,11 +83,11 @@ sealed class NoSerializerForTypeException : Exception() {
    * Exception describing a missing serializer condition for a signal proxy type
    */
   data class SignalProxy(
-    private val type: String,
+    private val type: Int,
     private val javaType: Class<*>? = null
   ) : NoSerializerForTypeException() {
     override fun toString(): String {
-      return "NoSerializerForTypeException.Handshake(type='$type', javaType=$javaType)"
+      return "NoSerializerForTypeException.SignalProxy(type='$type', javaType=$javaType)"
     }
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxyMessageSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxyMessageSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4ea7d9039e607836bc4bb308d346e3d0aa593572
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxyMessageSerializer.kt
@@ -0,0 +1,64 @@
+package de.justjanne.libquassel.protocol.serializers
+
+import de.justjanne.libquassel.protocol.features.FeatureSet
+import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.serializers.qt.QVariantListSerializer
+import de.justjanne.libquassel.protocol.serializers.signalproxy.HeartBeatReplySerializer
+import de.justjanne.libquassel.protocol.serializers.signalproxy.HeartBeatSerializer
+import de.justjanne.libquassel.protocol.serializers.signalproxy.InitDataSerializer
+import de.justjanne.libquassel.protocol.serializers.signalproxy.InitRequestSerializer
+import de.justjanne.libquassel.protocol.serializers.signalproxy.RpcSerializer
+import de.justjanne.libquassel.protocol.serializers.signalproxy.SyncSerializer
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import java.nio.ByteBuffer
+
+/**
+ * Singleton object containing all serializers for handshake message types
+ */
+object SignalProxyMessageSerializer : PrimitiveSerializer<SignalProxyMessage> {
+  override val javaType: Class<out SignalProxyMessage> = SignalProxyMessage::class.java
+
+  private fun serializeToList(data: SignalProxyMessage): QVariantList =
+    when (data) {
+      is SignalProxyMessage.Sync ->
+        SyncSerializer.serialize(data)
+      is SignalProxyMessage.Rpc ->
+        RpcSerializer.serialize(data)
+      is SignalProxyMessage.InitRequest ->
+        InitRequestSerializer.serialize(data)
+      is SignalProxyMessage.InitData ->
+        InitDataSerializer.serialize(data)
+      is SignalProxyMessage.HeartBeat ->
+        HeartBeatSerializer.serialize(data)
+      is SignalProxyMessage.HeartBeatReply ->
+        HeartBeatReplySerializer.serialize(data)
+    }
+
+  private fun deserializeFromList(data: QVariantList): SignalProxyMessage =
+    when (val type = data.firstOrNull().into<Int>(0)) {
+      SyncSerializer.type ->
+        SyncSerializer.deserialize(data)
+      RpcSerializer.type ->
+        RpcSerializer.deserialize(data)
+      InitRequestSerializer.type ->
+        InitRequestSerializer.deserialize(data)
+      InitDataSerializer.type ->
+        InitDataSerializer.deserialize(data)
+      HeartBeatSerializer.type ->
+        HeartBeatSerializer.deserialize(data)
+      HeartBeatReplySerializer.type ->
+        HeartBeatReplySerializer.deserialize(data)
+      else ->
+        throw NoSerializerForTypeException.SignalProxy(type)
+    }
+
+  override fun serialize(buffer: ChainedByteBuffer, data: SignalProxyMessage, featureSet: FeatureSet) {
+    QVariantListSerializer.serialize(buffer, serializeToList(data), featureSet)
+  }
+
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): SignalProxyMessage {
+    return deserializeFromList(QVariantListSerializer.deserialize(buffer, featureSet))
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxySerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxySerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..595ca30b263ef56aafee999709a594a3f7bd6568
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxySerializer.kt
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.variant.QVariantList
+
+/**
+ * High-level serializer for signal proxy messages.
+ */
+interface SignalProxySerializer<T : SignalProxyMessage> {
+  /**
+   * The underlying signal proxy message type this serializer can (de-)serialize.
+   * Used for type-safe serializer autodiscovery.
+   */
+  val type: Int
+
+  /**
+   * Serialize a signal proxy message into a [QVariantList] (for further serialization)
+   */
+  fun serialize(data: T): QVariantList
+
+  /**
+   * Deserialize a signal proxy message from a [QVariantList]
+   */
+  fun deserialize(data: QVariantList): T
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/StringSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/StringSerializer.kt
index dbf7c7bd9140483f18015c49e47fba6222835c62..59d4f7c130073c0e51d774938d9686aa16e44694 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/StringSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/StringSerializer.kt
@@ -86,7 +86,10 @@ abstract class StringSerializer(
   /**
    * Deserialize a string from a given byte slice
    */
-  fun deserializeRaw(data: ByteBuffer): String {
+  fun deserializeRaw(data: ByteBuffer?): String {
+    if (data == null) {
+      return ""
+    }
     if (nullLimited) {
       data.limit(removeNullBytes(data.limit()))
     }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatReplySerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatReplySerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e31ba6e980dd0cc0e8cf67b8394d8d17f740fe79
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatReplySerializer.kt
@@ -0,0 +1,22 @@
+package de.justjanne.libquassel.protocol.serializers.signalproxy
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import org.threeten.bp.Instant
+
+object HeartBeatReplySerializer : SignalProxySerializer<SignalProxyMessage.HeartBeatReply> {
+  override val type: Int = 6
+
+  override fun serialize(data: SignalProxyMessage.HeartBeatReply) = listOf(
+    qVariant(type, QtType.Int),
+    qVariant(data.timestamp, QtType.QDateTime)
+  )
+
+  override fun deserialize(data: QVariantList) = SignalProxyMessage.HeartBeatReply(
+    data[1].into(Instant.EPOCH)
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..efbc8a452d76027a3da9512a14ac6bf3f99e03e4
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt
@@ -0,0 +1,22 @@
+package de.justjanne.libquassel.protocol.serializers.signalproxy
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import org.threeten.bp.Instant
+
+object HeartBeatSerializer : SignalProxySerializer<SignalProxyMessage.HeartBeat> {
+  override val type: Int = 5
+
+  override fun serialize(data: SignalProxyMessage.HeartBeat) = listOf(
+    qVariant(type, QtType.Int),
+    qVariant(data.timestamp, QtType.QDateTime)
+  )
+
+  override fun deserialize(data: QVariantList) = SignalProxyMessage.HeartBeat(
+    data[1].into(Instant.EPOCH)
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e913014b7a4123d59756e411e6b37e21d9abb7e7
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt
@@ -0,0 +1,28 @@
+package de.justjanne.libquassel.protocol.serializers.signalproxy
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import de.justjanne.libquassel.protocol.variant.toVariantList
+import de.justjanne.libquassel.protocol.variant.toVariantMap
+import java.nio.ByteBuffer
+
+object InitDataSerializer : SignalProxySerializer<SignalProxyMessage.InitData> {
+  override val type: Int = 4
+
+  override fun serialize(data: SignalProxyMessage.InitData) = listOf(
+    qVariant(type, QtType.Int),
+    qVariant(StringSerializerUtf8.serializeRaw(data.className), QtType.QByteArray),
+    qVariant(StringSerializerUtf8.serializeRaw(data.objectName), QtType.QByteArray),
+  ) + data.initData.toVariantList()
+
+  override fun deserialize(data: QVariantList) = SignalProxyMessage.InitData(
+    StringSerializerUtf8.deserializeRaw(data[1].into<ByteBuffer>()),
+    StringSerializerUtf8.deserializeRaw(data[2].into<ByteBuffer>()),
+    data.drop(3).toVariantMap()
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitRequestSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitRequestSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a082f18054feae0c8fa6f6f55c26a4bcb09144b9
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitRequestSerializer.kt
@@ -0,0 +1,25 @@
+package de.justjanne.libquassel.protocol.serializers.signalproxy
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import java.nio.ByteBuffer
+
+object InitRequestSerializer : SignalProxySerializer<SignalProxyMessage.InitRequest> {
+  override val type: Int = 3
+
+  override fun serialize(data: SignalProxyMessage.InitRequest) = listOf(
+    qVariant(InitDataSerializer.type, QtType.Int),
+    qVariant(StringSerializerUtf8.serializeRaw(data.className), QtType.QByteArray),
+    qVariant(StringSerializerUtf8.serializeRaw(data.objectName), QtType.QByteArray),
+  )
+
+  override fun deserialize(data: QVariantList) = SignalProxyMessage.InitRequest(
+    StringSerializerUtf8.deserializeRaw(data[1].into<ByteBuffer>()),
+    StringSerializerUtf8.deserializeRaw(data[2].into<ByteBuffer>())
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/RpcSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/RpcSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0b72810b8be4051d334f15eb05c201360a433675
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/RpcSerializer.kt
@@ -0,0 +1,24 @@
+package de.justjanne.libquassel.protocol.serializers.signalproxy
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import java.nio.ByteBuffer
+
+object RpcSerializer : SignalProxySerializer<SignalProxyMessage.Rpc> {
+  override val type: Int = 2
+
+  override fun serialize(data: SignalProxyMessage.Rpc) = listOf(
+    qVariant(SyncSerializer.type, QtType.Int),
+    qVariant(StringSerializerUtf8.serializeRaw(data.slotName), QtType.QByteArray)
+  ) + data.params
+
+  override fun deserialize(data: QVariantList) = SignalProxyMessage.Rpc(
+    StringSerializerUtf8.deserializeRaw(data[1].into<ByteBuffer>()),
+    data.drop(2)
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1315215ef3a81723a81f885bd9cce59567c77c7c
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializer.kt
@@ -0,0 +1,28 @@
+package de.justjanne.libquassel.protocol.serializers.signalproxy
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import java.nio.ByteBuffer
+
+object SyncSerializer : SignalProxySerializer<SignalProxyMessage.Sync> {
+  override val type: Int = 1
+
+  override fun serialize(data: SignalProxyMessage.Sync) = listOf(
+    qVariant(type, QtType.Int),
+    qVariant(StringSerializerUtf8.serializeRaw(data.className), QtType.QByteArray),
+    qVariant(StringSerializerUtf8.serializeRaw(data.objectName), QtType.QByteArray),
+    qVariant(StringSerializerUtf8.serializeRaw(data.slotName), QtType.QByteArray)
+  ) + data.params
+
+  override fun deserialize(data: QVariantList) = SignalProxyMessage.Sync(
+    StringSerializerUtf8.deserializeRaw(data[1].into<ByteBuffer>()),
+    StringSerializerUtf8.deserializeRaw(data[2].into<ByteBuffer>()),
+    StringSerializerUtf8.deserializeRaw(data[3].into<ByteBuffer>()),
+    data.drop(4)
+  )
+}
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 91997768609729600e11b807219d3ddf57c59031..5f70f09b11745c0dcbc8bbff9ce81615e25ab162 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
@@ -4,3 +4,8 @@ package de.justjanne.libquassel.protocol.variant
  * Simple alias for a generic QVariantList type
  */
 typealias QVariantList = List<QVariant_>
+
+fun QVariantList.toVariantMap(): QVariantMap =
+  this.zipWithNext().map { (key, value) ->
+    Pair(key.into(""), value)
+  }.toMap()
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantMap.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantMap.kt
index 76057f15745fcbb009ed3b8875cf35eb4b194780..52e0fe81db563126618dc9e4d1eadbf8a86644f0 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantMap.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantMap.kt
@@ -1,6 +1,13 @@
 package de.justjanne.libquassel.protocol.variant
 
+import de.justjanne.libquassel.protocol.models.types.QtType
+
 /**
  * Simple alias for a generic QVariantMap type
  */
 typealias QVariantMap = Map<String, QVariant_>
+
+fun QVariantMap.toVariantList(): QVariantList =
+  this.toList().flatMap { (key, value) ->
+    listOf(qVariant(key, QtType.QString), value)
+  }
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/deserialize.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/deserialize.kt
index 34dd4ad658d57085f8a104852d5c02b6f7fbd799..a4f64c131999dfed506d0efec059e494d6c5a8ed 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/deserialize.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/deserialize.kt
@@ -19,9 +19,13 @@
 package de.justjanne.libquassel.protocol.testutil
 
 import de.justjanne.libquassel.protocol.features.FeatureSet
+import de.justjanne.libquassel.protocol.models.HandshakeMessage
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
 import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
 import de.justjanne.libquassel.protocol.serializers.qt.HandshakeMapSerializer
+import de.justjanne.libquassel.protocol.serializers.qt.QVariantListSerializer
 import org.hamcrest.Matcher
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.jupiter.api.Assertions.assertEquals
@@ -57,7 +61,7 @@ fun <T> testDeserialize(
   assertEquals(data, after)
 }
 
-fun <T> testDeserialize(
+fun <T : HandshakeMessage> testDeserialize(
   serializer: HandshakeSerializer<T>,
   matcher: Matcher<in T>,
   buffer: ByteBuffer,
@@ -68,7 +72,7 @@ fun <T> testDeserialize(
   assertThat(after, matcher)
 }
 
-fun <T> testDeserialize(
+fun <T : HandshakeMessage> testDeserialize(
   serializer: HandshakeSerializer<T>,
   data: T,
   buffer: ByteBuffer,
@@ -78,3 +82,25 @@ fun <T> testDeserialize(
   val after = serializer.deserialize(map)
   assertEquals(data, after)
 }
+
+fun <T : SignalProxyMessage> testDeserialize(
+  serializer: SignalProxySerializer<T>,
+  matcher: Matcher<in T>,
+  buffer: ByteBuffer,
+  featureSet: FeatureSet = FeatureSet.all()
+) {
+  val list = deserialize(QVariantListSerializer, buffer, featureSet)
+  val after = serializer.deserialize(list)
+  assertThat(after, matcher)
+}
+
+fun <T : SignalProxyMessage> testDeserialize(
+  serializer: SignalProxySerializer<T>,
+  data: T,
+  buffer: ByteBuffer,
+  featureSet: FeatureSet = FeatureSet.all()
+) {
+  val list = deserialize(QVariantListSerializer, buffer, featureSet)
+  val after = serializer.deserialize(list)
+  assertEquals(data, after)
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/handshakeSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/handshakeSerializerTest.kt
index d0e227107ad7d92b6c1582826e6cfe323cd26d0f..3e7c49f0cf0cadb1fba972126934ac06e793f10a 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/handshakeSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/handshakeSerializerTest.kt
@@ -19,11 +19,12 @@
 package de.justjanne.libquassel.protocol.testutil
 
 import de.justjanne.libquassel.protocol.features.FeatureSet
+import de.justjanne.libquassel.protocol.models.HandshakeMessage
 import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
 import org.hamcrest.Matcher
 import java.nio.ByteBuffer
 
-fun <T> handshakeSerializerTest(
+fun <T : HandshakeMessage> handshakeSerializerTest(
   serializer: HandshakeSerializer<T>,
   value: T,
   encoded: ByteBuffer? = null,
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/serialize.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/serialize.kt
index 70e7011701ba253fd64b0a45c2c88b8f472eb2db..c19cafe5d70dee2227e4108100571eddd8c66215 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/serialize.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/serialize.kt
@@ -20,9 +20,13 @@ package de.justjanne.libquassel.protocol.testutil
 
 import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
+import de.justjanne.libquassel.protocol.models.HandshakeMessage
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
 import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
 import de.justjanne.libquassel.protocol.serializers.qt.HandshakeMapSerializer
+import de.justjanne.libquassel.protocol.serializers.qt.QVariantListSerializer
 import de.justjanne.libquassel.protocol.testutil.matchers.ByteBufferMatcher
 import org.hamcrest.MatcherAssert.assertThat
 import java.nio.ByteBuffer
@@ -47,7 +51,7 @@ fun <T> testSerialize(
   assertThat(after, ByteBufferMatcher(buffer))
 }
 
-fun <T> testSerialize(
+fun <T : HandshakeMessage> testSerialize(
   serializer: HandshakeSerializer<T>,
   data: T,
   buffer: ByteBuffer,
@@ -57,3 +61,14 @@ fun <T> testSerialize(
   val after = serialize(HandshakeMapSerializer, map, featureSet)
   assertThat(after, ByteBufferMatcher(buffer))
 }
+
+fun <T : SignalProxyMessage> testSerialize(
+  serializer: SignalProxySerializer<T>,
+  data: T,
+  buffer: ByteBuffer,
+  featureSet: FeatureSet = FeatureSet.all()
+) {
+  val list = serializer.serialize(data)
+  val after = serialize(QVariantListSerializer, list, featureSet)
+  assertThat(after, ByteBufferMatcher(buffer))
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/signalProxySerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/signalProxySerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0fa7c85e39b2f694b88feaceac63d5555c7737f9
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/signalProxySerializerTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.testutil
+
+import de.justjanne.libquassel.protocol.features.FeatureSet
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import org.hamcrest.Matcher
+import java.nio.ByteBuffer
+
+fun <T : SignalProxyMessage> signalProxySerializerTest(
+  serializer: SignalProxySerializer<T>,
+  value: T,
+  encoded: ByteBuffer? = null,
+  matcher: ((T) -> Matcher<T>)? = null,
+  featureSets: List<FeatureSet> = listOf(FeatureSet.none(), FeatureSet.all()),
+  deserializeFeatureSet: FeatureSet? = FeatureSet.all(),
+  serializeFeatureSet: FeatureSet? = FeatureSet.all(),
+) {
+  if (encoded != null) {
+    if (deserializeFeatureSet != null) {
+      if (matcher != null) {
+        testDeserialize(serializer, matcher(value), encoded.rewind(), deserializeFeatureSet)
+      } else {
+        testDeserialize(serializer, value, encoded.rewind(), deserializeFeatureSet)
+      }
+    }
+    if (serializeFeatureSet != null) {
+      testSerialize(serializer, value, encoded.rewind(), serializeFeatureSet)
+    }
+  }
+  for (featureSet in featureSets) {
+    testSignalProxySerializerDirect(serializer, value)
+    testSignalProxySerializerEncoded(serializer, value, featureSet)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt
index c1ad766257539968183c8bc5b8b9d60abdbd3ffc..2fd34c3c27928635570f17ff21f230a4578b7ca5 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt
@@ -18,12 +18,13 @@
  */
 package de.justjanne.libquassel.protocol.testutil
 
+import de.justjanne.libquassel.protocol.models.HandshakeMessage
 import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
 import org.hamcrest.Matcher
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.jupiter.api.Assertions.assertEquals
 
-fun <T> testHandshakeSerializerDirect(
+fun <T : HandshakeMessage> testHandshakeSerializerDirect(
   serializer: HandshakeSerializer<T>,
   data: T,
   matcher: Matcher<T>? = null
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt
index 56692a983a9bb6ade35a0dac394e996d5771c22b..fcb03de52978174bbe9181a06227054a470853e5 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt
@@ -20,13 +20,14 @@ package de.justjanne.libquassel.protocol.testutil
 
 import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
+import de.justjanne.libquassel.protocol.models.HandshakeMessage
 import de.justjanne.libquassel.protocol.serializers.HandshakeSerializer
 import de.justjanne.libquassel.protocol.serializers.qt.HandshakeMapSerializer
 import org.hamcrest.Matcher
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.jupiter.api.Assertions.assertEquals
 
-fun <T> testHandshakeSerializerEncoded(
+fun <T : HandshakeMessage> testHandshakeSerializerEncoded(
   serializer: HandshakeSerializer<T>,
   data: T,
   featureSet: FeatureSet = FeatureSet.all(),
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerDirect.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerDirect.kt
new file mode 100644
index 0000000000000000000000000000000000000000..10bc255b167e1a2d2bb6d9578290d2733679d61c
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerDirect.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.testutil
+
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.jupiter.api.Assertions.assertEquals
+
+fun <T : SignalProxyMessage> testSignalProxySerializerDirect(
+  serializer: SignalProxySerializer<T>,
+  data: T,
+  matcher: Matcher<T>? = null
+) {
+  val after = serializer.deserialize(serializer.serialize(data))
+  if (matcher != null) {
+    assertThat(after, matcher)
+  } else {
+    assertEquals(data, after)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerEncoded.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerEncoded.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8575be4683c71e2f50511f1bcd1aa2e0bd8914f4
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerEncoded.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.testutil
+
+import de.justjanne.libquassel.protocol.features.FeatureSet
+import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.serializers.SignalProxySerializer
+import de.justjanne.libquassel.protocol.serializers.qt.QVariantListSerializer
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.jupiter.api.Assertions.assertEquals
+
+fun <T : SignalProxyMessage> testSignalProxySerializerEncoded(
+  serializer: SignalProxySerializer<T>,
+  data: T,
+  featureSet: FeatureSet = FeatureSet.all(),
+  matcher: Matcher<T>? = null
+) {
+  val buffer = ChainedByteBuffer(limit = 16384)
+  QVariantListSerializer.serialize(buffer, serializer.serialize(data), featureSet)
+  val result = buffer.toBuffer()
+  val after = serializer.deserialize(QVariantListSerializer.deserialize(result, featureSet))
+  assertEquals(0, result.remaining())
+  if (matcher != null) {
+    assertThat(after, matcher)
+  } else {
+    assertEquals(data, after)
+  }
+}