Skip to content

Commit

Permalink
Relogin when receiving returnCode <= -10000, fix #1961
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Apr 2, 2022
1 parent 2a4b2f9 commit 7bb788a
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 38 deletions.
100 changes: 75 additions & 25 deletions mirai-core/src/commonMain/kotlin/network/components/PacketCodec.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

package net.mamoe.mirai.internal.network.components
Expand All @@ -14,6 +14,8 @@ import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.components.PacketCodec.Companion.PacketLogger
import net.mamoe.mirai.internal.network.components.PacketCodecException.Kind.*
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
Expand All @@ -28,10 +30,14 @@ import kotlin.io.use
*/
internal interface PacketCodec {
/**
* It's caller's responsibility to close [input]
* It's caller's responsibility to close [input].
*
* @throws PacketCodecException normal, known errors
* @throws Exception unexpected errors
* @param input received from sockets.
* @return decoded
*/
@Throws(PacketCodecException::class)
fun decodeRaw(client: SsoSession, input: ByteReadPacket): RawIncomingPacket

/**
Expand All @@ -50,17 +56,39 @@ internal interface PacketCodec {
}
}

/**
* Wraps an exception thrown by [PacketCodec.decodeRaw], which is not a [PacketCodecException] (meaning unexpected).
*/
internal data class ExceptionInPacketCodecException(
override val cause: Throwable,
) : IllegalStateException("Exception in PacketCodec.", cause)

internal class OicqDecodingException(
val targetException: Throwable
) : RuntimeException(
null, targetException,
true, // enableSuppression
false, // writableStackTrace
) {
/**
* Thrown by [PacketCodec.decodeRaw], representing an excepted error.
*/
internal class PacketCodecException(
val targetException: Throwable,
val kind: Kind,
) : NetworkException(recoverable = true, cause = targetException) {
constructor(message: String, kind: Kind) : this(IllegalStateException(message), kind)

enum class Kind {
/**
* 会触发重连
*/
SESSION_EXPIRED,

/**
* 只记录日志
*/
PROTOCOL_UPDATED,

/**
* 只记录日志
*/
OTHER,
}

override fun getStackTrace(): Array<StackTraceElement> {
return targetException.stackTrace
}
Expand All @@ -76,9 +104,12 @@ internal class PacketCodecImpl : PacketCodec {

val flag2 = readByte().toInt()
val flag3 = readByte().toInt()
check(flag3 == 0) {
"Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " +
"Remaining=${this.readBytes().toUHexString()}"
if (flag3 != 0) {
throw PacketCodecException(
"Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " +
"Remaining=${this.readBytes().toUHexString()}",
kind = PROTOCOL_UPDATED
)
}

readString(readInt() - 4)// uinAccount
Expand All @@ -90,12 +121,15 @@ internal class PacketCodecImpl : PacketCodec {
2 -> TEA.decrypt(buffer, DECRYPTER_16_ZERO, size)
1 -> TEA.decrypt(buffer, client.wLoginSigInfo.d2Key, size)
0 -> buffer
else -> error("Unknown flag2=$flag2")
else -> throw PacketCodecException("Unknown flag2=$flag2", PROTOCOL_UPDATED)
}.let { decryptedData ->
when (flag1) {
0x0A -> parseSsoFrame(client, decryptedData)
0x0B -> parseSsoFrame(client, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样.
else -> error("unknown flag1: ${flag1.toByte().toUHexString()}")
else -> throw PacketCodecException(
"unknown flag1: ${flag1.toByte().toUHexString()}",
PROTOCOL_UPDATED
)
}
}.let { raw ->
when (flag2) {
Expand All @@ -107,11 +141,11 @@ internal class PacketCodecImpl : PacketCodec {
try {
parseOicqResponse(client)
} catch (e: Throwable) {
throw OicqDecodingException(e)
throw PacketCodecException(e, PacketCodecException.Kind.OTHER)
}
}
)
else -> error("Unknown flag2=$flag2")
else -> error("unreachable")
}
}
}
Expand All @@ -136,11 +170,27 @@ internal class PacketCodecImpl : PacketCodec {
PacketLogger.verbose { "sequenceId = $ssoSequenceId" }

val returnCode = readInt()
check(returnCode == 0) {
if (returnCode != 0) {
if (returnCode <= -10000) {
// https://github.com/mamoe/mirai/issues/470
error("returnCode = $returnCode")
} else "returnCode = $returnCode"
// #470: -10008, 例如在手机QQ强制下线机器人
// #1957: -10106, 未知原因, 但会导致收不到消息

throw PacketCodecException(
"Received packet returnCode = $returnCode, which may mean session expired.",
SESSION_EXPIRED
)

// 备注: 之后该异常将会导致 NetworkHandler close, 然后由 selector 触发重连.
// 重连时会在 net.mamoe.mirai.internal.network.components.SsoProcessorImpl.login 进行 FastLogin.
// 不确定在这种情况下执行 FastLogin 是否正确. 若有问题, 考虑强制执行 SlowLogin (by invalidating session).
} else {
throw PacketCodecException(
"Received unknown packet returnCode = $returnCode, ignoring. Please report to https://github.com/mamoe/mirai/issues/new/choose if you see anything abnormal",
OTHER
)

// 备注: OTHER 不会触发重连, 只会记录日志.
}
}

if (PacketLogger.isEnabled) {
Expand Down Expand Up @@ -182,7 +232,7 @@ internal class PacketCodecImpl : PacketCodec {
}
}
8 -> input
else -> error("unknown dataCompressed flag: $dataCompressed")
else -> throw PacketCodecException("Unknown dataCompressed flag: $dataCompressed", PROTOCOL_UPDATED)
}

// body
Expand Down Expand Up @@ -221,7 +271,7 @@ internal class PacketCodecImpl : PacketCodec {
TEA.decrypt(data, peerShareKey)
}
3 -> {
val size = (this.remaining - 1).toInt();
val size = (this.remaining - 1).toInt()
// session
TEA.decrypt(
this.readBytes(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

package net.mamoe.mirai.internal.network.handler.selector
Expand All @@ -19,6 +19,10 @@ internal abstract class NetworkException : Exception {
this.recoverable = recoverable
}

constructor(recoverable: Boolean, cause: Throwable?) : super(cause) {
this.recoverable = recoverable
}

constructor(message: String, recoverable: Boolean) : super(message) {
this.recoverable = recoverable
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

package net.mamoe.mirai.internal.network.impl.netty
Expand All @@ -22,6 +22,8 @@ import net.mamoe.mirai.internal.network.components.*
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
import net.mamoe.mirai.internal.network.handler.selector.NetworkHandlerSelector
import net.mamoe.mirai.internal.network.handler.state.StateObserver
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.utils.*
Expand Down Expand Up @@ -52,14 +54,27 @@ internal open class NettyNetworkHandler(
// exception handling
///////////////////////////////////////////////////////////////////////////
protected open fun handleExceptionInDecoding(error: Throwable) {
if (error is OicqDecodingException) {
fun passToExceptionHandler() {
// Typically, just log the exception
coroutineContext[CoroutineExceptionHandler]!!.handleException(
coroutineContext,
ExceptionInPacketCodecException(error.unwrap<PacketCodecException>())
)
}

if (error is PacketCodecException) {
if (error.targetException is EOFException) return
when (error.kind) {
PacketCodecException.Kind.SESSION_EXPIRED -> {
setState { StateClosed(error) }
return
}
PacketCodecException.Kind.PROTOCOL_UPDATED -> passToExceptionHandler()
PacketCodecException.Kind.OTHER -> passToExceptionHandler()
}
}

coroutineContext[CoroutineExceptionHandler]!!.handleException(
coroutineContext,
ExceptionInPacketCodecException(error.unwrap<OicqDecodingException>())
)
passToExceptionHandler()
}

protected open fun handlePipelineException(ctx: ChannelHandlerContext, error: Throwable) {
Expand Down Expand Up @@ -370,6 +385,11 @@ internal open class NettyNetworkHandler(
override fun toString(): String = "StateOK"
}

/**
* 这会永久关闭这个 [NettyNetworkHandler], 但通常 bot 会使用 [NetworkHandlerSelector], selector 会创建新的 [NettyNetworkHandler] 来恢复连接.
*
* 备注: selector 会恢复连接, 当且仅当 [exception] 类型是 [NetworkException] 且 [NetworkException.recoverable] 为 `true`.
*/
protected inner class StateClosed(
val exception: Throwable?,
) : NettyState(State.CLOSED) {
Expand Down

1 comment on commit 7bb788a

@Him188
Copy link
Member Author

@Him188 Him188 commented on 7bb788a Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

实际上解决 #1957

Please sign in to comment.