From fb3fe2bcca650597d24156f5e463ed6fd8e5564e Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 30 Jan 2020 23:53:28 +0800 Subject: [PATCH 01/16] Improve login and init --- .../network/QQAndroidBotNetworkHandler.kt | 127 +++++++++--------- .../kotlin/net.mamoe.mirai/BotImpl.kt | 21 ++- .../network/BotNetworkHandler.kt | 7 + 3 files changed, 85 insertions(+), 70 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 8bb5551f5..609f157c7 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 @@ -37,84 +37,79 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler override suspend fun login() { - suspend fun doLogin() { - channel = PlatformSocket() - channel.connect("113.96.13.208", 8080) - launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } + channel = PlatformSocket() + channel.connect("113.96.13.208", 8080) + launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } - bot.logger.info("Trying login") - var response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect() - mainloop@ while (true) { - when (response) { - is LoginPacket.LoginPacketResponse.UnsafeLogin -> { - bot.configuration.loginSolver.onSolveUnsafeDeviceLoginVerify(bot, response.url) - response = LoginPacket.SubCommand9(bot.client).sendAndExpect() - } + bot.logger.info("Trying login") + var response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect() + mainloop@ while (true) { + when (response) { + is LoginPacket.LoginPacketResponse.UnsafeLogin -> { + bot.configuration.loginSolver.onSolveUnsafeDeviceLoginVerify(bot, response.url) + response = LoginPacket.SubCommand9(bot.client).sendAndExpect() + } - is LoginPacket.LoginPacketResponse.Captcha -> when (response) { - is LoginPacket.LoginPacketResponse.Captcha.Picture -> { - var result = response.data.withUse { - bot.configuration.loginSolver.onSolvePicCaptcha(bot, this) - } - if (result == null || result.length != 4) { - //refresh captcha - result = "ABCD" - } - response = LoginPacket.SubCommand2.SubmitPictureCaptcha(bot.client, response.sign, result).sendAndExpect() - continue@mainloop + is LoginPacket.LoginPacketResponse.Captcha -> when (response) { + is LoginPacket.LoginPacketResponse.Captcha.Picture -> { + var result = response.data.withUse { + bot.configuration.loginSolver.onSolvePicCaptcha(bot, this) } - is LoginPacket.LoginPacketResponse.Captcha.Slider -> { - var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url) - if (ticket == null) { - ticket = "" - } - response = LoginPacket.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect() - continue@mainloop + if (result == null || result.length != 4) { + //refresh captcha + result = "ABCD" } - } - - is LoginPacket.LoginPacketResponse.Error -> error(response.toString()) - - is LoginPacket.LoginPacketResponse.DeviceLockLogin -> { - response = LoginPacket.SubCommand20( - bot.client, - response.t402, - response.t403 - ).sendAndExpect() + response = LoginPacket.SubCommand2.SubmitPictureCaptcha(bot.client, response.sign, result).sendAndExpect() continue@mainloop } - - is LoginPacket.LoginPacketResponse.Success -> { - bot.logger.info("Login successful") - break@mainloop + is LoginPacket.LoginPacketResponse.Captcha.Slider -> { + var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url) + if (ticket == null) { + ticket = "" + } + response = LoginPacket.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect() + continue@mainloop } } + + is LoginPacket.LoginPacketResponse.Error -> error(response.toString()) + + is LoginPacket.LoginPacketResponse.DeviceLockLogin -> { + response = LoginPacket.SubCommand20( + bot.client, + response.t402, + response.t403 + ).sendAndExpect() + continue@mainloop + } + + is LoginPacket.LoginPacketResponse.Success -> { + bot.logger.info("Login successful") + break@mainloop + } } - - println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}") - StatSvc.Register(bot.client).sendAndExpect() } - suspend fun doInit() { - //start updating friend/group list - bot.logger.info("Start updating friend/group list") - /* - val data = FriendList.GetFriendGroupList( - bot.client, - 0, - 1, - 0, - 2 - ).sendAndExpect() - */ - val data = FriendList.GetTroopList( - bot.client - ).sendAndExpect() - println(data.contentToString()) - } + println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}") + StatSvc.Register(bot.client).sendAndExpect() + } - doLogin() - doInit() + suspend fun init() { + //start updating friend/group list + bot.logger.info("Start updating friend/group list") + /* + val data = FriendList.GetFriendGroupList( + bot.client, + 0, + 1, + 0, + 2 + ).sendAndExpect() + */ + val data = FriendList.GetTroopList( + bot.client + ).sendAndExpect() + println(data.contentToString()) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 1a8d8d063..6a6293f31 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -27,7 +27,8 @@ abstract class BotImpl constructor( @Suppress("CanBePrimaryConstructorProperty") // for logger final override val account: BotAccount = account @UseExperimental(RawAccountIdUse::class) - override val uin: Long get() = account.id + override val uin: Long + get() = account.id final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } } init { @@ -98,16 +99,28 @@ abstract class BotImpl constructor( } _network = createNetworkHandler(this.coroutineContext) - while (true){ + loginLoop@ while (true) { try { - return _network.login() - } catch (e: Exception){ + _network.login() + break@loginLoop + } catch (e: Exception) { e.logStacktrace() _network.dispose(e) } logger.warning("Login failed. Retrying in 3s...") delay(3000) } + + while (true) { + try { + return _network.init() + } catch (e: Exception) { + e.logStacktrace() + _network.dispose(e) + } + logger.warning("Init failed. Retrying in 3s...") + delay(3000) + } } protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt index 4341880a9..f1ff2c865 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt @@ -47,6 +47,13 @@ abstract class BotNetworkHandler : CoroutineScope { @MiraiInternalAPI abstract suspend fun login() + /** + * 初始化获取好友列表等值. + */ + @MiraiInternalAPI + open suspend fun init() { + } + /** * 等待直到与服务器断开连接. 若未连接则立即返回 */ From 18cf5621d0f70a079c9c4753e5e6b995594d8ad2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 30 Jan 2020 23:54:58 +0800 Subject: [PATCH 02/16] Fix override --- .../mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 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 609f157c7..5b06551ea 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 @@ -41,7 +41,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler channel.connect("113.96.13.208", 8080) launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } - bot.logger.info("Trying login") + // bot.logger.info("Trying login") var response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect() mainloop@ while (true) { when (response) { @@ -94,7 +94,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler StatSvc.Register(bot.client).sendAndExpect() } - suspend fun init() { + override suspend fun init() { //start updating friend/group list bot.logger.info("Start updating friend/group list") /* From dc0df986baa414b09164f8c692dcdaccc6426763 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 01:34:52 +0800 Subject: [PATCH 03/16] Fix sync cookie --- .../network/QQAndroidBotNetworkHandler.kt | 2 +- .../mirai/qqandroid/network/QQAndroidClient.kt | 2 +- .../network/protocol/data/proto/SyncCookie.kt | 16 ++++++++++++++++ .../protocol/packet/chat/receive/MessageSvc.kt | 12 ++++++++---- .../protocol/packet/list/FriendListPacket.kt | 8 +++++--- 5 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.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 5b06551ea..dd61c7474 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 @@ -108,7 +108,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler */ val data = FriendList.GetTroopList( bot.client - ).sendAndExpect() + ).sendAndExpect() println(data.contentToString()) } 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 cd6f0a7d6..fc847868b 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 @@ -111,7 +111,7 @@ internal open class QQAndroidClient( val protocolVersion: Short = 8001 class C2cMessageSyncData { - var syncCookie = EMPTY_BYTE_ARRAY + var syncCookie: ByteArray? = null var pubAccountCookie = EMPTY_BYTE_ARRAY var syncFlag: Int = 0 var msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY 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 new file mode 100644 index 000000000..149e1bf62 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt @@ -0,0 +1,16 @@ +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.SerialId +import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.ProtoBuf + +@Serializable +class SyncCookie( + @SerialId(2) val time: Long, + @SerialId(3) val unknown1: Long = 2994099792, + @SerialId(4) val unknown2: Long = 3497826378, + @SerialId(5) val const1: Long = 1680172298, + @SerialId(6) val const2: Long = 2424173273, + @SerialId(7) val unknown3: Long = 83, + @SerialId(8) val unknown4: Long = 0 +) : ProtoBuf \ 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 38b96dff7..92e9e17e4 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 @@ -10,6 +10,7 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline @@ -17,6 +18,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify 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.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket @@ -41,7 +43,7 @@ internal class MessageSvc { override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) { network.run { - PbGetMsg(client, packet).sendAndExpect>() + PbGetMsg(client, packet.stMsgInfo?.uMsgTime ?: 0).sendAndExpect>() } } } @@ -56,7 +58,7 @@ internal class MessageSvc { operator fun invoke( client: QQAndroidClient, - from: RequestPushNotify + msgTime: Long //PbPushMsg.msg.msgHead.msgTime ): OutgoingPacket = buildOutgoingUniPacket( client, extraData = EXTRA_DATA.toReadPacket() @@ -64,14 +66,16 @@ internal class MessageSvc { writeProtoBuf( MsgSvc.PbGetMsgReq.serializer(), MsgSvc.PbGetMsgReq( - msgReqType = from.ctype.toInt(), + msgReqType = 1, // from.ctype.toInt() contextFlag = 1, rambleFlag = 0, latestRambleNumber = 20, otherRambleNumber = 3, onlineSyncFlag = 1, + whisperSessionId = 0, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, - syncCookie = client.c2cMessageSync.syncCookie, + syncCookie = client.c2cMessageSync.syncCookie + ?: SyncCookie(msgTime).toByteArray(SyncCookie.serializer()).also { client.c2cMessageSync.syncCookie = it }, syncFlag = 1 // syncFlag = client.c2cMessageSync.syncFlag, //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, 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 c62de6ef3..9414fed14 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 @@ -3,7 +3,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.list import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.QQAndroidBot -import net.mamoe.mirai.qqandroid.io.serialization.* +import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket +import net.mamoe.mirai.qqandroid.io.serialization.jceRequestSBuffer +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListReq import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListResp @@ -15,7 +18,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.utils.cryptor.contentToString -import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.readRemainingBytes import net.mamoe.mirai.utils.io.toUHexString @@ -24,7 +26,7 @@ import net.mamoe.mirai.utils.io.toUHexString internal class FriendList { internal object GetTroopList : PacketFactory("friendlist.GetTroopListReqV2") { - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GetTroopList.Response { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { println("获取到了GetTroopList的回信") println(this.readRemainingBytes().toUHexString()) return Response() From e956dccdd191a1d3afdd0b1440366bb5f35e1125 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 01:35:03 +0800 Subject: [PATCH 04/16] Fix typo --- .../kotlin/net.mamoe.mirai/event/MessageSubscribers.kt | 2 +- .../src/main/java/demo/subscribe/SubscribeSamples.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index 67b30d6ff..c9c565574 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -157,7 +157,7 @@ class MessageSubscribersBuilder>( ListeningFilter { !filter.invoke(this, it) || !another.filter.invoke(this, it) } /** - * 启动时间监听. + * 启动事件监听. */ // do not inline due to kotlin (1.3.61) bug: java.lang.IllegalAccessError operator fun invoke(onEvent: MessageListener): Listener { diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt index 26135d02b..e1f898ce4 100644 --- a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt +++ b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt @@ -130,7 +130,7 @@ fun Bot.messageDSL() { // 当消息中包含 "复读" 时 - contains("复读") { + val listener = (contains("复读1") or contains("复读2")) { reply(message) } From 06224aa10a78804b2083d6c7b82984b2ac434053 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 01:43:31 +0800 Subject: [PATCH 05/16] Fix channel close --- .../qqandroid/network/QQAndroidBotNetworkHandler.kt | 10 +++++++++- .../network/protocol/packet/chat/receive/MessageSvc.kt | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) 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 dd61c7474..a59edc796 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 @@ -37,6 +37,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler override suspend fun login() { + if (::channel.isInitialized) { + channel.close() + } channel = PlatformSocket() channel.connect("113.96.13.208", 8080) launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } @@ -91,7 +94,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler } println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}") - StatSvc.Register(bot.client).sendAndExpect() + StatSvc.Register(bot.client).sendAndExpect(6000) } override suspend fun init() { @@ -371,5 +374,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler fun filter(commandName: String, sequenceId: Int) = this.commandName == commandName && this.sequenceId == sequenceId } + override fun dispose(cause: Throwable?) { + channel.close() + super.dispose(cause) + } + override suspend fun awaitDisconnection() = supervisor.join() } \ 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 92e9e17e4..8fd2ee497 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 @@ -89,6 +89,10 @@ internal class MessageSvc { discardExact(4) val resp = readRemainingAsProtoBuf(MsgSvc.PbGetMsgResp.serializer()) + if (resp.result != 0) { + return MultiPacket(emptyList()) + } + bot.client.c2cMessageSync.syncCookie = resp.syncCookie bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie bot.client.c2cMessageSync.syncFlag = resp.syncFlag From 359be1235681d6140e664cb0c6f59f6a69fe7608 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 02:01:00 +0800 Subject: [PATCH 06/16] SEND IMAGE SUPPORT --- .../network/protocol/data/proto/Msg.kt | 17 ++++++++++++++++- .../mamoe/mirai/qqandroid/utils/MessageQQA.kt | 12 +++++++++++- .../net.mamoe.mirai/message/data/Image.kt | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt index 68f3773b1..0eddb2658 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt @@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY @Serializable @@ -624,7 +625,7 @@ internal class ImMsgBody : ProtoBuf { internal class NotOnlineImage( @SerialId(1) val filePath: String = "", @SerialId(2) val fileLen: Int = 0, - @SerialId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(3) val downloadPath: String = "", @SerialId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(5) val imgType: Int = 0, @SerialId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, @@ -653,6 +654,20 @@ internal class ImMsgBody : ProtoBuf { @SerialId(29) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf + @Serializable // 非官方. + internal data class PbReserve( + @SerialId(1) val unknown1: Int = 1, + @SerialId(2) val unknown2: Int = 0, + @SerialId(6) val unknown3: Int = 0, + @SerialId(8) val hint: String = "[动画表情]", + @SerialId(10) val unknown5: Int = 0, + @SerialId(15) val unknwon6: Int = 5 + ) : ProtoBuf { + companion object { + val DEFAULT: ByteArray = PbReserve().toByteArray(serializer()) + } + } + @Serializable internal class OnlineImage( @SerialId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt index 169347a09..0bda9be52 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt @@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.utils import net.mamoe.mirai.data.ImageLink import net.mamoe.mirai.message.data.* import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.utils.io.hexToBytes internal fun MessageChain.toRichTextElems(): MutableList { @@ -20,7 +21,16 @@ internal fun MessageChain.toRichTextElems(): MutableList { elems.add( ImMsgBody.Elem( notOnlineImage = ImMsgBody.NotOnlineImage( - filePath = it.id.value + filePath = it.id.value, // 错了, 应该是 2B23D705CAD1F2CF3710FE582692FCC4.jpg + fileLen = 1149, // 假的 + downloadPath = it.id.value, + imgType = 1000, // 不确定 + picMd5 = "2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4".hexToBytes(), + picHeight = 66, + picWidth = 66, + resId = it.id.value, + bizType = 5, + pbReserve = ImMsgBody.PbReserve.DEFAULT // 可能还可以改变 `[动画表情]` ) ) ) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index 95dcdbdbb..c3ec60093 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -14,7 +14,7 @@ fun Image(id: String) = Image(ImageId(id)) * 由接收消息时构建, 可直接发送 * * @param id 这个图片的 [ImageId] - */ + */ // TODO: 2020/1/31 去掉 Image. 将 Image 改为 interface/class inline class Image(inline val id: ImageId) : Message { override fun toString(): String = "[${id.value}]" From f5b4ca4f953705f7ae91ab8d6d1d40e204455229 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 02:08:07 +0800 Subject: [PATCH 07/16] Changelog --- UpdateLog.md => CHANGELOG.md | 0 README.md | 14 ++++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) rename UpdateLog.md => CHANGELOG.md (100%) diff --git a/UpdateLog.md b/CHANGELOG.md similarity index 100% rename from UpdateLog.md rename to CHANGELOG.md diff --git a/README.md b/README.md index ee60e7bb8..8ed0247c3 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ 加入 Gitter, 或加入 QQ 群: 655057127 -## Update log +## CHANGELOG 在 [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划(更新不及时) -在 [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录(准确更新发布的版本) +在 [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 查看版本更新记录(准确更新发布的版本) ## Modules #### mirai-core @@ -32,15 +32,17 @@ TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅 #### mirai-core-qqandroid QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。 -- 高兼容性:Mirai 协议仅含极少部分为硬编码,其余全部随官方方式动态生成 -- 高安全性:密匙随机,ECDH 动态计算,硬件信息真机模拟(Android 平台获取真机信息) +- 高兼容性:协议仅含极少部分为硬编码,其余全部随官方方式动态生成 +- 高安全性:密匙随机,ECDH 动态计算 开发进度: - 完成 密码登录 (2020/1/23) - 完成 群消息解析 (2020/1/25) - 完成 图片验证码登录 (2020/1/26) -- 完成 设备锁登录 (2020/1/29) -- 进行中 消息解析和发送 +- 完成 "不安全"状态登录, 设备锁登录 (2020/1/29) +- 完成 群消息解析: 图片, 文字 (2020/1/31) +- 进行中 好友消息同步 +- 进行中 好友列表, 群列表, 分组列表 - 进行中 图片上传和下载 ## Use directly From 44a95792dd3d5dc248bacaae2a40a5468776e35c Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 02:08:21 +0800 Subject: [PATCH 08/16] Remove print --- .../mirai/qqandroid/io/serialization/SerializationUtils.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt index 23c6e83ed..cfd57a14a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt @@ -11,7 +11,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.toUHexString fun ByteArray.loadAs(deserializer: DeserializationStrategy, c: JceCharset = JceCharset.UTF8): T { @@ -71,9 +70,7 @@ fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null fun T.toByteArray(serializer: SerializationStrategy, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this) fun BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy, v: T) { - this.writeFully(v.toByteArray(serializer).also { - println("发送 protobuf: ${it.toUHexString()}") - }) + this.writeFully(v.toByteArray(serializer)) } /** From 846280db50c0e5ea8987f65d4bab8ea177822c36 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 02:09:17 +0800 Subject: [PATCH 09/16] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ed0247c3..407a17c53 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ 在 [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 查看版本更新记录(准确更新发布的版本) ## Modules -#### mirai-core +### mirai-core 通用 API 模块,一套 API 适配两套协议。 **请参考此模块的 API** -#### mirai-core-timpc +### mirai-core-timpc TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/) 支持的功能: - 消息收发:图片文字复合消息,图片消息 @@ -30,7 +30,7 @@ TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅 (目前不再更新此协议,请关注下文的安卓协议) -#### mirai-core-qqandroid +### mirai-core-qqandroid QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。 - 高兼容性:协议仅含极少部分为硬编码,其余全部随官方方式动态生成 - 高安全性:密匙随机,ECDH 动态计算 From e76bbf3c59c643786d37a563af255d02f17f136b Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 13:49:00 +0800 Subject: [PATCH 10/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 407a17c53..5223640da 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) **[English](README-eng.md)** -跨平台 **TIM PC 和 QQ Android** 协议支持库. +多平台 **TIM PC 和 QQ Android** 协议支持库. 纯 Kotlin 实现协议和支持框架,模块全部开源。 目前可运行在 JVM 或 Android。 From 56a267dfa30374c706a585de1d27457efc1230fc Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 15:05:02 +0800 Subject: [PATCH 11/16] Improve input --- .../net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt | 1 + .../commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt index c983ff475..b3140ee04 100644 --- a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt @@ -55,6 +55,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor private var heartbeatJob: Job? = null + @MiraiInternalAPI override suspend fun login() { TIMProtocol.SERVER_IP.shuffled().forEach { ip -> 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 3bd8002ae..5b87a8c70 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 @@ -54,6 +54,10 @@ fun ByteReadPacket.readIoBuffer( n: Int = remaining.toInt()//not that safe but adequate ): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) } +fun ByteReadPacket.readPacket( + n: Int = remaining.toInt()//not that safe but adequate +): ByteReadPacket = this.readBytes(n).toReadPacket() + fun ByteReadPacket.readIoBuffer(n: Short) = this.readIoBuffer(n.toInt()) fun Input.readIP(): String = buildString(4 + 3) { From 538c2b97916583cf34cc625b3d9417e3455bed9a Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 15:05:24 +0800 Subject: [PATCH 12/16] Adjust accessibility --- .../io/serialization/SerializationUtils.kt | 10 +-- .../network/QQAndroidBotNetworkHandler.kt | 28 ++------ .../qqandroid/network/QQAndroidClient.kt | 64 ++++++++++--------- .../protocol/packet/OutgoingPacketAndroid.kt | 5 +- .../packet/chat/receive/MessageSvc.kt | 6 +- .../network/protocol/packet/login/StatSvc.kt | 4 +- .../mamoe/mirai/qqandroid/utils/TgtgtKey.kt | 8 --- 7 files changed, 53 insertions(+), 72 deletions(-) delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/TgtgtKey.kt diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt index cfd57a14a..9b5f13501 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt @@ -21,7 +21,7 @@ fun BytePacketBuilder.writeJceStruct(serializer: SerializationSt this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct)) } -fun ByteReadPacket.readRemainingAsJceStruct( +fun ByteReadPacket.readJceStruct( serializer: DeserializationStrategy, charset: JceCharset = JceCharset.UTF8, length: Int = this.remaining.toInt() @@ -36,7 +36,7 @@ fun ByteReadPacket.decodeUniPacket(deserializer: Deserialization return decodeUniRequestPacketAndDeserialize(name) { it.read { discardExact(1) - this.readRemainingAsJceStruct(deserializer, length = (this.remaining - 1).toInt()) + this.readJceStruct(deserializer, length = (this.remaining - 1).toInt()) } } } @@ -48,13 +48,13 @@ fun ByteReadPacket.decodeUniPacket(deserializer: DeserializationS return decodeUniRequestPacketAndDeserialize(name) { it.read { discardExact(1) - this.readRemainingAsProtoBuf(deserializer, (this.remaining - 1).toInt()) + this.readProtoBuf(deserializer, (this.remaining - 1).toInt()) } } } fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { - val request = this.readRemainingAsJceStruct(RequestPacket.serializer()) + val request = this.readJceStruct(RequestPacket.serializer()) return block(if (name == null) when (request.iVersion.toInt()) { 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() @@ -90,7 +90,7 @@ fun ByteArray.loadAs(deserializer: DeserializationStrategy): T /** * load */ -fun ByteReadPacket.readRemainingAsProtoBuf(serializer: DeserializationStrategy, length: Int = this.remaining.toInt()): T { +fun ByteReadPacket.readProtoBuf(serializer: DeserializationStrategy, length: Int = this.remaining.toInt()): T { return ProtoBufWithNullableSupport.load(serializer, this.readBytes(length)) } 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 a59edc796..fb51a07b2 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 @@ -3,8 +3,10 @@ package net.mamoe.mirai.qqandroid.network import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.coroutines.* -import kotlinx.io.core.* -import kotlinx.io.pool.ObjectPool +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.Input +import kotlinx.io.core.buildPacket +import kotlinx.io.core.use import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.BroadcastControllable @@ -143,28 +145,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler /** * 在 [PacketProcessDispatcher] 调度器中解析包内容. - * [input] 将会被 [ObjectPool.recycle]. - * - * @param input 一个完整的包的内容, 去掉开头的 int 包长度 - */ - fun parsePacketAsync(input: IoBuffer, pool: ObjectPool = IoBuffer.Pool): Job = - this.launch(PacketProcessDispatcher) { - try { - parsePacket(input) - } finally { - input.discard() - input.release(pool) - } - } - - /** - * 在 [PacketProcessDispatcher] 调度器中解析包内容. - * [input] 将会被 [Input.close], 因此 [input] 不能为 [IoBuffer] * * @param input 一个完整的包的内容, 去掉开头的 int 包长度 */ fun parsePacketAsync(input: Input): Job { - require(input !is IoBuffer) { "input cannot be IoBuffer" } return this.launch(PacketProcessDispatcher) { input.use { parsePacket(it) } } @@ -180,6 +164,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler generifiedParsePacket(input) } + // with generic type, less mistakes private suspend inline fun

generifiedParsePacket(input: Input) { KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory

, packet: P, commandName: String, sequenceId: Int -> handlePacket(packetFactory, packet, commandName, sequenceId) @@ -230,7 +215,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler * 处理从服务器接收过来的包. 这些包可能是粘在一起的, 也可能是不完整的. 将会自动处理. * 处理后的包会调用 [parsePacketAsync] */ - @UseExperimental(ExperimentalCoroutinesApi::class) internal fun processPacket(rawInput: ByteReadPacket) { if (rawInput.remaining == 0L) { return @@ -248,7 +232,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler } // 循环所有完整的包 while (rawInput.remaining > length) { - parsePacketAsync(rawInput.readIoBuffer(length)) + parsePacketAsync(rawInput.readPacket(length)) length = rawInput.readInt() - 4 } 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 fc847868b..e34531671 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 @@ -11,15 +11,15 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv -import net.mamoe.mirai.qqandroid.utils.* -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.qqandroid.utils.Context +import net.mamoe.mirai.qqandroid.utils.DeviceInfo +import net.mamoe.mirai.qqandroid.utils.NetworkType +import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo +import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.cryptor.decryptBy -import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.unsafeWeakRef /* APP ID: @@ -174,7 +174,11 @@ internal open class QQAndroidClient( lateinit var t104: ByteArray } -class ReserveUinInfo( +internal fun generateTgtgtKey(guid: ByteArray): ByteArray = + md5(getRandomByteArray(16) + guid) + + +internal class ReserveUinInfo( val imgType: ByteArray, val imgFormat: ByteArray, val imgUrl: ByteArray @@ -184,7 +188,7 @@ class ReserveUinInfo( } } -class WFastLoginInfo( +internal class WFastLoginInfo( val outA1: ByteReadPacket, var adUrl: String = "", var iconUrl: String = "", @@ -196,7 +200,7 @@ class WFastLoginInfo( } } -class WLoginSimpleInfo( +internal class WLoginSimpleInfo( val uin: Long, // uin val face: Int, // ubyte actually val age: Int, // ubyte @@ -212,7 +216,7 @@ class WLoginSimpleInfo( } } -class LoginExtraData( +internal class LoginExtraData( val uin: Long, val ip: ByteArray, val time: Int, @@ -223,7 +227,7 @@ class LoginExtraData( } } -class WLoginSigInfo( +internal class WLoginSigInfo( val uin: Long, val encryptA1: ByteArray?, // sigInfo[0] val noPicSig: ByteArray?, // sigInfo[1] @@ -275,24 +279,24 @@ class WLoginSigInfo( } } -class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -typealias PSKeyMap = MutableMap -typealias Pt4TokenMap = MutableMap +internal typealias PSKeyMap = MutableMap +internal typealias Pt4TokenMap = MutableMap internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) = data.read { @@ -308,17 +312,17 @@ internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, ex } } -class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -open class KeyWithExpiry( +internal open class KeyWithExpiry( data: ByteArray, creationTime: Long, val expireTime: Long ) : KeyWithCreationTime(data, creationTime) -open class KeyWithCreationTime( +internal open class KeyWithCreationTime( val data: ByteArray, val creationTime: Long ) \ No newline at end of file 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 8c7825e67..f1b05fd0a 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 @@ -192,7 +192,7 @@ internal inline fun PacketFactory<*>.buildLoginOutgoingPacket( }) } -private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY) +private inline val BRP_STUB get() = ByteReadPacket.Empty /** * The second outermost packet for login @@ -233,7 +233,8 @@ internal inline fun BytePacketBuilder.writeSsoPacket( writeInt(subAppId.toInt()) writeInt(subAppId.toInt()) writeHex(unknownHex) - if (extraData === BRP_STUB) { + if (extraData === BRP_STUB || extraData.remaining == 0L) { + // fast-path writeInt(0x04) } else { writeInt((extraData.remaining + 4).toInt()) 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 8fd2ee497..875858b11 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 @@ -9,7 +9,7 @@ import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket -import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf import net.mamoe.mirai.qqandroid.network.QQAndroidClient @@ -87,7 +87,7 @@ internal class MessageSvc { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MultiPacket { // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 discardExact(4) - val resp = readRemainingAsProtoBuf(MsgSvc.PbGetMsgResp.serializer()) + val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer()) if (resp.result != 0) { return MultiPacket(emptyList()) @@ -201,7 +201,7 @@ internal class MessageSvc { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { discardExact(4) - val response = readRemainingAsProtoBuf(MsgSvc.PbSendMsgResp.serializer()) + val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer()) return if (response.result == 0) { Response.SUCCESS } else { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt index 766f26ba7..91beaf8f1 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt @@ -20,7 +20,7 @@ import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.localIpAddress @Suppress("EnumEntryName") -enum class RegPushReason { +internal enum class RegPushReason { appRegister, createDefaultRegInfo, fillRegProxy, @@ -32,7 +32,7 @@ enum class RegPushReason { unknown } -class StatSvc { +internal class StatSvc { internal object Register : PacketFactory("StatSvc.register") { internal object Response : Packet { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/TgtgtKey.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/TgtgtKey.kt deleted file mode 100644 index c72c93eeb..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/TgtgtKey.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.mamoe.mirai.qqandroid.utils - -import net.mamoe.mirai.utils.io.getRandomByteArray -import net.mamoe.mirai.utils.md5 - -fun generateTgtgtKey(guid: ByteArray): ByteArray = - md5(getRandomByteArray(16) + guid) - From e41c9231e149250e5365935b41c36ad831dec48a Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 15:06:23 +0800 Subject: [PATCH 13/16] Add intro --- .../mamoe/mirai/qqandroid/io/serialization/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/README.md diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/README.md b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/README.md new file mode 100644 index 000000000..1e027be1e --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/README.md @@ -0,0 +1,10 @@ +# io.serialization + +**序列化支持** + +包含: +- QQ 的 JceStruct 相关的全自动序列化和反序列化: [Jce.kt](Jce.kt) +- Protocol Buffers 的 optional 支持: [ProtoBufWithNullableSupport.kt](ProtoBufWithNullableSupport.kt) + +其中, ProtoBufWithNullableSupport.kt 的绝大部分源码来自 `kotlinx.serialization`. 原著权归该项目作者所有. +Mirai 所做的修改已经标记上了 `MIRAI MODIFY START` \ No newline at end of file From 26b9aa10e439d736b01c54ee9447710a7f3ed040 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 17:47:30 +0800 Subject: [PATCH 14/16] Jce head fix --- .../mirai/qqandroid/io/serialization/Jce.kt | 107 +++++++++++------- .../JceDecoderTest.kt | 102 ++++++++++++++++- 2 files changed, 165 insertions(+), 44 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt index 9096ede89..7ebc53161 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt @@ -8,12 +8,8 @@ import kotlinx.serialization.modules.EmptyModule import kotlinx.serialization.modules.SerialModule import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.ProtoBuf -import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse -import net.mamoe.mirai.utils.io.readIoBuffer import net.mamoe.mirai.utils.io.readString -import net.mamoe.mirai.utils.io.toIoBuffer -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract +import net.mamoe.mirai.utils.io.toReadPacket @PublishedApi internal val CharsetGBK = Charset.forName("GBK") @@ -383,11 +379,10 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo } } - if (!input.input.endOfInput) { - val tag = currentTagOrNull - if (tag != null && input.peakHead().tag > tag) { - return NullReader(this.input) - } + val tag = currentTagOrNull + val jceHead = input.peakHeadOrNull() + if (tag != null && (jceHead == null || jceHead.tag > tag)) { + return NullReader(this.input) } return super.beginStructure(desc, *typeParams) @@ -402,7 +397,8 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo } fun isTagOptional(tag: Int): Boolean { - return input.input.endOfInput || input.peakHead().tag > tag + val head = input.peakHeadOrNull() + return input.isEndOfInput || head == null || head.tag > tag } @Suppress("UNCHECKED_CAST") @@ -492,7 +488,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo @Suppress("UNCHECKED_CAST") override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { return decodeNullableSerializableValue(deserializer as DeserializationStrategy) as? T - ?: error("value with tag $currentTagOrNull(by ${deserializer.getClassName()}) is not optional but cannot find") + ?: error("value with tag $currentTagOrNull(by ${deserializer.getClassName()}) is not optional but cannot find. currentJceHead = ${input.currentJceHead}") } } @@ -500,33 +496,48 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo @UseExperimental(ExperimentalUnsignedTypes::class) internal inner class JceInput( @PublishedApi - internal val input: IoBuffer + internal val input: ByteReadPacket, + maxReadSize: Long = input.remaining ) : Closeable { - override fun close() = IoBuffer.Pool.recycle(input) + internal val leastRemaining = input.remaining - maxReadSize + internal val isEndOfInput: Boolean get() = input.remaining <= leastRemaining + + internal var currentJceHead: JceHead? = input.doReadHead().also { println("first jce head = $it") } + + override fun close() = input.close() + + internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() + internal fun peakHead(): JceHead = peakHeadOrNull() ?: error("no enough data to read head") @PublishedApi - internal fun readHead(): JceHead = input.readHead() ?: error("no enough data to read head") + internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") @PublishedApi - internal fun readHeadOrNull(): JceHead? = input.readHead() + internal fun readHeadOrNull(): JceHead? = input.doReadHead() - @PublishedApi - internal fun peakHead(): JceHead = input.makeView().readHead() ?: error("no enough data to read head") - - @PublishedApi - internal fun peakHeadOrNull(): JceHead? = input.makeView().readHead() - - @Suppress("NOTHING_TO_INLINE") // 避免 stacktrace 出现两个 readHead - private inline fun IoBuffer.readHead(): JceHead? { - if (endOfInput) return null + /** + * 读取下一个 head 存储到 [currentJceHead] + */ + private fun ByteReadPacket.doReadHead(): JceHead? { + if (isEndOfInput) { + currentJceHead = null + println("doReadHead: endOfInput") + return null + } val var2 = readUByte() val type = var2 and 15u var tag = var2.toUInt() shr 4 if (tag == 15u) { - if (endOfInput) return null + if (isEndOfInput) { + currentJceHead = null + println("doReadHead: endOfInput2") + return null + } tag = readUByte().toUInt() } - return JceHead(tag = tag.toInt(), type = type.toByte()) + currentJceHead = JceHead(tag = tag.toInt(), type = type.toByte()) + println("doReadHead: $currentJceHead") + return currentJceHead } fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag") @@ -583,8 +594,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo LIST -> ByteArray(readInt(0)) { readByte(0) } SIMPLE_LIST -> { val head = readHead() + readHead() check(head.type.toInt() == 0) { "type mismatch" } - input.readBytes(readInt(0)) + input.readBytes(readInt(0).also { println("list size=$it") }) } else -> error("type mismatch") } @@ -610,7 +622,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, charset = charset.kotlinCharset ) - else -> error("type mismatch: ${head.type}") + else -> error("type mismatch: ${head.type}, expecting 6 or 7 (for string)") } } @@ -762,40 +774,51 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo return dumpAsPacket(serializer, obj).readBytes() } + /** + * 注意 close [packet]!! + */ fun load(deserializer: DeserializationStrategy, packet: ByteReadPacket, length: Int = packet.remaining.toInt()): T { - packet.readIoBuffer(n = length).withUse { - val decoder = JceDecoder(JceInput(this)) - return decoder.decode(deserializer) - } + return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer) } override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T { - return bytes.toIoBuffer().withUse { - val decoder = JceDecoder(JceInput(this)) + return bytes.toReadPacket().use { + val decoder = JceDecoder(JceInput(it)) decoder.decode(deserializer) } } } -@UseExperimental(ExperimentalContracts::class) internal inline fun Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { - contract { - callsInPlace(block, kotlin.contracts.InvocationKind.UNKNOWN) - } + println("skipping to $tag start") while (true) { - if (this.input.endOfInput) { + if (isEndOfInput) { // 读不了了 + currentJceHead = null + println("skipping to $tag: endOfInput") return null } - val head = peakHead() + var head = currentJceHead + if (head == null) { // 没有新的 head 了 + head = readHeadOrNull() ?: return null + } + if (head.tag > tag) { + println("skipping to $tag: head.tag > tag") return null } - readHead() + // readHead() if (head.tag == tag) { + // readHeadOrNull() + currentJceHead = null + println("skipping to $tag: run block") return block(head) + } else { + println("skipping to $tag: tag not matching") } + println("skipping to $tag: skipField") this.skipField(head.type) + currentJceHead = readHeadOrNull() } } diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt index c065e0392..44f26d192 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt @@ -20,7 +20,9 @@ class JceDecoderTest { @SerialId(3) val int: Int = 123, @SerialId(4) val long: Long = 123, @SerialId(5) val float: Float = 123f, - @SerialId(6) val double: Double = 123.0 + @SerialId(6) val double: Double = 123.0, + @SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3), + @SerialId(8) val byteArray2: ByteArray = byteArrayOf(1, 2, 3) ) : JceStruct { override fun writeTo(output: JceOutput) = output.run { writeString(string, 0) @@ -30,7 +32,78 @@ class JceDecoderTest { writeLong(long, 4) writeFloat(float, 5) writeDouble(double, 6) + writeFully(byteArray, 7) + writeFully(byteArray2, 8) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TestSimpleJceStruct + + if (string != other.string) return false + if (byte != other.byte) return false + if (short != other.short) return false + if (int != other.int) return false + if (long != other.long) return false + if (float != other.float) return false + if (double != other.double) return false + if (!byteArray.contentEquals(other.byteArray)) return false + if (!byteArray2.contentEquals(other.byteArray2)) return false + + return true + } + + override fun hashCode(): Int { + var result = string.hashCode() + result = 31 * result + byte + result = 31 * result + short + result = 31 * result + int + result = 31 * result + long.hashCode() + result = 31 * result + float.hashCode() + result = 31 * result + double.hashCode() + result = 31 * result + byteArray.contentHashCode() + result = 31 * result + byteArray2.contentHashCode() + return result + } + } + + + @Test + fun testByteArray() { + + @Serializable + data class TestByteArray( + @SerialId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3) + ) : JceStruct { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TestByteArray + + if (!byteArray.contentEquals(other.byteArray)) return false + + return true + } + + override fun hashCode(): Int { + return byteArray.contentHashCode() + } + } + assertEquals( + TestByteArray(), + TestByteArray().toByteArray(TestByteArray.serializer()).loadAs(TestByteArray.serializer()) + ) + } + + @Test + fun testSimpleStruct() { + assertEquals( + TestSimpleJceStruct(), + TestSimpleJceStruct().toByteArray(TestSimpleJceStruct.serializer()).loadAs(TestSimpleJceStruct.serializer()) + ) } @@ -77,7 +150,7 @@ class JceDecoderTest { @Test fun testNestedList() { @Serializable - class TestNestedList( + data class TestNestedList( @SerialId(7) val array: List> = listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3)) ) : JceStruct @@ -133,6 +206,28 @@ class JceDecoderTest { }.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString(), "{01=[0x0002(2)]}") } + @Test + fun testMap3() { + @Serializable + class TestNestedMap( + @SerialId(7) val map: Map = mapOf(1.toByte() to shortArrayOf(2)) + ) : JceStruct + assertEquals("{0x01(1)=[0x0002(2)]}", buildJcePacket { + writeMap(mapOf(1.toByte() to shortArrayOf(2)), 7) + }.readBytes().loadAs(TestNestedMap.serializer()).map.contentToString()) + } + + @Test + fun testNestedMap2() { + @Serializable + class TestNestedMap( + @SerialId(7) val map: Map> = mapOf(1 to mapOf(1.toByte() to shortArrayOf(2))) + ) : JceStruct + assertEquals(buildJcePacket { + writeMap(mapOf(1 to mapOf(1.toByte() to shortArrayOf(2))), 7) + }.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString(), "{0x01(1)=[0x0002(2)]}") + } + @Test fun testNullableEncode() { @@ -186,6 +281,9 @@ class JceDecoderTest { @SerialId(0) val innerStructList: List ) : JceStruct + println(buildJcePacket { + writeCollection(listOf(TestSimpleJceStruct(), TestSimpleJceStruct()), 0) + }.readBytes().loadAs(OuterStruct.serializer()).innerStructList.toString()) assertEquals( buildJcePacket { writeCollection(listOf(TestSimpleJceStruct(), TestSimpleJceStruct()), 0) From 85b72ac4b8342a550a22cfebf3a81bbea4099fb9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 17:48:48 +0800 Subject: [PATCH 15/16] Remove println --- .../mirai/qqandroid/io/serialization/Jce.kt | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt index 7ebc53161..9ed4b391a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt @@ -55,7 +55,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo override fun endEncode(desc: SerialDescriptor) { parentEncoder.writeHead(MAP, this.tag) parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count) - println(this.output.toByteArray().toUHexString()) + // println(this.output.toByteArray().toUHexString()) parentEncoder.output.write(this.output.toByteArray()) }*/ @@ -340,7 +340,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo * 在 [KSerializer.serialize] 前 */ override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - //println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}") + //// println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}") when (desc) { // 由于 Byte 的数组有两种方式写入, 需特定读取器 ByteArraySerializer.descriptor -> { @@ -403,7 +403,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo @Suppress("UNCHECKED_CAST") override fun decodeNullableSerializableValue(deserializer: DeserializationStrategy): T? { - // println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}") + // // println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}") if (deserializer is NullReader) { return null } @@ -430,7 +430,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo else input.readByteArray(tag).toMutableList() as T } val tag = currentTag -// println(tag) +// // println(tag) @Suppress("SENSELESS_COMPARISON") // false positive if (input.skipToTagOrNull(tag) { return deserializer.deserialize(JceListReader(input.readInt(0), input)) @@ -502,17 +502,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo internal val leastRemaining = input.remaining - maxReadSize internal val isEndOfInput: Boolean get() = input.remaining <= leastRemaining - internal var currentJceHead: JceHead? = input.doReadHead().also { println("first jce head = $it") } + internal var currentJceHead: JceHead? = input.doReadHead().also { + // println("first jce head = $it") } - override fun close() = input.close() + override fun close() = input.close() - internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() - internal fun peakHead(): JceHead = peakHeadOrNull() ?: error("no enough data to read head") + internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() + internal fun peakHead(): JceHead = peakHeadOrNull() ?: error("no enough data to read head") - @PublishedApi - internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") + @PublishedApi + internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") - @PublishedApi + @PublishedApi internal fun readHeadOrNull(): JceHead? = input.doReadHead() /** @@ -521,7 +522,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo private fun ByteReadPacket.doReadHead(): JceHead? { if (isEndOfInput) { currentJceHead = null - println("doReadHead: endOfInput") + // println("doReadHead: endOfInput") return null } val var2 = readUByte() @@ -530,13 +531,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo if (tag == 15u) { if (isEndOfInput) { currentJceHead = null - println("doReadHead: endOfInput2") + // println("doReadHead: endOfInput2") return null } tag = readUByte().toUInt() } currentJceHead = JceHead(tag = tag.toInt(), type = type.toByte()) - println("doReadHead: $currentJceHead") + // println("doReadHead: $currentJceHead") return currentJceHead } @@ -596,9 +597,10 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo val head = readHead() readHead() check(head.type.toInt() == 0) { "type mismatch" } - input.readBytes(readInt(0).also { println("list size=$it") }) - } - else -> error("type mismatch") + input.readBytes(readInt(0).also { + // println("list size=$it") }) + } + else -> error("type mismatch") } } @@ -790,11 +792,11 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo } internal inline fun Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { - println("skipping to $tag start") + // println("skipping to $tag start") while (true) { if (isEndOfInput) { // 读不了了 currentJceHead = null - println("skipping to $tag: endOfInput") + // println("skipping to $tag: endOfInput") return null } @@ -804,19 +806,19 @@ internal inline fun Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) } if (head.tag > tag) { - println("skipping to $tag: head.tag > tag") + // println("skipping to $tag: head.tag > tag") return null } // readHead() if (head.tag == tag) { // readHeadOrNull() currentJceHead = null - println("skipping to $tag: run block") + // println("skipping to $tag: run block") return block(head) } else { - println("skipping to $tag: tag not matching") + // println("skipping to $tag: tag not matching") } - println("skipping to $tag: skipField") + // println("skipping to $tag: skipField") this.skipField(head.type) currentJceHead = readHeadOrNull() } From b85c91b25d180df9a9c4f9df6ea3841a7dec0e63 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 31 Jan 2020 17:52:28 +0800 Subject: [PATCH 16/16] Fix build --- .../mirai/qqandroid/io/serialization/Jce.kt | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt index 9ed4b391a..d9483355a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt @@ -502,18 +502,17 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo internal val leastRemaining = input.remaining - maxReadSize internal val isEndOfInput: Boolean get() = input.remaining <= leastRemaining - internal var currentJceHead: JceHead? = input.doReadHead().also { - // println("first jce head = $it") } + internal var currentJceHead: JceHead? = input.doReadHead() - override fun close() = input.close() + override fun close() = input.close() - internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() - internal fun peakHead(): JceHead = peakHeadOrNull() ?: error("no enough data to read head") + internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() + internal fun peakHead(): JceHead = peakHeadOrNull() ?: error("no enough data to read head") - @PublishedApi - internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") + @PublishedApi + internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") - @PublishedApi + @PublishedApi internal fun readHeadOrNull(): JceHead? = input.doReadHead() /** @@ -597,10 +596,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo val head = readHead() readHead() check(head.type.toInt() == 0) { "type mismatch" } - input.readBytes(readInt(0).also { - // println("list size=$it") }) - } - else -> error("type mismatch") + input.readBytes(readInt(0)) + } + else -> error("type mismatch") } }