diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
index 33317b512f568b4cf5873515923fe720ca706c17..ad98e36a9458e1b30ebe50cefde68b6a0dd20de7 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
@@ -47,16 +47,22 @@ open class BufferViewConfig(
           ?.mapNotNull { it.into<BufferId>() }
           ?.toSet()
           .orEmpty(),
-        bufferViewName = properties["bufferViewName"].into(bufferViewName()),
-        networkId = properties["networkId"].into(networkId()),
-        addNewBuffersAutomatically = properties["addNewBuffersAutomatically"].into(addNewBuffersAutomatically()),
-        sortAlphabetically = properties["sortAlphabetically"].into(sortAlphabetically()),
-        hideInactiveBuffers = properties["hideInactiveBuffers"].into(hideInactiveBuffers()),
-        hideInactiveNetworks = properties["hideInactiveNetworks"].into(hideInactiveNetworks()),
-        disableDecoration = properties["disableDecoration"].into(disableDecoration()),
-        allowedBufferTypes = properties["allowedBufferTypes"].into(allowedBufferTypes()),
-        minimumActivity = properties["minimumActivity"].into(minimumActivity()),
-        showSearch = properties["showSearch"].into(showSearch()),
+        bufferViewName = properties["bufferViewName"].into(bufferViewName),
+        networkId = properties["networkId"].into(networkId),
+        addNewBuffersAutomatically = properties["addNewBuffersAutomatically"].into(addNewBuffersAutomatically),
+        sortAlphabetically = properties["sortAlphabetically"].into(sortAlphabetically),
+        hideInactiveBuffers = properties["hideInactiveBuffers"].into(hideInactiveBuffers),
+        hideInactiveNetworks = properties["hideInactiveNetworks"].into(hideInactiveNetworks),
+        disableDecoration = properties["disableDecoration"].into(disableDecoration),
+        allowedBufferTypes = properties["allowedBufferTypes"].into<Int>()
+          ?.let(Int::toUShort)
+          ?.let(BufferType.Companion::of)
+          ?: allowedBufferTypes,
+        minimumActivity = properties["minimumActivity"].into<Int>()
+          ?.let(Int::toUInt)
+          ?.let(BufferActivity.Companion::of)
+          ?: minimumActivity,
+        showSearch = properties["showSearch"].into(showSearch),
       )
     }
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
index 758d914e5ee228f313ba2159086f1a3911f2f83b..d33c2a696611e8b0798b0dfe1f2c88dfdf536416 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
@@ -44,17 +44,17 @@ open class Network(
       copy(
         networkName = properties["networkName"].into(networkName),
         currentServer = properties["currentServer"].into(currentServer),
-        myNick = properties["myNick"].into(),
+        myNick = properties["myNick"].into(myNick),
         latency = properties["latency"].into(latency),
-        codecForServer = StringSerializerUtf8.deserializeRaw(
-          properties["codecForServer"].into<ByteBuffer>()
-        ),
-        codecForEncoding = StringSerializerUtf8.deserializeRaw(
-          properties["codecForEncoding"].into<ByteBuffer>()
-        ),
-        codecForDecoding = StringSerializerUtf8.deserializeRaw(
-          properties["codecForDecoding"].into<ByteBuffer>()
-        ),
+        codecForServer = properties["codecForServer"].into<ByteBuffer>()
+          ?.let(StringSerializerUtf8::deserializeRaw)
+          ?: codecForServer,
+        codecForEncoding = properties["codecForEncoding"].into<ByteBuffer>()
+          ?.let(StringSerializerUtf8::deserializeRaw)
+          ?: codecForServer,
+        codecForDecoding = properties["codecForDecoding"].into<ByteBuffer>()
+          ?.let(StringSerializerUtf8::deserializeRaw)
+          ?: codecForServer,
         identity = properties["identityId"]
           .into(identity),
         connected = properties["isConnected"]
@@ -356,10 +356,10 @@ open class Network(
     if (properties.isNotEmpty()) {
       channel.fromVariantMap(properties, index)
       channel.initialized = true
-      session?.synchronize(channel)
-      state.update {
-        copy(ircChannels = ircChannels + Pair(caseMapper().toLowerCase(name), channel))
-      }
+    }
+    session?.synchronize(channel)
+    state.update {
+      copy(ircChannels = ircChannels + Pair(caseMapper().toLowerCase(name), channel))
     }
     return channel
   }
@@ -421,7 +421,10 @@ open class Network(
 
   override fun removeCap(capability: String) {
     state.update {
-      copy(capsEnabled = capsEnabled - caseMapper().toLowerCase(capability))
+      copy(
+        caps = caps - caseMapper().toLowerCase(capability),
+        capsEnabled = capsEnabled - caseMapper().toLowerCase(capability),
+      )
     }
     super.removeCap(capability)
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
index e5c06881ad0afec6bab6ddf0d0c8b8e2bac84191..0f2abfacbbe5e52336008ee33fe7314cad628690 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
@@ -119,29 +119,29 @@ data class NetworkState(
       return Pair(defaultPrefixes, defaultPrefixModes)
     } else if ((prefix.toSet() intersect defaultPrefixes.toSet()).isNotEmpty()) {
       val (prefixes, prefixModes) = defaultPrefixes.zip(defaultPrefixModes)
-        .filter { prefix.contains(it.second) }
+        .filter { prefix.contains(it.first) }
         .unzip()
 
-      return Pair(prefixModes, prefixes)
+      return Pair(prefixes, prefixModes)
     } else if ((prefix.toSet() intersect defaultPrefixModes.toSet()).isNotEmpty()) {
       val (prefixes, prefixModes) = defaultPrefixes.zip(defaultPrefixModes)
-        .filter { prefix.contains(it.first) }
+        .filter { prefix.contains(it.second) }
         .unzip()
 
-      return Pair(prefixModes, prefixes)
+      return Pair(prefixes, prefixModes)
     }
 
     return Pair(defaultPrefixes, defaultPrefixModes)
   }
 
   private fun determineChannelModeTypes(): Map<ChannelModeType, Set<Char>> {
-    return ChannelModeType.values()
-      .zip(
-        supportValue(IrcISupport.CHANMODES)
-          ?.split(',', limit = ChannelModeType.values().size)
-          ?.map(String::toSet)
-          .orEmpty()
-      )
-      .toMap()
+    val groups = supportValue(IrcISupport.CHANMODES)
+      ?.split(',')
+      ?.map(String::toSet)
+      .orEmpty()
+
+    return ChannelModeType.values().withIndex().map { (index, key) ->
+      key to groups.getOrNull(index).orEmpty()
+    }.toMap()
   }
 }
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4c4f265ec31125fbe75f9a013eaa47e7b8f0b0a5
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt
@@ -0,0 +1,44 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
+import de.justjanne.libquassel.protocol.testutil.nextBufferViewConfig
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class BufferViewConfigTest {
+  @Test
+  fun testEmpty() {
+    val state = BufferViewConfigState(bufferViewId = 1)
+    val actual = BufferViewConfig(state = state).apply {
+      fromVariantMap(emptyMap())
+    }.state()
+
+    assertEquals(state, actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextBufferViewConfig(bufferViewId = 1)
+
+    val actual = BufferViewConfig(
+      state = BufferViewConfigState(
+        bufferViewId = expected.bufferViewId,
+      )
+    ).apply {
+      fromVariantMap(BufferViewConfig(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ea5a9213233a27d9c129d6bb8eba16df62232c04
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt
@@ -0,0 +1,40 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
+import de.justjanne.libquassel.protocol.testutil.nextBufferViewManager
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class BufferViewManagerTest {
+  @Test
+  fun testEmpty() {
+    val state = BufferViewManagerState()
+    val actual = BufferViewManager(state = state).apply {
+      fromVariantMap(emptyMap())
+    }.state()
+
+    assertEquals(state, actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextBufferViewManager()
+
+    val actual = BufferViewManager().apply {
+      fromVariantMap(BufferViewManager(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..05e7790295a0b2e3d836c27a15f6d3f2ef3050e1
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt
@@ -0,0 +1,49 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
+import de.justjanne.libquassel.protocol.testutil.nextIrcChannel
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class IrcChannelTest {
+  @Test
+  fun testEmpty() {
+    val state = IrcChannelState(
+      network = NetworkId(1),
+      name = "#name"
+    )
+    val actual = IrcChannel(state = state).apply {
+      fromVariantMap(emptyMap())
+    }.state()
+
+    assertEquals(state, actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextIrcChannel(NetworkId(random.nextInt()))
+
+    val actual = IrcChannel(
+      state = IrcChannelState(
+        network = expected.network,
+        name = expected.name,
+      )
+    ).apply {
+      fromVariantMap(IrcChannel(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..973a7d8c2a7ac45c77fbb20543660cdd0be6ba4e
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt
@@ -0,0 +1,53 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
+import de.justjanne.libquassel.protocol.testutil.nextIrcUser
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class IrcUserTest {
+  @Test
+  fun testEmpty() {
+    val state = IrcUserState(
+      network = NetworkId(1),
+      nick = "nick",
+      user = "user",
+      host = "host"
+    )
+    val actual = IrcUser(state = state).apply {
+      fromVariantMap(emptyMap())
+    }.state()
+
+    assertEquals(state, actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextIrcUser(NetworkId(random.nextInt()))
+
+    val actual = IrcUser(
+      state = IrcUserState(
+        network = expected.network,
+        nick = expected.nick,
+        user = expected.user,
+        host = expected.host
+      )
+    ).apply {
+      fromVariantMap(IrcUser(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt
index 7959f102c51b5a1e4f0cdaa582559cccc41686c6..fe4bb8f13d732673d3eb1c7248b0d3f4c33fac54 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt
@@ -10,13 +10,27 @@
 package de.justjanne.libquassel.protocol.syncables
 
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.network.ChannelModeType
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
 import de.justjanne.libquassel.protocol.testutil.nextNetwork
+import de.justjanne.libquassel.protocol.testutil.nextString
 import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Nested
 import org.junit.jupiter.api.Test
 import kotlin.random.Random
+import kotlin.test.assertNotEquals
 
 class NetworkTest {
+  @Test
+  fun testEmpty() {
+    val state = NetworkState(networkId = NetworkId(1))
+    val actual = Network(state = state).apply {
+      fromVariantMap(emptyMap())
+    }.state()
+
+    assertEquals(state, actual)
+  }
+
   @Test
   fun testSerialization() {
     val random = Random(1337)
@@ -29,4 +43,406 @@ class NetworkTest {
 
     assertEquals(expected, actual)
   }
+
+  @Nested
+  inner class User {
+    @Test
+    fun addNew() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.ircUserCount()
+      assertNotEquals(0, sizeBefore)
+      network.addIrcUser(random.nextString())
+      assertEquals(sizeBefore + 1, network.ircUserCount())
+    }
+
+    @Test
+    fun addExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      assertNotEquals(0, network.ircUserCount())
+      val existing = network.ircUsers().first()
+      assertEquals(existing, network.newIrcUser(existing.hostMask()))
+    }
+
+    @Test
+    fun removeExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.ircUserCount()
+      assertNotEquals(0, sizeBefore)
+      network.removeIrcUser(network.ircUsers().first())
+      assertEquals(sizeBefore - 1, network.ircUserCount())
+    }
+  }
+
+  @Nested
+  inner class Channel {
+    @Test
+    fun addNew() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.ircChannelCount()
+      assertNotEquals(0, sizeBefore)
+      network.addIrcChannel(random.nextString())
+      assertEquals(sizeBefore + 1, network.ircChannelCount())
+    }
+
+    @Test
+    fun addExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      assertNotEquals(0, network.ircUserCount())
+      val existing = network.ircChannels().first()
+      assertEquals(existing, network.newIrcChannel(existing.name()))
+    }
+
+    @Test
+    fun removeExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.ircChannelCount()
+      assertNotEquals(0, sizeBefore)
+      network.removeIrcChannel(network.ircChannels().first())
+      assertEquals(sizeBefore - 1, network.ircChannelCount())
+    }
+  }
+
+  @Nested
+  inner class Support {
+    @Test
+    fun addNew() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.supports().size
+      assertNotEquals(0, sizeBefore)
+      network.addSupport(random.nextString(), random.nextString())
+      assertEquals(sizeBefore + 1, network.supports().size)
+    }
+
+    @Test
+    fun addExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.supports().size
+      assertNotEquals(0, sizeBefore)
+      network.addSupport(network.supports().keys.first(), random.nextString())
+      assertEquals(sizeBefore, network.supports().size)
+    }
+
+    @Test
+    fun removeExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.supports().size
+      assertNotEquals(0, sizeBefore)
+      network.removeSupport(network.supports().keys.first())
+      assertEquals(sizeBefore - 1, network.supports().size)
+    }
+  }
+
+  @Nested
+  inner class Capability {
+    @Test
+    fun addNew() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.caps().size
+      assertNotEquals(0, sizeBefore)
+      network.addCap(random.nextString(), random.nextString())
+      assertEquals(sizeBefore + 1, network.caps().size)
+    }
+
+    @Test
+    fun addExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.caps().size
+      assertNotEquals(0, sizeBefore)
+      network.addCap(network.caps().keys.first(), random.nextString())
+      assertEquals(sizeBefore, network.caps().size)
+    }
+
+    @Test
+    fun acknowledgeNew() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.capsEnabled().size
+      assertNotEquals(0, sizeBefore)
+      network.acknowledgeCap(random.nextString())
+      assertEquals(sizeBefore + 1, network.capsEnabled().size)
+    }
+
+    @Test
+    fun acknowledgeExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.capsEnabled().size
+      assertNotEquals(0, sizeBefore)
+      network.acknowledgeCap(network.capsEnabled().first())
+      assertEquals(sizeBefore, network.capsEnabled().size)
+    }
+
+    @Test
+    fun removeExisting() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.caps().size
+      assertNotEquals(0, sizeBefore)
+      network.removeCap(network.caps().keys.first())
+      assertEquals(sizeBefore - 1, network.caps().size)
+    }
+
+    @Test
+    fun clear() {
+      val random = Random(1337)
+      val network = Network(state = random.nextNetwork(networkId = NetworkId(random.nextInt())))
+
+      val sizeBefore = network.caps().size
+      assertNotEquals(0, sizeBefore)
+      network.clearCaps()
+      assertEquals(0, network.caps().size)
+      assertEquals(0, network.capsEnabled().size)
+    }
+  }
+
+  @Nested
+  inner class ChannelModes {
+    @Test
+    fun usual() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "CHANMODES" to "eIbq,k,flj,CFLMPQScgimnprstuz"
+          )
+        )
+      )
+
+      assertEquals(
+        mapOf(
+          ChannelModeType.A_CHANMODE to setOf('e', 'I', 'b', 'q'),
+          ChannelModeType.B_CHANMODE to setOf('k'),
+          ChannelModeType.C_CHANMODE to setOf('f', 'l', 'j'),
+          ChannelModeType.D_CHANMODE to setOf(
+            'C', 'F', 'L', 'M', 'P', 'Q', 'S', 'c', 'g', 'i', 'm', 'n', 'p', 'r', 's', 't', 'u', 'z'
+          ),
+        ),
+        network.channelModes()
+      )
+    }
+
+    @Test
+    fun blank() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "CHANMODES" to ""
+          )
+        )
+      )
+
+      assertEquals(
+        mapOf<ChannelModeType, Set<Char>>(
+          ChannelModeType.A_CHANMODE to emptySet(),
+          ChannelModeType.B_CHANMODE to emptySet(),
+          ChannelModeType.C_CHANMODE to emptySet(),
+          ChannelModeType.D_CHANMODE to emptySet(),
+        ),
+        network.channelModes()
+      )
+    }
+
+    @Test
+    fun empty() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = emptyMap()
+        )
+      )
+
+      assertEquals(
+        mapOf<ChannelModeType, Set<Char>>(
+          ChannelModeType.A_CHANMODE to emptySet(),
+          ChannelModeType.B_CHANMODE to emptySet(),
+          ChannelModeType.C_CHANMODE to emptySet(),
+          ChannelModeType.D_CHANMODE to emptySet(),
+        ),
+        network.channelModes()
+      )
+    }
+
+    @Test
+    fun wrongData() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "CHANMODES" to "a,b,c,d,e"
+          )
+        )
+      )
+
+      assertEquals(
+        mapOf(
+          ChannelModeType.A_CHANMODE to setOf('a'),
+          ChannelModeType.B_CHANMODE to setOf('b'),
+          ChannelModeType.C_CHANMODE to setOf('c'),
+          ChannelModeType.D_CHANMODE to setOf('d'),
+        ),
+        network.channelModes()
+      )
+    }
+  }
+
+  @Nested
+  inner class Prefixes {
+    @Test
+    fun usual() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "PREFIX" to "(@+)ov"
+          )
+        )
+      )
+
+      assertEquals(
+        listOf('@', '+'),
+        network.prefixes()
+      )
+
+      assertEquals(
+        listOf('o', 'v'),
+        network.prefixModes()
+      )
+    }
+
+    @Test
+    fun wrongFormatting() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "PREFIX" to "(@+]ov"
+          )
+        )
+      )
+
+      assertEquals(
+        listOf('@', '+'),
+        network.prefixes()
+      )
+
+      assertEquals(
+        listOf('o', 'v'),
+        network.prefixModes()
+      )
+    }
+
+    @Test
+    fun onlyPrefixes() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "PREFIX" to "@+"
+          )
+        )
+      )
+
+      assertEquals(
+        listOf('@', '+'),
+        network.prefixes()
+      )
+
+      assertEquals(
+        listOf('o', 'v'),
+        network.prefixModes()
+      )
+    }
+
+    @Test
+    fun onlyModes() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "PREFIX" to "ov"
+          )
+        )
+      )
+
+      assertEquals(
+        listOf('@', '+'),
+        network.prefixes()
+      )
+
+      assertEquals(
+        listOf('o', 'v'),
+        network.prefixModes()
+      )
+    }
+
+    @Test
+    fun blank() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "PREFIX" to ""
+          )
+        )
+      )
+
+      assertEquals(
+        listOf('~', '&', '@', '%', '+'),
+        network.prefixes()
+      )
+
+      assertEquals(
+        listOf('q', 'a', 'o', 'h', 'v'),
+        network.prefixModes()
+      )
+    }
+
+    @Test
+    fun wrongContent() {
+      val network = Network(
+        state = NetworkState(
+          networkId = NetworkId(1),
+          supports = mapOf(
+            "PREFIX" to "12345"
+          )
+        )
+      )
+
+      assertEquals(
+        listOf('~', '&', '@', '%', '+'),
+        network.prefixes()
+      )
+
+      assertEquals(
+        listOf('q', 'a', 'o', 'h', 'v'),
+        network.prefixModes()
+      )
+    }
+  }
 }
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt
index bf64cd20336aeee6cc8d4c12f777b3001652f9f3..80766aaab4263812a67af3d21e224e18b1fbc576 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt
@@ -9,16 +9,24 @@
 
 package de.justjanne.libquassel.protocol.testutil
 
