diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
index 8b8518b04..f02238579 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
@@ -7,20 +7,26 @@ import net.mamoe.mirai.data.AddFriendResult
 import net.mamoe.mirai.data.ImageLink
 import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
+import net.mamoe.mirai.qqandroid.network.QQAndroidClient
+import net.mamoe.mirai.qqandroid.utils.Context
 import net.mamoe.mirai.utils.BotConfiguration
 import net.mamoe.mirai.utils.MiraiInternalAPI
 import kotlin.coroutines.CoroutineContext
 
 internal expect class QQAndroidBot(
+    context: Context,
     account: BotAccount,
     configuration: BotConfiguration
 ) : QQAndroidBotBase
 
 @UseExperimental(MiraiInternalAPI::class)
 internal abstract class QQAndroidBotBase constructor(
+    context: Context,
     account: BotAccount,
     configuration: BotConfiguration
 ) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
+    val client: QQAndroidClient = QQAndroidClient(context, account)
+
     override val qqs: ContactList<QQ>
         get() = TODO("not implemented")
 
@@ -47,10 +53,6 @@ internal abstract class QQAndroidBotBase constructor(
         TODO("not implemented")
     }
 
-    override suspend fun login() {
-        TODO("not implemented")
-    }
-
     override suspend fun Image.getLink(): ImageLink {
         TODO("not implemented")
     }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
index 7a0c51b91..45d6d7227 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
@@ -1,21 +1,90 @@
 package net.mamoe.mirai.qqandroid.network
 
