From 0c6526afb809e9578b47d7ba495ffb712e7b2a0c Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Sun, 2 Feb 2020 17:35:42 +0800
Subject: [PATCH] Fix friendlist

---
 .../network/QQAndroidBotNetworkHandler.kt     | 27 +++++++++++----
 .../network/protocol/data/proto/SyncCookie.kt |  6 ++--
 .../network/protocol/packet/PacketFactory.kt  | 34 ++++++++++++++-----
 .../packet/chat/receive/MessageSvc.kt         |  7 ++--
 .../protocol/packet/list/FriendListPacket.kt  |  6 ++--
 .../net.mamoe.mirai/utils/io/ByteArrayPool.kt |  2 +-
 6 files changed, 58 insertions(+), 24 deletions(-)

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 1d8b43a65..42e29a2f7 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
@@ -29,6 +29,7 @@ import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.unsafeWeakRef
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.jvm.Volatile
 
 @Suppress("MemberVisibilityCanBePrivate")
 @UseExperimental(MiraiInternalAPI::class)
@@ -123,7 +124,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
                 val data = FriendList.GetFriendGroupList(
                     bot.client,
                     currentFriendCount,
-                    20,
+                    150,
                     0,
                     0
                 ).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 1000)
@@ -161,6 +162,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     /**
      * 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理
      */
+    @Volatile
     private var cachedPacketTimeoutJob: Job? = null
     /**
      * 缓存的包
@@ -169,6 +171,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     /**
      * 缓存的包还差多少长度
      */