+import de.justjanne.bitflags.of
+import de.justjanne.libquassel.protocol.models.flags.BufferActivity
+import de.justjanne.libquassel.protocol.models.flags.BufferType
+import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.network.NetworkServer
+import de.justjanne.libquassel.protocol.syncables.BufferViewConfig
 import de.justjanne.libquassel.protocol.syncables.IrcChannel
 import de.justjanne.libquassel.protocol.syncables.IrcUser
+import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
+import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
 import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
 import org.threeten.bp.Instant
 import java.util.EnumSet
+import java.util.Locale
 import java.util.UUID
 import kotlin.random.Random
 import kotlin.random.nextUInt
@@ -52,16 +60,20 @@ fun Random.nextNetwork(networkId: NetworkId) = NetworkState(
   connected = nextBoolean(),
   connectionState = nextEnum(),
   ircUsers = List(nextInt(20)) {
-    nextIrcUser(networkId)
-  }.associateBy(IrcUser::nick),
+    IrcUser(state = nextIrcUser(networkId))
+  }.associateBy(IrcUser::nick).mapKeys { (key) ->
+    key.toLowerCase(Locale.ROOT)
+  },
   ircChannels = List(nextInt(20)) {
-    nextIrcChannel(networkId)
-  }.associateBy(IrcChannel::name),
+    IrcChannel(state = nextIrcChannel(networkId))
+  }.associateBy(IrcChannel::name).mapKeys { (key) ->
+    key.toLowerCase(Locale.ROOT)
+  },
   supports = List(nextInt(20)) {
-    nextString() to nextString()
+    nextString().toUpperCase(Locale.ROOT) to nextString()
   }.toMap(),
   caps = List(nextInt(20)) {
-    nextString() to nextString()
+    nextString().toLowerCase(Locale.ROOT) to nextString()
   }.toMap(),
   capsEnabled = List(nextInt(20)) {
     nextString()
@@ -109,35 +121,66 @@ fun Random.nextNetworkServer() = NetworkServer(
 
 fun Random.nextIrcUser(
   networkId: NetworkId = NetworkId(nextInt())
-) = IrcUser(
-  state = IrcUserState(
-    network = networkId,
-    nick = nextString(),
-    user = nextString(),
-    host = nextString(),
-    realName = nextString(),
-    account = nextString(),
-    away = nextBoolean(),
-    awayMessage = nextString(),
-    idleTime = nextInstant(),
-    loginTime = nextInstant(),
-    server = nextString(),
-    ircOperator = nextString(),
-    lastAwayMessageTime = nextInstant(),
-    whoisServiceReply = nextString(),
-    suserHost = nextString(),
-    encrypted = nextBoolean()
-  )
+) = IrcUserState(
+  network = networkId,
+  nick = nextString(),
+  user = nextString(),
+  host = nextString(),
+  realName = nextString(),
+  account = nextString(),
+  away = nextBoolean(),
+  awayMessage = nextString(),
+  idleTime = nextInstant(),
+  loginTime = nextInstant(),
+  server = nextString(),
+  ircOperator = nextString(),
+  lastAwayMessageTime = nextInstant(),
+  whoisServiceReply = nextString(),
+  suserHost = nextString(),
+  encrypted = nextBoolean()
 )
 
 fun Random.nextIrcChannel(
   networkId: NetworkId = NetworkId(nextInt())
-) = IrcChannel(
-  state = IrcChannelState(
-    network = networkId,
-    name = nextString(),
-    topic = nextString(),
-    password = nextString(),
-    encrypted = nextBoolean()
-  )
+) = IrcChannelState(
+  network = networkId,
+  name = nextString(),
+  topic = nextString(),
+  password = nextString(),
+  encrypted = nextBoolean()
+)
+
+fun Random.nextBufferViewConfig(
+  bufferViewId: Int = nextInt()
+) = BufferViewConfigState(
+  bufferViewId = bufferViewId,
+  bufferViewName = nextString(),
+  networkId = NetworkId(nextInt()),
+  addNewBuffersAutomatically = nextBoolean(),
+  sortAlphabetically = nextBoolean(),
+  hideInactiveNetworks = nextBoolean(),
+  hideInactiveBuffers = nextBoolean(),
+  disableDecoration = nextBoolean(),
+  allowedBufferTypes = BufferType.of(
+    List(nextInt(BufferType.values().size)) {
+      nextEnum()
+    }
+  ),
+  minimumActivity = BufferActivity.of(nextEnum<BufferActivity>()),
+  showSearch = nextBoolean(),
+  buffers = List(nextInt(20)) {
+    BufferId(nextInt())
+  },
+  removedBuffers = List(nextInt(20)) {
+    BufferId(nextInt())
+  }.toSet(),
+  temporarilyRemovedBuffers = List(nextInt(20)) {
+    BufferId(nextInt())
+  }.toSet()
+)
+
+fun Random.nextBufferViewManager() = BufferViewManagerState(
+  bufferViewConfigs = List(nextInt(20)) {
+    BufferViewConfig(state = BufferViewConfigState(bufferViewId = nextInt()))
+  }.associateBy(BufferViewConfig::bufferViewId)
 )