-import kotlinx.coroutines.CompletableJob
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.*
+import kotlinx.io.core.*
+import kotlinx.io.pool.useInstance
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
+import net.mamoe.mirai.utils.io.*
 import kotlin.coroutines.CoroutineContext
 
 internal class QQAndroidBotNetworkHandler(override val bot: QQAndroidBot) : BotNetworkHandler() {
     override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
 
+    private val channel: PlatformDatagramChannel = PlatformDatagramChannel("wtlogin.qq.com", 8000)
+
     override suspend fun login() {
-        TODO("not implemented")
+        launch { processReceive() }
+
+        val buffer = IoBuffer.Pool.borrow()
+        buffer.writePacket(LoginPacket(bot.client).delegate)
+        val shouldBeSent = buffer.readRemaining
+        check(channel.send(buffer) == shouldBeSent) {
+            "Buffer is not entirely sent. " +
+                    "Required sent length=$shouldBeSent, but after channel.send, " +
+                    "buffer remains ${buffer.readBytes().toUHexString()}"
+        }
+        buffer.release(IoBuffer.Pool)
+        println("Login sent")
+    }
+
+    private suspend fun processReceive() {
+        while (channel.isOpen) {
+            val buffer = IoBuffer.Pool.borrow()
+
+            try {
+                channel.read(buffer)// JVM: withContext(IO)
+            } catch (e: ClosedChannelException) {
+                dispose()
+                return
+            } catch (e: ReadPacketInternalException) {
+                bot.logger.error("Socket channel read failed: ${e.message}")
+                continue
+            } catch (e: CancellationException) {
+                return
+            } catch (e: Throwable) {
+                bot.logger.error("Caught unexpected exceptions", e)
+                continue
+            } finally {
+                if (!buffer.canRead() || buffer.readRemaining == 0) {//size==0
+                    //bot.logger.debug("processReceive: Buffer cannot be read")
+                    buffer.release(IoBuffer.Pool)
+                    continue
+                }// sometimes exceptions are thrown without this `if` clause
+            }
+
+            //buffer.resetForRead()
+            launch(CoroutineName("handleServerPacket")) {
+                // `.use`: Ensure that the packet is consumed **totally**
+                // so that all the buffers are released
+                ByteArrayPool.useInstance {
+                    val length = buffer.readRemaining - 1
+                    buffer.readFully(it, 0, length)
+                    buffer.resetForWrite()
+                    buffer.writeFully(it, 0, length)
+                }
+                ByteReadPacket(buffer, IoBuffer.Pool).use { input ->
+                    try {
+                        input.debugPrint("Received")
+                    } catch (e: Exception) {
+                        bot.logger.error(e)
+                    }
+                }
+            }
+        }
     }
 
     override suspend fun awaitDisconnection() {
-        TODO()
+        while (true) {
+            delay(100)
+            // TODO: 2019/12/31
+        }
+    }
+
+    override fun dispose(cause: Throwable?) {
+        println("Closed")
+        super.dispose(cause)
     }
 
     override val coroutineContext: CoroutineContext = bot.coroutineContext
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
index ddc341c44..79ccf9324 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
@@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.network
 import kotlinx.io.core.toByteArray
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.qqandroid.utils.*
+import net.mamoe.mirai.utils.io.hexToBytes
 
 /*
  APP ID:
@@ -41,7 +42,7 @@ internal open class QQAndroidClient(
 
     var networkType: NetworkType = NetworkType.WIFI
 
-    val apkSignatureMd5: ByteArray = TODO()
+    val apkSignatureMd5: ByteArray = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes()
 
     /**
      * 协议版本?, 8.2.0 的为 8001
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt
index b495a6ef7..6b3dff8ce 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt
@@ -96,6 +96,9 @@ fun BytePacketBuilder.t106(
     loginType: LoginType
 ) {
     writeShort(0x106)
+    passwordMd5.requireSize(16)
+    tgtgtKey.requireSize(16)
+    guid?.requireSize(16)
 
     writeShortLVPacket {
         encryptAndWrite(md5(passwordMd5 + (salt.takeIf { it != 0L } ?: uin).toInt().toByteArray())) {
@@ -132,7 +135,7 @@ fun BytePacketBuilder.t106(
             writeInt(loginType.value)
             writeShortLVByteArray(uinAccount) // TODO check if should be empty byte[]
         }
-    } shouldEqualsTo 98
+    }
 }
 
 fun BytePacketBuilder.t116(
@@ -620,7 +623,8 @@ fun BytePacketBuilder.t318(
 private fun Boolean.toByte(): Byte = if (this) 1 else 0
 private fun Boolean.toInt(): Int = if (this) 1 else 0
 
-private infix fun Int.shouldEqualsTo(int: Int) = require(this == int)
+private infix fun Int.shouldEqualsTo(int: Int) = require(this == int) { "Required $int, but found $this" }
+private fun ByteArray.requireSize(exactSize: Int) = require(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
 
 fun randomAndroidId(): String = buildString(15) {
     repeat(15) { append(Random.nextInt(10)) }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
index 220fe6768..28404d5ed 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
@@ -21,6 +21,9 @@ class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray {
 
 @UseExperimental(ExperimentalUnsignedTypes::class)
 internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, LoginPacketDecrypter>(LoginPacketDecrypter) {
+    init {
+        this._id = PacketId(0x0810, 9)
+    }
 
     operator fun invoke(
         client: QQAndroidClient
@@ -144,6 +147,15 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
     }
 }
 
+
+@Suppress("FunctionName")
+fun PacketId(commandId: Int, subCommandId: Int) = object : PacketId {
+    override val commandId: Int
+        get() = commandId
+    override val subCommandId: Int
+        get() = subCommandId
+}
+
 interface PacketId {
     val commandId: Int // ushort actually
     val subCommandId: Int // ushort actually
diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
index 03b0702e3..059749948 100644
--- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
@@ -1,15 +1,15 @@
 package net.mamoe.mirai.qqandroid
 
 import net.mamoe.mirai.BotAccount
-import net.mamoe.mirai.alsoLogin
+import net.mamoe.mirai.qqandroid.utils.Context
+import net.mamoe.mirai.qqandroid.utils.ContextImpl
 import net.mamoe.mirai.utils.BotConfiguration
 
+@Suppress("FunctionName")
+internal fun QQAndroidBot(account: BotAccount, configuration: BotConfiguration) = QQAndroidBot(ContextImpl(), account, configuration)
+
 internal actual class QQAndroidBot actual constructor(
+    context: Context,
     account: BotAccount,
     configuration: BotConfiguration
-) : QQAndroidBotBase(account, configuration)
-
-suspend fun main() {
-    val bot = QQAndroidBot(BotAccount(1, ""), BotConfiguration()).alsoLogin()
-    bot.network.awaitDisconnection()
-}
\ No newline at end of file
+) : QQAndroidBotBase(context, account, configuration)
\ No newline at end of file
diff --git a/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt b/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
index b68b2663c..823b491ec 100644
--- a/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
+++ b/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
@@ -16,13 +16,14 @@ import kotlinx.serialization.internal.ArrayListSerializer
 import kotlinx.serialization.json.Json
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.network.BotNetworkHandler
-
 import net.mamoe.mirai.timpc.TIMPC
 import net.mamoe.mirai.timpc.network.TIMProtocol
 import net.mamoe.mirai.timpc.network.packet.*
-import net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket
 import net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket
-import net.mamoe.mirai.timpc.network.packet.login.*
+import net.mamoe.mirai.timpc.network.packet.login.CaptchaKey
+import net.mamoe.mirai.timpc.network.packet.login.HeartbeatPacket
+import net.mamoe.mirai.timpc.network.packet.login.ShareKey
+import net.mamoe.mirai.timpc.network.packet.login.TouchKey
 import net.mamoe.mirai.utils.cryptor.Decrypter
 import net.mamoe.mirai.utils.cryptor.DecryptionFailedException
 import net.mamoe.mirai.utils.cryptor.NoDecrypter
@@ -127,7 +128,7 @@ suspend fun main() {
             listenDevice(localIp, it)
         }
         println("Using sessionKey = ${sessionKey.value.toUHexString()}")
-        println("Filter QQ = ${qq?.toLong()}")
+        println("Filter QQ = $qq")
         PacketDebugger.recorder?.let { println("Recorder is enabled") }
         Runtime.getRuntime().addShutdownHook(thread(false) {
             PacketDebugger.recorder?.writeTo(File(GMTDate().toString() + ".record"))?.also { println("${PacketDebugger.recorder.list.size} records saved.") }
@@ -183,8 +184,7 @@ internal object PacketDebugger {
      * 7. 运行完 `mov eax,dword ptr ss:[ebp+10]`
      * 8. 查看内存, `eax` 到 `eax+10` 的 16 字节就是 `sessionKey`
      */
-    val sessionKey: SessionKey =
-        SessionKey("D8 D0 B0 DE 37 53 9B 05 A5 E7 AB 96 B2 AC AD EC".hexToBytes())
+    val sessionKey: SessionKey get() = SessionKey("D8 D0 B0 DE 37 53 9B 05 A5 E7 AB 96 B2 AC AD EC".hexToBytes())
     // TODO: 2019/12/7 无法访问 internal 是 kotlin bug, KT-34849
 
     /**
@@ -197,9 +197,9 @@ internal object PacketDebugger {
     val recorder: Recorder? = Recorder()
 
     val IgnoredPacketIdList: List<PacketId> = listOf(
-        KnownPacketId.get<FriendOnlineStatusChangedPacket>(),
-        KnownPacketId.get<ChangeOnlineStatusPacket>(),
-        KnownPacketId.get<HeartbeatPacket>()
+        // KnownPacketId.get<FriendOnlineStatusChangedPacket>(),
+        // KnownPacketId.get<ChangeOnlineStatusPacket>(),
+        // KnownPacketId.get<HeartbeatPacket>()
     )
 
     suspend fun dataReceived(data: ByteArray) {
@@ -304,7 +304,7 @@ internal object PacketDebugger {
         // 3E 03 3F A2 02 00 00 00 01 2E 01 00 00 69 35
 
         discardExact(3)//head
-        val id = net.mamoe.mirai.timpc.network.packet.matchPacketId(readUShort())
+        val id = matchPacketId(readUShort())
         val sequence = readUShort().toUHexString()
         if (IgnoredPacketIdList.contains(id)) {
             return