From 10d6cd5a8f1cbc90aeb428cebb24c0fea795fd1b Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sun, 6 Jun 2021 19:53:04 +0200
Subject: [PATCH] Implement sync fully

---
 .../client/session/BaseInitHandler.kt         | 56 +++++++++++++++
 .../client/session/BaseInitHandlerState.kt    | 18 +++++
 .../client/session/ClientConnectionHandler.kt |  4 ++
 .../client/session/ClientHandshakeHandler.kt  | 21 +++++-
 .../client/session/ClientMagicHandler.kt      |  2 +
 .../session/ClientProxyMessageHandler.kt      | 48 ++++++-------
 .../client/session/ClientRpcHandler.kt        | 10 ++-
 .../client/session/ClientSession.kt           | 68 ++++++++++++++++---
 .../client/session/ClientSessionState.kt      |  2 +
 .../justjanne/libquassel/client/ClientTest.kt | 19 +++---
 .../test/resources/simplelogger.properties    |  2 +-
 .../protocol/models/ConnectedClient.kt        |  3 +-
 .../serializers/qt/QDateTimeSerializer.kt     |  6 +-
 .../serializers/qt/QTimeSerializer.kt         |  6 +-
 .../signalproxy/HeartBeatSerializer.kt        |  3 +-
 .../signalproxy/InitDataSerializer.kt         |  2 +-
 .../protocol/session/CommonSyncProxy.kt       |  7 +-
 .../protocol/session/ConnectionHandler.kt     |  1 +
 .../protocol/session/MessageChannel.kt        | 20 ++++--
 .../libquassel/protocol/session/Session.kt    | 13 +++-
 .../syncables/common/BacklogManager.kt        |  6 +-
 .../protocol/syncables/common/BufferSyncer.kt |  7 ++
 .../syncables/common/BufferViewConfig.kt      |  4 ++
 .../protocol/syncables/common/CertManager.kt  |  5 ++
 .../protocol/syncables/common/CoreInfo.kt     |  3 +-
 .../syncables/common/IrcListHelper.kt         |  6 +-
 .../protocol/syncables/common/IrcUser.kt      |  8 ++-
 .../syncables/state/BufferViewConfigState.kt  |  4 +-
 .../syncables/state/CertManagerState.kt       |  4 +-
 .../libquassel/protocol/variant/QVariant.kt   |  2 +
 .../protocol/variant/QVariantList.kt          | 10 ++-
 31 files changed, 296 insertions(+), 74 deletions(-)
 create mode 100644 libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
 create mode 100644 libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandlerState.kt

diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
new file mode 100644
index 0000000..1b14319
--- /dev/null
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandler.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.client.session
+
+import de.justjanne.libquassel.client.util.CoroutineQueue
+import de.justjanne.libquassel.protocol.syncables.ObjectIdentifier
+import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.util.update
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class BaseInitHandler(
+  private val session: ClientSession
+) {
+  private val coroutineQueue = CoroutineQueue<Unit>()
+
+  fun sync(stub: SyncableStub) {
+    if (!stub.initialized) {
+      state.update {
+        copy(started = true, total = total + 1, waiting = waiting + ObjectIdentifier(stub))
+      }
+    }
+    session.proxy.synchronize(stub)
+  }
+
+  suspend fun initialized(identifier: ObjectIdentifier) {
+    state.update {
+      copy(waiting = waiting - identifier)
+    }
+    if (initDone()) {
+      coroutineQueue.resume(Unit)
+    }
+  }
+
+  fun initDone() = state().started && state().waiting.isEmpty()
+
+  suspend fun waitForInitDone() = if (!initDone()) {
+    coroutineQueue.wait()
+  } else Unit
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state(): BaseInitHandlerState = state.value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow(): Flow<BaseInitHandlerState> = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(BaseInitHandlerState())
+}
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandlerState.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandlerState.kt
new file mode 100644
index 0000000..63142f8
--- /dev/null
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/BaseInitHandlerState.kt
@@ -0,0 +1,18 @@
+/*
+ * 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.client.session
+
+import de.justjanne.libquassel.protocol.syncables.ObjectIdentifier
+
+data class BaseInitHandlerState(
+  val started: Boolean = false,
+  val total: Int = 0,
+  val waiting: Set<ObjectIdentifier> = setOf()
+)
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientConnectionHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientConnectionHandler.kt
index 63c798b..506e1a2 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientConnectionHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientConnectionHandler.kt
@@ -24,6 +24,10 @@ abstract class ClientConnectionHandler : ConnectionHandler {
     return false
   }
 
+  override suspend fun done() {
+    this.channel = null
+  }
+
   suspend fun emit(message: SignalProxyMessage) {
     if (channel == null) {
       readyQueue.wait()
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientHandshakeHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientHandshakeHandler.kt
index b0077fe..c7c409a 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientHandshakeHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientHandshakeHandler.kt
@@ -20,13 +20,21 @@ import de.justjanne.libquassel.protocol.session.MessageChannelReadThread
 import de.justjanne.libquassel.protocol.session.Session
 import de.justjanne.libquassel.protocol.util.log.trace
 import de.justjanne.libquassel.protocol.variant.QVariantMap
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.runInterruptible
+import kotlinx.coroutines.withContext
 import org.slf4j.LoggerFactory
+import sun.security.util.DisabledAlgorithmConstraints
 import java.nio.ByteBuffer
 
 class ClientHandshakeHandler(
   val session: Session
 ) : HandshakeHandler, ClientConnectionHandler() {
   private val messageQueue = CoroutineKeyedQueue<Class<out HandshakeMessage>, HandshakeMessage>()
+  private var sessionInit: HandshakeMessage.SessionInit? = null
 
   override suspend fun read(buffer: ByteBuffer): Boolean {
     return dispatch(HandshakeMessageSerializer.deserialize(buffer, channel!!.negotiatedFeatures))
@@ -36,10 +44,17 @@ class ClientHandshakeHandler(
     logger.trace { "Read handshake message $message" }
     messageQueue.resume(message.javaClass, message)
     if (message is HandshakeMessage.SessionInit) {
-      session.init(message.identities, message.bufferInfos, message.networkIds)
+      sessionInit = message
       return true
-    } else {
-      return false
+    }
+    return false
+  }
+
+  override suspend fun done() {
+    super.done()
+    val message = sessionInit
+    if (message != null) {
+      session.init(message.identities, message.bufferInfos, message.networkIds)
     }
   }
 
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientMagicHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientMagicHandler.kt
index 31f78d9..f067518 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientMagicHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientMagicHandler.kt
@@ -60,6 +60,8 @@ class ClientMagicHandler(
 
   override suspend fun read(buffer: ByteBuffer) = true
 
+  override suspend fun done() = Unit
+
   companion object {
     private val logger = LoggerFactory.getLogger(ClientMagicHandler::class.java)
   }
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientProxyMessageHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientProxyMessageHandler.kt
index 424f823..e1669ea 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientProxyMessageHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientProxyMessageHandler.kt
@@ -15,18 +15,19 @@ import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.serializers.SignalProxyMessageSerializer
 import de.justjanne.libquassel.protocol.session.ProxyMessageHandler
 import de.justjanne.libquassel.protocol.syncables.HeartBeatHandler
+import de.justjanne.libquassel.protocol.syncables.ObjectIdentifier
 import de.justjanne.libquassel.protocol.syncables.ObjectRepository
 import de.justjanne.libquassel.protocol.syncables.common.RpcHandler
 import de.justjanne.libquassel.protocol.syncables.invoker.Invokers
 import de.justjanne.libquassel.protocol.util.log.trace
 import org.slf4j.LoggerFactory
-import java.lang.Exception
 import java.nio.ByteBuffer
 
 class ClientProxyMessageHandler(
   private val heartBeatHandler: HeartBeatHandler,
   private val objectRepository: ObjectRepository,
-  private val rpcHandler: RpcHandler
+  private val rpcHandler: RpcHandler,
+  private val baseInitHandler: BaseInitHandler
 ) : ProxyMessageHandler, ClientConnectionHandler() {
 
   override suspend fun read(buffer: ByteBuffer): Boolean {
@@ -36,32 +37,31 @@ class ClientProxyMessageHandler(
 
   override suspend fun dispatch(message: SignalProxyMessage) {
     logger.trace { "Read signal proxy message $message" }
-    try {
-      when (message) {
-        is SignalProxyMessage.HeartBeat -> emit(SignalProxyMessage.HeartBeatReply(message.timestamp))
-        is SignalProxyMessage.HeartBeatReply -> heartBeatHandler.recomputeLatency(message.timestamp, force = true)
-        is SignalProxyMessage.InitData -> objectRepository.init(
+    when (message) {
+      is SignalProxyMessage.HeartBeat -> emit(SignalProxyMessage.HeartBeatReply(message.timestamp))
+      is SignalProxyMessage.HeartBeatReply -> heartBeatHandler.recomputeLatency(message.timestamp, force = true)
+      is SignalProxyMessage.InitData -> {
+        objectRepository.init(
           objectRepository.find(message.className, message.objectName) ?: return,
           message.initData
         )
-        is SignalProxyMessage.InitRequest -> {
-          // Ignore incoming requests, we’re a client, we shouldn’t ever receive these
-        }
-        is SignalProxyMessage.Rpc -> {
-          val invoker = Invokers.get(ProtocolSide.CLIENT, "RpcHandler")
-            ?: throw RpcInvocationFailedException.InvokerNotFoundException("RpcHandler")
-          invoker.invoke(rpcHandler, message.slotName, message.params)
-        }
-        is SignalProxyMessage.Sync -> {
-          val invoker = Invokers.get(ProtocolSide.CLIENT, message.className)
-            ?: throw RpcInvocationFailedException.InvokerNotFoundException(message.className)
-          val syncable = objectRepository.find(message.className, message.objectName)
-            ?: throw RpcInvocationFailedException.SyncableNotFoundException(message.className, message.objectName)
-          invoker.invoke(syncable, message.slotName, message.params)
-        }
+        baseInitHandler.initialized(ObjectIdentifier(message.className, message.objectName))
+      }
+      is SignalProxyMessage.InitRequest -> {
+        // Ignore incoming requests, we’re a client, we shouldn’t ever receive these
+      }
+      is SignalProxyMessage.Rpc -> {
+        val invoker = Invokers.get(ProtocolSide.CLIENT, "RpcHandler")
+          ?: throw RpcInvocationFailedException.InvokerNotFoundException("RpcHandler")
+        invoker.invoke(rpcHandler, message.slotName, message.params)
+      }
+      is SignalProxyMessage.Sync -> {
+        val invoker = Invokers.get(ProtocolSide.CLIENT, message.className)
+          ?: throw RpcInvocationFailedException.InvokerNotFoundException(message.className)
+        val syncable = objectRepository.find(message.className, message.objectName)
+          ?: throw RpcInvocationFailedException.SyncableNotFoundException(message.className, message.objectName)
+        invoker.invoke(syncable, message.slotName, message.params)
       }
-    } catch (e: Exception) {
-      println(e)
     }
   }
 
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientRpcHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientRpcHandler.kt
index c2e5257..62a5493 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientRpcHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientRpcHandler.kt
@@ -14,10 +14,12 @@ import de.justjanne.libquassel.protocol.models.StatusMessage
 import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
 import de.justjanne.libquassel.protocol.session.Session
 import de.justjanne.libquassel.protocol.syncables.common.RpcHandler
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.runBlocking
 import java.nio.ByteBuffer
 
 class ClientRpcHandler(session: Session) : RpcHandler(session) {
@@ -30,11 +32,15 @@ class ClientRpcHandler(session: Session) : RpcHandler(session) {
   }
 
   override fun displayMsg(message: Message) {
-    messages.tryEmit(message)
+    runBlocking(Dispatchers.Default) {
+      messages.emit(message)
+    }
   }
 
   override fun displayStatusMsg(net: String?, msg: String?) {
-    statusMessage.tryEmit(StatusMessage(net, msg ?: return))
+    runBlocking(Dispatchers.Default) {
+      statusMessage.emit(StatusMessage(net, msg ?: ""))
+    }
   }
 
   @Suppress("NOTHING_TO_INLINE")
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
index 03e6584..f9d4448 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSession.kt
@@ -12,6 +12,7 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.client.syncables.ClientBacklogManager
 import de.justjanne.libquassel.protocol.connection.ProtocolFeatures
 import de.justjanne.libquassel.protocol.connection.ProtocolMeta
+import de.justjanne.libquassel.protocol.features.QuasselFeature
 import de.justjanne.libquassel.protocol.io.CoroutineChannel
 import de.justjanne.libquassel.protocol.models.BufferInfo
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
@@ -26,6 +27,7 @@ import de.justjanne.libquassel.protocol.syncables.ObjectRepository
 import de.justjanne.libquassel.protocol.syncables.common.AliasManager
 import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
 import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CertManager
 import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
 import de.justjanne.libquassel.protocol.syncables.common.DccConfig
 import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
@@ -34,6 +36,7 @@ import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
 import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
 import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
+import de.justjanne.libquassel.protocol.syncables.state.CertManagerState
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
 import de.justjanne.libquassel.protocol.util.log.info
 import de.justjanne.libquassel.protocol.util.update
@@ -51,21 +54,23 @@ class ClientSession(
 ) : Session {
   override val side = ProtocolSide.CLIENT
 
-  private val rpcHandler = ClientRpcHandler(this)
-  private val heartBeatHandler = HeartBeatHandler()
+  override val rpcHandler = ClientRpcHandler(this)
+  override val heartBeatHandler = HeartBeatHandler()
   override val objectRepository = ObjectRepository()
   val handshakeHandler = ClientHandshakeHandler(this)
+  val baseInitHandler = BaseInitHandler(this)
   private val proxyMessageHandler = ClientProxyMessageHandler(
     heartBeatHandler,
     objectRepository,
-    rpcHandler
+    rpcHandler,
+    baseInitHandler
   )
-  private val magicHandler = ClientMagicHandler(protocolFeatures, protocols, sslContext)
   override val proxy = CommonSyncProxy(
     ProtocolSide.CLIENT,
     objectRepository,
     proxyMessageHandler
   )
+  private val magicHandler = ClientMagicHandler(protocolFeatures, protocols, sslContext)
   private val messageChannel = MessageChannel(connection)
 
   init {
@@ -76,15 +81,53 @@ class ClientSession(
   }
 
   override fun init(
-    identities: List<QVariantMap>,
+    identityInfo: List<QVariantMap>,
     bufferInfos: List<BufferInfo>,
     networkIds: List<NetworkId>
   ) {
     logger.info {
-      "Client session initialized: networks = $networkIds, buffers = $bufferInfos, identities = $identities"
+      "Client session initialized: networks = $networkIds, buffers = $bufferInfos, identities = $identityInfo"
+    }
+
+    bufferSyncer.initializeBufferInfos(bufferInfos)
+
+    val networks = networkIds.map {
+      Network(this@ClientSession, NetworkState(networkId = it))
+    }
+    val identities = identityInfo.map {
+      Identity(this@ClientSession).apply {
+        fromVariantMap(it)
+      }
+    }
+
+    state.update {
+      copy(
+        networks = networks.associateBy(Network::networkId),
+        identities = identities.associateBy(Identity::id),
+      )
     }
-    objectRepository.add(state().coreInfo)
-    objectRepository.add(state().backlogManager)
+
+    for (network in networks) {
+      baseInitHandler.sync(network)
+    }
+    for (identity in identities) {
+      baseInitHandler.sync(identity)
+      baseInitHandler.sync(CertManager(this, CertManagerState(identityId = identity.id())))
+    }
+    baseInitHandler.sync(state().aliasManager)
+    baseInitHandler.sync(state().bufferSyncer)
+    baseInitHandler.sync(state().bufferViewManager)
+    baseInitHandler.sync(state().coreInfo)
+    if (messageChannel.negotiatedFeatures.hasFeature(QuasselFeature.DccFileTransfer)) {
+      baseInitHandler.sync(state().dccConfig)
+    }
+    baseInitHandler.sync(state().ignoreListManager)
+    if (messageChannel.negotiatedFeatures.hasFeature(QuasselFeature.CoreSideHighlights)) {
+      baseInitHandler.sync(state().highlightRuleManager)
+    }
+    baseInitHandler.sync(state().ircListHelper)
+    baseInitHandler.sync(state().networkConfig)
+    baseInitHandler.sync(state().backlogManager)
   }
 
   override fun network(id: NetworkId) = state().networks[id]
@@ -104,6 +147,8 @@ class ClientSession(
     }
   }
 
+  override fun networks() = state().networks.values.toSet()
+
   override fun identity(id: IdentityId) = state().identities[id]
 
   override fun addIdentity(properties: QVariantMap) {
@@ -123,6 +168,12 @@ class ClientSession(
     }
   }
 
+  override fun identities() = state().identities.values.toSet()
+
+  override fun certManager(id: IdentityId) = state().certManagers[id]
+
+  override fun certManagers() = state().certManagers.values.toSet()
+
   override fun rename(className: String, oldName: String, newName: String) {
     rpcHandler.objectRenamed(
       StringSerializerUtf8.serializeRaw(className),
@@ -162,6 +213,7 @@ class ClientSession(
     ClientSessionState(
       networks = mapOf(),
       identities = mapOf(),
+      certManagers = mapOf(),
       aliasManager = AliasManager(this),
       backlogManager = ClientBacklogManager(this),
       bufferSyncer = BufferSyncer(this),
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSessionState.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSessionState.kt
index d03123d..3103847 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSessionState.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/session/ClientSessionState.kt
@@ -15,6 +15,7 @@ import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.syncables.common.AliasManager
 import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
 import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CertManager
 import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
 import de.justjanne.libquassel.protocol.syncables.common.DccConfig
 import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
@@ -27,6 +28,7 @@ import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
 data class ClientSessionState(
   val networks: Map<NetworkId, Network>,
   val identities: Map<IdentityId, Identity>,
+  val certManagers: Map<IdentityId, CertManager>,
   val aliasManager: AliasManager,
   val backlogManager: ClientBacklogManager,
   val bufferSyncer: BufferSyncer,
diff --git a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/ClientTest.kt b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/ClientTest.kt
index b9b1ea7..21b4eae 100644
--- a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/ClientTest.kt
+++ b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/ClientTest.kt
@@ -23,11 +23,11 @@ import de.justjanne.testcontainersci.api.providedContainer
 import de.justjanne.testcontainersci.extension.CiContainers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.assertThrows
 import java.net.InetSocketAddress
 import javax.net.ssl.SSLContext
-import kotlin.test.assertTrue
 
 @ExperimentalCoroutinesApi
 @CiContainers
@@ -36,17 +36,13 @@ class ClientTest {
     QuasselCoreContainer()
   }
 
-  private val sslContext = SSLContext.getInstance("TLSv1.3").apply {
-    init(null, arrayOf(TestX509TrustManager), null)
-  }
-
-  private val channel = CoroutineChannel()
-
-  private val username = "AzureDiamond"
-  private val password = "hunter2"
+  private val username = "kuschku"
+  private val password = "goalielecturetrawl"
 
   @Test
   fun testConnect(): Unit = runBlocking {
+    val channel = CoroutineChannel()
+
     channel.connect(
       InetSocketAddress(
         quassel.address,
@@ -62,7 +58,9 @@ class ClientTest {
           0x0000u
         )
       ),
-      sslContext
+      SSLContext.getInstance("TLSv1.3").apply {
+        init(null, arrayOf(TestX509TrustManager), null)
+      }
     )
     val coreState: CoreState = session.handshakeHandler.init(
       "Quasseltest v0.1",
@@ -92,6 +90,7 @@ class ClientTest {
       session.handshakeHandler.login("acidburn", "ineverweardresses")
     }
     session.handshakeHandler.login(username, password)
+    session.baseInitHandler.waitForInitDone()
     channel.close()
   }
 }
diff --git a/libquassel-client/src/test/resources/simplelogger.properties b/libquassel-client/src/test/resources/simplelogger.properties
index 594f59e..1bf2717 100644
--- a/libquassel-client/src/test/resources/simplelogger.properties
+++ b/libquassel-client/src/test/resources/simplelogger.properties
@@ -7,4 +7,4 @@
 # obtain one at https://mozilla.org/MPL/2.0/.
 #
 
-org.slf4j.simpleLogger.defaultLogLevel=debug
+org.slf4j.simpleLogger.defaultLogLevel=trace
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt
index 4e12743..75edaf3 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt
@@ -19,6 +19,7 @@ import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import org.threeten.bp.Instant
+import org.threeten.bp.ZoneOffset
 
 data class ConnectedClient(
   val id: Int,
@@ -51,7 +52,7 @@ data class ConnectedClient(
       versionDate = properties["clientVersionDate"].into("")
         .toLongOrNull()
         ?.let(Instant::ofEpochSecond),
-      connectedSince = properties["connectedSince"].into(Instant.EPOCH),
+      connectedSince = properties["connectedSince"].into(Instant.EPOCH.atOffset(ZoneOffset.UTC)).toInstant(),
       secure = properties["secure"].into(false),
       features = FeatureSet.build(
         LegacyFeature.of(properties["features"].into()),
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QDateTimeSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QDateTimeSerializer.kt
index 80b261e..6ca4bb6 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QDateTimeSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QDateTimeSerializer.kt
@@ -16,6 +16,7 @@ import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
 import org.threeten.bp.Instant
 import org.threeten.bp.LocalDateTime
 import org.threeten.bp.OffsetDateTime
+import org.threeten.bp.ZoneId
 import org.threeten.bp.ZoneOffset
 import org.threeten.bp.ZonedDateTime
 import org.threeten.bp.temporal.Temporal
@@ -51,7 +52,7 @@ object QDateTimeSerializer : PrimitiveSerializer<Temporal> {
     }
   }
 
-  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): Temporal {
+  override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): OffsetDateTime {
     val julianDay = QDateSerializer.deserialize(buffer, featureSet)
     val localTime = QTimeSerializer.deserialize(buffer, featureSet)
     val localDateTime = LocalDateTime.of(julianDay, localTime)
@@ -61,7 +62,7 @@ object QDateTimeSerializer : PrimitiveSerializer<Temporal> {
       TimeSpec.LocalStandard,
       TimeSpec.LocalUnknown,
       TimeSpec.LocalDST ->
-        localDateTime
+        localDateTime.atZone(ZoneId.systemDefault()).toOffsetDateTime()
       TimeSpec.OffsetFromUTC ->
         localDateTime
           .atOffset(
@@ -72,7 +73,6 @@ object QDateTimeSerializer : PrimitiveSerializer<Temporal> {
       TimeSpec.UTC ->
         localDateTime
           .atOffset(ZoneOffset.UTC)
-          .toInstant()
     }
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QTimeSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QTimeSerializer.kt
index 4f921ef..a09bd4d 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QTimeSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/qt/QTimeSerializer.kt
@@ -28,6 +28,10 @@ object QTimeSerializer : PrimitiveSerializer<LocalTime> {
 
   override fun deserialize(buffer: ByteBuffer, featureSet: FeatureSet): LocalTime {
     val millisecondOfDay = IntSerializer.deserialize(buffer, featureSet).toLong()
-    return LocalTime.ofNanoOfDay(millisecondOfDay * 1_000_000)
+    try {
+      return LocalTime.ofNanoOfDay(millisecondOfDay * 1_000_000)
+    } catch (e: Exception) {
+      return LocalTime.MAX
+    }
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt
index a377cf6..7c2837c 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/HeartBeatSerializer.kt
@@ -16,6 +16,7 @@ import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import org.threeten.bp.Instant
+import org.threeten.bp.ZoneOffset
 
 /**
  * Serializer for [SignalProxyMessage.HeartBeat]
@@ -29,6 +30,6 @@ object HeartBeatSerializer : SignalProxySerializer<SignalProxyMessage.HeartBeat>
   )
 
   override fun deserialize(data: QVariantList) = SignalProxyMessage.HeartBeat(
-    data.getOrNull(1).into(Instant.EPOCH)
+    data.getOrNull(1).into(Instant.EPOCH.atOffset(ZoneOffset.UTC)).toInstant()
   )
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt
index 9ab6016..3681fa9 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializer.kt
@@ -35,6 +35,6 @@ object InitDataSerializer : SignalProxySerializer<SignalProxyMessage.InitData> {
   override fun deserialize(data: QVariantList) = SignalProxyMessage.InitData(
     StringSerializerUtf8.deserializeRaw(data.getOrNull(1).into<ByteBuffer>()),
     StringSerializerUtf8.deserializeRaw(data.getOrNull(2).into<ByteBuffer>()),
-    data.drop(3).toVariantMap()
+    data.drop(3).toVariantMap(byteBuffer = true)
   )
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CommonSyncProxy.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CommonSyncProxy.kt
index de1d9a6..ffd0cc8 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CommonSyncProxy.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/CommonSyncProxy.kt
@@ -14,6 +14,7 @@ import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.syncables.ObjectRepository
 import de.justjanne.libquassel.protocol.syncables.SyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantList
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 
 class CommonSyncProxy(
@@ -22,9 +23,9 @@ class CommonSyncProxy(
   private val proxyMessageHandler: ProxyMessageHandler
 ) : SyncProxy {
   override fun synchronize(syncable: SyncableStub) {
-    if (objectRepository.add(syncable)) {
-      runBlocking {
-        proxyMessageHandler.dispatch(SignalProxyMessage.InitRequest(syncable.className, syncable.objectName))
+    if (objectRepository.add(syncable) && !syncable.initialized) {
+      runBlocking(context = Dispatchers.IO) {
+        proxyMessageHandler.emit(SignalProxyMessage.InitRequest(syncable.className, syncable.objectName))
       }
     }
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt
index 23376b0..80a8b6b 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/ConnectionHandler.kt
@@ -13,5 +13,6 @@ import java.nio.ByteBuffer
 
 interface ConnectionHandler {
   suspend fun init(channel: MessageChannel): Boolean
+  suspend fun done()
   suspend fun read(buffer: ByteBuffer): Boolean
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt
index 9bb1fed..8394110 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/MessageChannel.kt
@@ -55,7 +55,8 @@ class MessageChannel(
     dispatch(messageBuffer)
   }
 
-  private suspend fun setupHandlers() {
+  private suspend fun setupHandlers(): List<ConnectionHandler> {
+    val removed = mutableListOf<ConnectionHandler>()
     while (true) {
       val handler = handlers.firstOrNull()
       logger.trace { "Setting up handler $handler" }
@@ -63,18 +64,27 @@ class MessageChannel(
         break
       }
       logger.trace { "Handler $handler is done" }
-      handlers.removeFirst()
+      removed.add(handlers.removeFirst())
     }
     if (handlers.isEmpty()) {
       logger.trace { "All handlers done" }
       channel.close()
     }
+    return removed
   }
 
   private suspend fun dispatch(message: ByteBuffer) {
-    if (handlers.first().read(message)) {
-      handlers.removeFirst()
-      setupHandlers()
+    val handlerDone = try {
+      handlers.first().read(message)
+    } catch (e: Exception) {
+      logger.warn("Error while handling message: ", e)
+      false
+    }
+    if (handlerDone) {
+      val removed = listOf(handlers.removeFirst()) + setupHandlers()
+      for (handler in removed) {
+        handler.done()
+      }
     }
   }
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/Session.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/Session.kt
index 056a86b..63f32d1 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/Session.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/session/Session.kt
@@ -13,11 +13,13 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.protocol.models.BufferInfo
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.HeartBeatHandler
 import de.justjanne.libquassel.protocol.syncables.ObjectRepository
 import de.justjanne.libquassel.protocol.syncables.common.AliasManager
 import de.justjanne.libquassel.protocol.syncables.common.BacklogManager
 import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
 import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CertManager
 import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
 import de.justjanne.libquassel.protocol.syncables.common.DccConfig
 import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
@@ -26,6 +28,7 @@ import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
 import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
 import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
+import de.justjanne.libquassel.protocol.syncables.common.RpcHandler
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 
 interface Session {
@@ -34,7 +37,7 @@ interface Session {
   val objectRepository: ObjectRepository
 
   fun init(
-    identities: List<QVariantMap>,
+    identityInfo: List<QVariantMap>,
     bufferInfos: List<BufferInfo>,
     networkIds: List<NetworkId>
   )
@@ -42,13 +45,21 @@ interface Session {
   fun network(id: NetworkId): Network?
   fun addNetwork(id: NetworkId)
   fun removeNetwork(id: NetworkId)
+  fun networks(): Set<Network>
 
   fun identity(id: IdentityId): Identity?
   fun addIdentity(properties: QVariantMap)
   fun removeIdentity(id: IdentityId)
+  fun identities(): Set<Identity>
+
+  fun certManager(id: IdentityId): CertManager?
+  fun certManagers(): Set<CertManager>
 
   fun rename(className: String, oldName: String, newName: String)
 
+  val heartBeatHandler: HeartBeatHandler
+  val rpcHandler: RpcHandler
+
   val aliasManager: AliasManager
   val backlogManager: BacklogManager
   val bufferSyncer: BufferSyncer
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt
index 706d6bf..e459daa 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt
@@ -15,4 +15,8 @@ import de.justjanne.libquassel.protocol.syncables.stubs.BacklogManagerStub
 
 open class BacklogManager(
   session: Session? = null
-) : SyncableObject(session, "BacklogManager"), BacklogManagerStub
+) : SyncableObject(session, "BacklogManager"), BacklogManagerStub {
+  init {
+    initialized = true
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt
index 52b62f4..1db9f81 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt
@@ -28,6 +28,7 @@ import de.justjanne.libquassel.protocol.util.collections.pairs
 import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.QVariant_
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 
@@ -107,6 +108,12 @@ open class BufferSyncer(
     initialized = true
   }
 
+  fun initializeBufferInfos(infos: List<BufferInfo>) {
+    state.update {
+      copy(bufferInfos = infos.associateBy(BufferInfo::bufferId))
+    }
+  }
+
   fun lastSeenMsg(buffer: BufferId): MsgId = state().lastSeenMsg[buffer] ?: MsgId(0)
 
   fun markerLine(buffer: BufferId): MsgId = state().markerLines[buffer] ?: MsgId(0)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
index a0498d9..769844b 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
@@ -35,6 +35,10 @@ open class BufferViewConfig(
   state: BufferViewConfigState
 ) : StatefulSyncableObject<BufferViewConfigState>(session, "BufferViewConfig", state),
   BufferViewConfigStub {
+  init {
+    renameObject(state().identifier())
+  }
+
   override fun fromVariantMap(properties: QVariantMap) {
     state.update {
       copy(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
index c52bdc5..1c36265 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
@@ -33,6 +33,10 @@ open class CertManager(
   state: CertManagerState
 ) : StatefulSyncableObject<CertManagerState>(session, "CertManager", state),
   CertManagerStub {
+  init {
+    renameObject(state().identifier())
+  }
+
   override fun fromVariantMap(properties: QVariantMap) {
     val privateKeyPem = properties["sslKey"].into("")
     val certPem = properties["sslCert"].into("")
@@ -45,6 +49,7 @@ open class CertManager(
         certificate = readCertificate(certPem)
       )
     }
+    renameObject(state().identifier())
     initialized = true
   }
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
index 68226de..5dde098 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
@@ -22,6 +22,7 @@ import de.justjanne.libquassel.protocol.variant.QVariant_
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import org.threeten.bp.Instant
+import org.threeten.bp.ZoneOffset
 
 open class CoreInfo(
   session: Session? = null,
@@ -37,7 +38,7 @@ open class CoreInfo(
         versionDate = coreData["quasselBuildDate"].into("")
           .toLongOrNull()
           ?.let(Instant::ofEpochSecond),
-        startTime = coreData["startTime"].into(startTime),
+        startTime = coreData["startTime"].into(startTime.atOffset(ZoneOffset.UTC)).toInstant(),
         connectedClientCount = coreData["sessionConnectedClients"].into(connectedClientCount),
         connectedClients = coreData["sessionConnectedClientData"].into<QVariantList>()
           ?.mapNotNull<QVariant_, QVariantMap>(QVariant_::into)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt
index 72fc263..e2b543f 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt
@@ -15,4 +15,8 @@ import de.justjanne.libquassel.protocol.syncables.stubs.IrcListHelperStub
 
 open class IrcListHelper(
   session: Session? = null
-) : SyncableObject(session, "IrcListHelper"), IrcListHelperStub
+) : SyncableObject(session, "IrcListHelper"), IrcListHelperStub {
+  init {
+    initialized = true
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt
index 7cf374b..7870c59 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt
@@ -21,6 +21,8 @@ import de.justjanne.libquassel.protocol.variant.indexed
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import org.threeten.bp.Instant
+import org.threeten.bp.OffsetDateTime
+import org.threeten.bp.ZoneOffset
 import org.threeten.bp.temporal.Temporal
 
 open class IrcUser(
@@ -45,11 +47,11 @@ open class IrcUser(
         account = properties["account"].indexed(index).into(account),
         away = properties["away"].indexed(index).into(away),
         awayMessage = properties["awayMessage"].indexed(index).into(awayMessage),
-        idleTime = properties["idleTime"].indexed(index).into(idleTime),
-        loginTime = properties["loginTime"].indexed(index).into(loginTime),
+        idleTime = properties["idleTime"].indexed(index).into(idleTime.atOffset(ZoneOffset.UTC)).toInstant(),
+        loginTime = properties["loginTime"].indexed(index).into(loginTime.atOffset(ZoneOffset.UTC)).toInstant(),
         server = properties["server"].indexed(index).into(server),
         ircOperator = properties["ircOperator"].indexed(index).into(ircOperator),
-        lastAwayMessageTime = properties["lastAwayMessageTime"].indexed(index).into()
+        lastAwayMessageTime = properties["lastAwayMessageTime"].indexed(index).into<OffsetDateTime>()?.toInstant()
           ?: properties["lastAwayMessage"].indexed(index).into<Int>()?.toLong()
             ?.let(Instant::ofEpochSecond)
           ?: lastAwayMessageTime,
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt
index 141716a..760d7d3 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt
@@ -30,4 +30,6 @@ data class BufferViewConfigState(
   val buffers: List<BufferId> = emptyList(),
   val removedBuffers: Set<BufferId> = emptySet(),
   val hiddenBuffers: Set<BufferId> = emptySet(),
-)
+) {
+  fun identifier() = "$bufferViewId"
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt
index 18e9f3c..ea6bb8b 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt
@@ -19,4 +19,6 @@ data class CertManagerState(
   val certificatePem: String = "",
   val privateKey: PrivateKey? = null,
   val certificate: Certificate? = null
-)
+) {
+  fun identifier() = "${identityId.id}"
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
index 38f03d7..5022ca6 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
@@ -18,6 +18,8 @@ import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
 import de.justjanne.libquassel.protocol.util.reflect.instanceof
 import de.justjanne.libquassel.protocol.util.reflect.subtype
 import java.nio.ByteBuffer
+import org.threeten.bp.Instant
+import org.threeten.bp.ZoneOffset
 
 /**
  * Simple alias for a generic QVariant type
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt
index eba9cee..4ef290d 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariantList.kt
@@ -9,7 +9,9 @@
 
 package de.justjanne.libquassel.protocol.variant
 
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
 import de.justjanne.libquassel.protocol.util.collections.pairs
+import java.nio.ByteBuffer
 
 /**
  * Simple alias for a generic QVariantList type
@@ -19,7 +21,11 @@ typealias QVariantList = List<QVariant_>
 /**
  * Transform a QVariantList of interleaved keys and values into a QVariantMap
  */
-fun QVariantList.toVariantMap(): QVariantMap =
+fun QVariantList.toVariantMap(byteBuffer: Boolean = false): QVariantMap =
   pairs { key, value ->
-    Pair(key.into(""), value)
+    if (byteBuffer) {
+      Pair(StringSerializerUtf8.deserializeRaw(key.into<ByteBuffer>()), value)
+    } else {
+      Pair(key.into(""), value)
+    }
   }.toMap()
-- 
GitLab