+    @Volatile
     private var expectingRemainingLength: Long = 0
 
     /**
@@ -256,8 +259,11 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
         if (cache == null) {
             // 没有缓存
             var length: Int = rawInput.readInt() - 4
-            if (length < 0) {
+            if (length and 0xFFFF != length) {
+                cachedPacket.value = rawInput
+                expectingRemainingLength = length.toLong() and 0xFFFF
                 // 丢包了. 后半部分包提前到达
+                PacketLogger.error { "丢包了." }
                 return
             }
             if (rawInput.remaining == length.toLong()) {
@@ -267,7 +273,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
                 return
             }
             // 循环所有完整的包
-            while (rawInput.remaining > length) {
+            while (rawInput.remaining >= length) {
                 parsePacketAsync(rawInput.readPacket(length))
 
                 length = rawInput.readInt() - 4
@@ -284,20 +290,27 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
             }
         } else {
             // 有缓存
-
-            if (rawInput.remaining >= expectingRemainingLength) {
+            val expectingLength = expectingRemainingLength
+            if (expectingLength and 0xFFFF != expectingLength) {
+                processPacket(buildPacket {
+                    writePacket(rawInput)
+                    writeInt(expectingLength.toInt())
+                    writePacket(cache)
+                })
+            }
+            if (rawInput.remaining >= expectingLength) {
                 // 剩余长度够, 连接上去, 处理这个包.
                 parsePacketAsync(buildPacket {
                     writePacket(cache)
-                    writePacket(rawInput, expectingRemainingLength)
+                    writePacket(rawInput, expectingLength)
                 })
                 cachedPacket.value = null // 缺少的长度已经给上了.
+                cachedPacketTimeoutJob?.cancel()
 
                 if (rawInput.remaining != 0L) {
                     return processPacket(rawInput) // 继续处理剩下内容
                 } else {
                     // 处理好了.
-                    cachedPacketTimeoutJob?.cancel()
                     return
                 }
             } else {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt
index bfccab92c..6a0411386 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt
@@ -11,13 +11,15 @@ class SyncCookie(
     @SerialId(2) val time: Long, // 1580277992
     @SerialId(3) val unknown1: Long = Random.nextLong(),// 678328038
     @SerialId(4) val unknown2: Long = Random.nextLong(), // 1687142153
-    @SerialId(5) val const1: Long = Random.nextLong(), // 1458467940
-    @SerialId(11) val const2: Long = Random.nextLong(), // 2683038258
+    @SerialId(5) val const1: Long = const1_, // 1458467940
+    @SerialId(11) val const2: Long = const2_, // 2683038258
     @SerialId(12) val unknown3: Long = 0x1d,
     @SerialId(13) val lastSyncTime: Long? = null,
     @SerialId(14) val unknown4: Long = 0
 ) : ProtoBuf
 
+private val const1_: Long = Random.nextLong()
+private val const2_: Long = Random.nextLong()
 /*
 
 @Serializable
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
index cc1e30ba7..dd6a1c7c6 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
@@ -147,6 +147,7 @@ internal object KnownPacketFactories {
         // login
         val flag1 = readInt()
 
+        PacketLogger.verbose("开始处理一个包")
         PacketLogger.verbose("flag1(0A/0B) = ${flag1.toUByte().toUHexString()}")
         // 00 00 05 30
         // 00 00 00 0A // flag 1
@@ -240,10 +241,13 @@ internal object KnownPacketFactories {
         val ssoSequenceId: Int
         val dataCompressed: Int
         // head
-        input.readIoBuffer(input.readInt() - 4).withUse {
+        input.readPacket(input.readInt() - 4).withUse {
             ssoSequenceId = readInt()
             PacketLogger.verbose("sequenceId = $ssoSequenceId")
-            check(readInt() == 0)
+            val returnCode = readInt()
+            if (returnCode != 0) {
+                error("returnCode = $returnCode")
+            }
             val extraData = readBytes(readInt() - 4)
             PacketLogger.verbose("(sso/inner)extraData = ${extraData.toUHexString()}")
 
@@ -280,7 +284,7 @@ internal object KnownPacketFactories {
         ssoSequenceId: Int,
         consumer: PacketConsumer<T>
     ) {
-        readIoBuffer(readInt() - 4).withUse {
+        readPacket(readInt() - 4).withUse {
             check(readByte().toInt() == 2)
             this.discardExact(2) // 27 + 2 + body.size
             this.discardExact(2) // const, =8001
@@ -292,30 +296,30 @@ internal object KnownPacketFactories {
             this.discardExact(1) // const = 0
             val packet = when (encryptionMethod) {
                 4 -> { // peer public key, ECDH
-                    var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, this.readRemaining - 1)
+                    var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())
 
                     val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey())
                     data = data.decryptBy(peerShareKey)
 
-                    packetFactory.decode(bot, data.toReadPacket())
+                    packetFactory.decode(bot, data)
                 }
                 0 -> {
                     val data = if (bot.client.loginState == 0) {
                         ByteArrayPool.useInstance { byteArrayBuffer ->
-                            val size = this.readRemaining - 1
+                            val size = (this.remaining - 1).toInt()
                             this.readFully(byteArrayBuffer, 0, size)
 
                             runCatching {
                                 byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size)
                             }.getOrElse {
                                 byteArrayBuffer.decryptBy(bot.client.randomKey, size)
-                            } // 这里实际上应该用 privateKey(另一个random出来的key)
+                            }.toReadPacket() // 这里实际上应该用 privateKey(另一个random出来的key)
                         }
                     } else {
-                        this.decryptBy(bot.client.randomKey, 0, this.readRemaining - 1)
+                        this.decryptBy(bot.client.randomKey, 0, (this.remaining - 1).toInt())
                     }
 
-                    packetFactory.decode(bot, data.toReadPacket())
+                    packetFactory.decode(bot, data)
 
                 }
                 else -> error("Illegal encryption method. expected 0 or 4, got $encryptionMethod")
@@ -337,4 +341,16 @@ internal inline fun <I : IoBuffer, R> I.withUse(block: I.() -> R): R {
     } finally {
         this.release(IoBuffer.Pool)
     }
+}
+
+@UseExperimental(ExperimentalContracts::class)
+internal inline fun <I : ByteReadPacket, R> I.withUse(block: I.() -> R): R {
+    contract {
+        callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
+    }
+    return try {
+        block(this)
+    } finally {
+        this.close()
+    }
 }
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
index 3b93a559e..8990c7d96 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
@@ -19,7 +19,10 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
-import net.mamoe.mirai.qqandroid.network.protocol.packet.*
+import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
 import net.mamoe.mirai.qqandroid.utils.toMessageChain
 import net.mamoe.mirai.qqandroid.utils.toRichTextElems
 import net.mamoe.mirai.utils.MiraiInternalAPI
@@ -229,7 +232,7 @@ internal class MessageSvc {
                     ),
                     msgSeq = client.atomicNextMessageSequenceId(),
                     //msgRand = Random.nextInt() and 0x7FFF,
-                    syncCookie = client.c2cMessageSync.syncCookie?.takeIf { it.isNotEmpty() } ?: EMPTY_BYTE_ARRAY
+                    syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
                     //SyncCookie(currentTimeSeconds, Random.nextLong().absoluteValue, Random.nextLong().absoluteValue).toByteArray(SyncCookie.serializer())
                     // msgVia = 1
                 )
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
index ca27136d0..fddb08186 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
@@ -16,7 +16,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
 import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
 import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
-import net.mamoe.mirai.utils.io.discardExact
+import net.mamoe.mirai.utils.io.debugIfFail
 
 
 internal class FriendList {
@@ -120,8 +120,8 @@ internal class FriendList {
         }
 
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
-            this.discardExact(4)
-            val res = this.decodeUniPacket(GetFriendListResp.serializer())
+            //this.discardExact(4)
+            val res = this.debugIfFail { this.decodeUniPacket(GetFriendListResp.serializer()) }
             return Response(
                 res.totoalFriendCount,
                 res.vecFriendInfo.orEmpty()
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt
index 27ad698bf..87a0c3bcb 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt
@@ -4,7 +4,7 @@ import kotlinx.io.pool.DefaultPool
 import kotlinx.io.pool.ObjectPool
 
 internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256
-internal const val DEFAULT_BYTE_ARRAY_SIZE = 8192
+internal const val DEFAULT_BYTE_ARRAY_SIZE = 81920
 
 val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl