From 2792ca9ab07eab08b01dc8cb7e1ab4bf0884210f Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Tue, 16 Feb 2021 21:26:10 +0100
Subject: [PATCH] Implement signal proxy messages

---
 .../protocol/models/DccIpDetectionMode.kt     |  3 +-
 .../protocol/models/DccPortSelectionMode.kt   |  3 +-
 .../protocol/models/NetworkLayerProtocol.kt   |  3 +-
 .../protocol/models/SignalProxyMessage.kt     | 64 ++++++++++++++++++-
 .../libquassel/protocol/models/TimeSpec.kt    |  3 +-
 .../protocol/models/flags/BufferActivity.kt   |  3 +-
 .../protocol/models/flags/BufferType.kt       |  3 +-
 .../protocol/models/flags/MessageFlag.kt      |  3 +-
 .../protocol/models/flags/MessageType.kt      |  3 +-
 .../protocol/models/types/QtType.kt           |  3 +-
 .../protocol/models/types/QuasselType.kt      |  3 +-
 .../serializers/HandshakeSerializer.kt        |  3 +-
 .../NoSerializerForTypeException.kt           |  4 +-
 .../SignalProxyMessageSerializer.kt           | 64 +++++++++++++++++++
 .../serializers/SignalProxySerializer.kt      | 44 +++++++++++++
 .../serializers/qt/StringSerializer.kt        |  5 +-
 .../signalproxy/HeartBeatReplySerializer.kt   | 22 +++++++
 .../signalproxy/HeartBeatSerializer.kt        | 22 +++++++
 .../signalproxy/InitDataSerializer.kt         | 28 ++++++++
 .../signalproxy/InitRequestSerializer.kt      | 25 ++++++++
 .../serializers/signalproxy/RpcSerializer.kt  | 24 +++++++
 .../serializers/signalproxy/SyncSerializer.kt | 28 ++++++++
 .../protocol/variant/QVariantList.kt          |  5 ++
 .../protocol/variant/QVariantMap.kt           |  7 ++
 .../protocol/testutil/deserialize.kt          | 30 ++++++++-
 .../testutil/handshakeSerializerTest.kt       |  3 +-
 .../libquassel/protocol/testutil/serialize.kt | 17 ++++-
 .../testutil/signalProxySerializerTest.kt     | 52 +++++++++++++++
 .../testutil/testHandshakeSerializerDirect.kt |  3 +-
 .../testHandshakeSerializerEncoded.kt         |  3 +-
 .../testSignalProxySerializerDirect.kt        | 38 +++++++++++
 .../testSignalProxySerializerEncoded.kt       | 46 +++++++++++++
 32 files changed, 546 insertions(+), 21 deletions(-)
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxyMessageSerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/SignalProxySerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatReplySerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitRequestSerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/RpcSerializer.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializer.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/signalProxySerializerTest.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerDirect.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/testSignalProxySerializerEncoded.kt

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 ce8224f..04234b6 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 32ad226..e3a5f38 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 1e54f7b..0588c5b 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 dab50bc..25930f8 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 7627c2b..b738b66 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 4737c74..286e221 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 1cc549b..40e2300 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 f2c897a..9676839 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 653bc95..765cf2a 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 793adea..0ece62b 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 40fc108..9e6d03f 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 357bc67..2890ffa 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 7d1f670..996e507 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 0000000..4ea7d90
--- /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 0000000..595ca30
--- /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 dbf7c7b..59d4f7c 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 0000000..e31ba6e
--- /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 0000000..efbc8a4
--- /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 0000000..e913014
--- /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 0000000..a082f18
--- /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 0000000..0b72810
--- /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 0000000..1315215
--- /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 9199776..5f70f09 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 76057f1..52e0fe8 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 34dd4ad..a4f64c1 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 d0e2271..3e7c49f 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 70e7011..c19cafe 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 0000000..0fa7c85
--- /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 c1ad766..2fd34c3 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 56692a9..fcb03de 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 0000000..10bc255
--- /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 0000000..8575be4
--- /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)
+  }
+}
-- 
GitLab