From d7f67e5159f06df5f98f2c0ef138b7d549564ed9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 11 Jan 2020 15:30:19 +0800 Subject: [PATCH] Fix packet parsing --- .../network/QQAndroidBotNetworkHandler.kt | 46 ++++++++--------- .../qqandroid/network/QQAndroidClient.kt | 45 +++++++++++++---- .../protocol/packet/OutgoingPacketAndroid.kt | 4 +- .../network/protocol/packet/PacketFactory.kt | 7 ++- .../qqandroid/network/protocol/packet/Tlv.kt | 5 ++ .../protocol/packet/login/LoginPacket.kt | 48 ++++++++++++------ .../mamoe/mirai/qqandroid/utils/DeviceInfo.kt | 7 +++ .../mamoe/mirai/utils/cryptor/ECDHAndroid.kt | 4 ++ .../mirai/utils/cryptor/contentToString.kt | 18 +++++++ .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 4 +- .../net.mamoe.mirai/utils/cryptor/ECDH.kt | 2 + .../net.mamoe.mirai/utils/cryptor/Proto.kt | 39 +++++++++++++-- .../net.mamoe.mirai/utils/io/InputUtils.kt | 49 ++++++++++++------- .../utils/io/TypeConversion.kt | 4 +- .../net/mamoe/mirai/utils/cryptor/ECDHJvm.kt | 4 ++ .../mirai/utils/cryptor/contentToString.kt | 31 ++++++++++++ 16 files changed, 244 insertions(+), 73 deletions(-) create mode 100644 mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt create mode 100644 mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt 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 3696242dd..cf40a7e7d 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,6 +1,7 @@ package net.mamoe.mirai.qqandroid.network import kotlinx.coroutines.* +import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.use import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.broadcast @@ -36,6 +37,27 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler LoginPacket.SubCommand9(bot.client).sendAndExpect() } + internal fun launchPacketProcessor(rawInput: ByteReadPacket): Job { + return launch(CoroutineName("Incoming Packet handler")) { + rawInput.debugPrint("Received").use { input -> + if (input.remaining == 0L) { + bot.logger.error("Empty packet received. Consider if bad packet was sent.") + return@launch + } + KnownPacketFactories.parseIncomingPacket(bot, input) { packet: Packet, packetId: PacketId, sequenceId: Int -> + if (PacketReceivedEvent(packet).broadcast().cancelled) { + return@parseIncomingPacket + } + packetListeners.forEach { listener -> + if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) { + listener.complete(packet) + } + } + } + } + } + } + private suspend fun processReceive() { while (channel.isOpen) { val rawInput = try { @@ -52,25 +74,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler bot.logger.error("Caught unexpected exceptions", e) continue } - - launch(CoroutineName("Incoming Packet handler")) { - rawInput.debugPrint("Received").use { input -> - if (input.remaining == 0L) { - bot.logger.error("Empty packet received. Consider if bad packet was sent.") - return@launch - } - KnownPacketFactories.parseIncomingPacket(bot, input) { packet: Packet, packetId: PacketId, sequenceId: Int -> - if (PacketReceivedEvent(packet).broadcast().cancelled) { - return@parseIncomingPacket - } - packetListeners.forEach { listener -> - if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) { - listener.complete(packet) - } - } - } - } - } + launchPacketProcessor(rawInput) } } @@ -96,9 +100,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler fun filter(packetId: PacketId, sequenceId: Int) = this.packetId == packetId && this.sequenceId == sequenceId } - override suspend fun awaitDisconnection() { - supervisor.join() - } + override suspend fun awaitDisconnection() = supervisor.join() override fun dispose(cause: Throwable?) { println("Closed") 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 ea9d053e8..248c1b80d 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 @@ -6,6 +6,7 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.toByteArray import net.mamoe.mirai.BotAccount import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv import net.mamoe.mirai.qqandroid.utils.Context import net.mamoe.mirai.qqandroid.utils.DeviceInfo import net.mamoe.mirai.qqandroid.utils.NetworkType @@ -13,6 +14,7 @@ import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.cryptor.ECDH +import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.unsafeWeakRef @@ -40,6 +42,11 @@ internal open class QQAndroidClient( val device: DeviceInfo = SystemDeviceInfo(context), bot: QQAndroidBot ) { + @UseExperimental(MiraiInternalAPI::class) + override fun toString(): String { // net.mamoe.mirai.utils.cryptor.ProtoKt.contentToString + return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.contentToString()}, randomKey=${randomKey.contentToString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, _ssoSequenceId=$_ssoSequenceId, openAppId=$openAppId, apkVersionName=${apkVersionName.contentToString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.contentToString()}, protocolVersion=$protocolVersion, apkId=${apkId.contentToString()}, t150=${t150?.contentToString()}, rollbackSig=${rollbackSig?.contentToString()}, ipFromT149=${ipFromT149?.contentToString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.contentToString()}, t528=${t528?.contentToString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.contentToString()}, qrPushSig=${qrPushSig.contentToString()}, mainDisplayName='$mainDisplayName')" + } + val context by context.unsafeWeakRef() val bot: QQAndroidBot by bot.unsafeWeakRef() @@ -81,7 +88,7 @@ internal open class QQAndroidClient( */ - var t150: ByteArray? = null + var t150: Tlv? = null var rollbackSig: ByteArray? = null var ipFromT149: ByteArray? = null /** @@ -100,7 +107,7 @@ internal open class QQAndroidClient( /** * t108 时更新 */ - var ksid: String = "|454001228437590|A8.2.0.27f6ea96" + var ksid: ByteArray = "|454001228437590|A8.2.0.27f6ea96".toByteArray() /** * t186 */ @@ -110,19 +117,23 @@ internal open class QQAndroidClient( */ var loginExtraData: LoginExtraData? = null lateinit var wFastLoginInfo: WFastLoginInfo - lateinit var reserveUinInfo: ReserveUinInfo + var reserveUinInfo: ReserveUinInfo? = null var wLoginSigInfo: WLoginSigInfo? = null var tlv113: ByteArray? = null lateinit var qrPushSig: ByteArray - lateinit var mainDisplayName: String + lateinit var mainDisplayName: ByteArray } class ReserveUinInfo( val imgType: ByteArray, val imgFormat: ByteArray, val imgUrl: ByteArray -) +) { + override fun toString(): String { + return "ReserveUinInfo(imgType=${imgType.contentToString()}, imgFormat=${imgFormat.contentToString()}, imgUrl=${imgUrl.contentToString()})" + } +} class WFastLoginInfo( val outA1: ByteReadPacket, @@ -130,7 +141,11 @@ class WFastLoginInfo( var iconUrl: String = "", var profileUrl: String = "", var userJson: String = "" -) +) { + override fun toString(): String { + return "WFastLoginInfo(outA1=$outA1, adUrl='$adUrl', iconUrl='$iconUrl', profileUrl='$profileUrl', userJson='$userJson')" + } +} class WLoginSimpleInfo( val uin: Long, // uin @@ -142,14 +157,22 @@ class WLoginSimpleInfo( val imgFormat: ByteArray, val imgUrl: ByteArray, val mainDisplayName: ByteArray -) +) { + override fun toString(): String { + return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.contentToString()}, imgFormat=${imgFormat.contentToString()}, imgUrl=${imgUrl.contentToString()}, mainDisplayName=${mainDisplayName.contentToString()})" + } +} class LoginExtraData( val uin: Long, val ip: ByteArray, val time: Int, val version: Int -) +) { + override fun toString(): String { + return "LoginExtraData(uin=$uin, ip=${ip.contentToString()}, time=$time, version=$version)" + } +} class WLoginSigInfo( val uin: Long, @@ -193,7 +216,11 @@ class WLoginSigInfo( val wtSessionTicket: WtSessionTicket, val wtSessionTicketKey: ByteArray, val deviceToken: ByteArray -) +) { + override fun toString(): String { + return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1.contentToString()}, noPicSig=${noPicSig.contentToString()}, G=${G.contentToString()}, dpwd=${dpwd.contentToString()}, randSeed=${randSeed.contentToString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.contentToString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.contentToString()}, userStSig=$userStSig, userStKey=${userStKey.contentToString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.contentToString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.contentToString()}, sid=$sid, aqSig=$aqSig, psKey=$psKey, superKey=${superKey.contentToString()}, payToken=${payToken.contentToString()}, pf=${pf.contentToString()}, pfKey=${pfKey.contentToString()}, da2=${da2.contentToString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.contentToString()}, deviceToken=${deviceToken.contentToString()})" + } +} class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt index d0b31c0c2..3ea42a591 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt @@ -144,8 +144,8 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket( writeInt(4) client.ksid.let { - writeShort((it.length + 2).toShort()) - writeStringUtf8(it) + writeShort((it.size + 2).toShort()) + writeFully(it) } writeInt(4) 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 1c4c3b4b6..f1487e692 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 @@ -48,6 +48,7 @@ private val DECRYPTER_16_ZERO = ByteArray(16) internal typealias PacketConsumer = suspend (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit +@UseExperimental(ExperimentalUnsignedTypes::class) internal object KnownPacketFactories : List> by mutableListOf( LoginPacket ) { @@ -60,7 +61,11 @@ internal object KnownPacketFactories : List> by mutableListO suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) = rawInput.debugPrintIfFail("Incoming packet") { require(remaining < Int.MAX_VALUE) { "rawInput is too long" } - val expectedLength = readInt() - 4 + val expectedLength = readUInt().toInt() - 4 + if (expectedLength > 16e7) { + bot.logger.warning("Detect incomplete packet, ignoring.") + return@debugPrintIfFail + } check(remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " } // login when (val flag1 = readInt()) { 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 244ce9cad..d7b3332a8 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 @@ -10,6 +10,11 @@ import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.md5 import kotlin.random.Random +/** + * 显式表示一个 [ByteArray] 是一个 tlv 的 body + */ +inline class Tlv(val value: ByteArray) + inline class LoginType( val value: Int ) { 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 3a998d48c..b0cc0d8a0 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 @@ -13,6 +13,7 @@ import net.mamoe.mirai.qqandroid.utils.inline import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.cryptor.DecrypterByteArray import net.mamoe.mirai.utils.cryptor.DecrypterType +import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.cryptor.decryptBy import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.currentTimeSeconds @@ -176,7 +177,7 @@ internal object LoginPacket : PacketFactory = this.readTLVMap() + println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() }) tlvMap[0x150]?.let { client.analysisTlv150(it) } tlvMap[0x161]?.let { client.analysisTlv161(it) } tlvMap[0x172]?.let { client.rollbackSig = it } tlvMap[0x119]?.let { t119Data -> - t119Data.decryptBy(client.tgtgtKey).read { + t119Data.decryptBy(client.tgtgtKey).toReadPacket().debugPrint("0x119data").apply { + discardExact(2) // always discarded. 00 1C + // 00 1C + // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 val tlvMap119 = this.readTLVMap() + println("tlvMap119 KEYS: " + tlvMap119.keys.joinToString { it.contentToString() }) + + // ??? + tlvMap119[0x1c]?.read { + val bytes = readBytes() + DebugLogger.warning(bytes.toUHexString()) + DebugLogger.warning(bytes.encodeToString()) + } tlvMap119[0x149]?.let { client.analysisTlv149(it) } tlvMap119[0x130]?.let { client.analysisTlv130(it) } @@ -208,8 +221,8 @@ internal object LoginPacket : PacketFactory error("Cannot find tlv 0x11a, which is account info(face, gender, nick, age)") + null -> { + face = 0 + age = 0 + gender = 0 + nick = "" + } else -> t11a.read { face = readUShort().toInt() age = readUByte().toInt() @@ -272,7 +290,7 @@ internal object LoginPacket : PacketFactory payToken = byteArrayOf() else -> t199.read { @@ -316,9 +334,9 @@ internal object LoginPacket : PacketFactory") + "#" + this::class.hashCode() + " {\n" + + this::class.java.fields.toMutableSet().apply { addAll(this::class.java.declaredFields) }.asSequence().filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic } + .joinToStringPrefixed( + prefix = newPrefix + ) { + it.isAccessible = true + it.name + "=" + kotlin.runCatching { + val value = it.get(this) + if (value == this) "" + else value.contentToString(newPrefix) + }.getOrElse { "" } + } + "\n$prefix}" +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 0da7a4d41..6c147d261 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -93,7 +93,9 @@ abstract class Bot : CoroutineScope { abstract val network: BotNetworkHandler /** - * 登录 + * 登录. + * + * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login] * * @throws LoginFailedException */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt index caa389a1a..01a779f3f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt @@ -28,6 +28,8 @@ expect class ECDH(keyPair: ECDHKeyPair) { fun generateKeyPair(): ECDHKeyPair fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray } + + override fun toString(): String } @Suppress("FunctionName") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt index bff3e23bf..4dd003400 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt @@ -96,7 +96,7 @@ fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3) class ProtoMap(map: MutableMap) : MutableMap by map { companion object { @JvmStatic - val indent: String = " " + internal val indent: String = " " } override fun toString(): String { @@ -117,7 +117,12 @@ class ProtoMap(map: MutableMap) : MutableMap Sequence.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { + return this.joinToString(prefix = "$prefix${ProtoMap.indent}", separator = "\n$prefix${ProtoMap.indent}", transform = transform) +} + +fun Any?.contentToString(prefix: String = ""): String = when (this) { + is Unit -> "Unit" is UInt -> "0x" + this.toUHexString("") + "($this)" is UByte -> "0x" + this.toUHexString() + "($this)" is UShort -> "0x" + this.toUHexString("") + "($this)" @@ -131,12 +136,38 @@ internal fun Any.contentToString(prefix: String = ""): String = when (this) { is Boolean -> if (this) "true" else "false" - is ByteArray -> this.toUHexString()// + " (${this.encodeToString()})" + is ByteArray -> { + if (this.size == 0) "" + else this.toUHexString()// + " (${this.encodeToString()})" + } + is UByteArray -> { + if (this.size == 0) "" + else this.toUHexString()// + " (${this.encodeToString()})" + } is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}" - else -> this.toString() + is Collection<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() } + is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString() + "=" + it.value.contentToString() } + else -> { + if (this == null) "null" + else if (this::class.isData) this.toString() + else { + if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) { + this.contentToStringReflectively(prefix + ProtoMap.indent) + } else this.toString() + /* + (this::class.simpleName ?: "") + "#" + this::class.hashCode() + "{\n" + + this::class.members.asSequence().filterIsInstance>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC } + .joinToStringPrefixed( + prefix = ProtoMap.indent + ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(ProtoMap.indent) }.getOrElse { "" } } + */ + } + } } +expect fun Any.contentToStringReflectively(prefix: String = ""): String + fun ByteReadPacket.readProtoMap(length: Long = this.remaining): ProtoMap { val map = ProtoMap(mutableMapOf()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt index 7a42bec10..09f34d3d0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt @@ -9,6 +9,8 @@ import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.contact.groupId import net.mamoe.mirai.contact.groupInternalId +import net.mamoe.mirai.utils.assertUnreachable +import net.mamoe.mirai.utils.cryptor.contentToString import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -76,14 +78,14 @@ fun Input.readTLVMap(tagSize: Int = 2): MutableMap = readTLVMap( @Suppress("DuplicatedCode") fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): MutableMap { val map = mutableMapOf() - var type: Int = 0 + var key = 0 while (inline { try { - type = when (tagSize) { - 1 -> readByte().toInt() - 2 -> readShort().toInt() - 4 -> readInt() + key = when (tagSize) { + 1 -> readUByte().toInt() + 2 -> readUShort().toInt() + 4 -> readUInt().toInt() else -> error("Unsupported tag size: $tagSize") } } catch (e: Exception) { // java.nio.BufferUnderflowException is not a EOFException... @@ -92,22 +94,33 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): MutableMap key.toByte() + 2 -> key.toShort() + 4 -> key + else -> assertUnreachable() + }.contentToString()} + map=${map.contentToString()} + duplicating value=${this.readUShortLVByteArray().toUHexString()} + """.trimIndent() + ) + } else { + try { + map[key] = this.readUShortLVByteArray() + } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException + // if (expectingEOF) { + // return map + // } + throw e } - throw e } } return map diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt index d020a5336..6cfe2ff68 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt @@ -51,13 +51,13 @@ fun UShort.toByteArray(): ByteArray = with(toUInt()) { fun Short.toUHexString(separator: String = " "): String = this.toUShort().toUHexString(separator) fun UShort.toUHexString(separator: String = " "): String = - (this.toInt().shr(8).toUShort() and 255u).toByte().toUHexString() + separator + (this and 255u).toByte().toUHexString() + this.toInt().shr(8).toUShort().toUByte().toUHexString() + separator + this.toUByte().toUHexString() fun ULong.toUHexString(separator: String = " "): String = this.toLong().toUHexString(separator) fun Long.toUHexString(separator: String = " "): String = - this.ushr(32).toUInt().toUHexString(separator) + separator + this.ushr(32).toUInt().toUHexString(separator) + this.ushr(32).toUInt().toUHexString(separator) + separator + this.toUInt().toUHexString(separator) /** * 255 -> 00 FF diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt index cffda0016..66a06aa2a 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt @@ -51,4 +51,8 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray { return calculateShareKey(keyPair.privateKey, peerPublicKey) } + + actual override fun toString(): String { + return "ECDH(keyPair=$keyPair)" + } } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt new file mode 100644 index 000000000..1df504a55 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt @@ -0,0 +1,31 @@ +package net.mamoe.mirai.utils.cryptor + +import java.lang.reflect.Field +import kotlin.reflect.full.allSuperclasses + + +actual fun Any.contentToStringReflectively(prefix: String): String { + val newPrefix = prefix + ProtoMap.indent + return (this::class.simpleName ?: "") + "#" + this::class.hashCode() + " {\n" + + this.allFieldsFromSuperClassesMatching { it.packageName.startsWith("net.mamoe.mirai") } + .distinctBy { it.name } + .filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" } + .joinToStringPrefixed( + prefix = newPrefix + ) { + it.trySetAccessible() + it.name + "=" + kotlin.runCatching { + val value = it.get(this) + if (value == this) "" + else value.contentToString(newPrefix) + }.getOrElse { "" } + } + "\n$prefix}" +} + +internal fun Any.allFieldsFromSuperClassesMatching(classFilter: (Class) -> Boolean): Sequence { + return (this::class.java.takeIf(classFilter)?.declaredFields?.asSequence() ?: sequenceOf()) + this::class.allSuperclasses + .asSequence() + .map { it.java } + .filter(classFilter) + .flatMap { it.declaredFields.asSequence() } +} \ No newline at end of file