Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login: Add StatSvc.SimpleGet: Update tlv and solve constant connection dropping issue #1150

Merged
merged 17 commits into from
Apr 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5518,6 +5518,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun getProtocol ()Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;
public final fun getReconnectPeriodMillis ()J
public final fun getReconnectionRetryTimes ()I
public final fun getStatHeartbeatPeriodMillis ()J
public final fun getWorkingDir ()Ljava/io/File;
public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun isConvertLineSeparator ()Z
Expand Down Expand Up @@ -5560,6 +5561,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun setProtocol (Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;)V
public final fun setReconnectPeriodMillis (J)V
public final fun setReconnectionRetryTimes (I)V
public final fun setStatHeartbeatPeriodMillis (J)V
public final fun setWorkingDir (Ljava/io/File;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5518,6 +5518,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun getProtocol ()Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;
public final fun getReconnectPeriodMillis ()J
public final fun getReconnectionRetryTimes ()I
public final fun getStatHeartbeatPeriodMillis ()J
public final fun getWorkingDir ()Ljava/io/File;
public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun isConvertLineSeparator ()Z
Expand Down Expand Up @@ -5560,6 +5561,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun setProtocol (Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;)V
public final fun setReconnectPeriodMillis (J)V
public final fun setReconnectionRetryTimes (I)V
public final fun setStatHeartbeatPeriodMillis (J)V
public final fun setWorkingDir (Ljava/io/File;)V
}

Expand Down
11 changes: 9 additions & 2 deletions mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,16 @@ public open class BotConfiguration { // open for Java
// Connection
///////////////////////////////////////////////////////////////////////////

/** 心跳周期. 过长会导致被服务器断开连接. */
/** 连接心跳包周期. 过长会导致被服务器断开连接. */
public var heartbeatPeriodMillis: Long = 60.secondsToMillis

/**
* 状态心跳包周期. 过长会导致掉线.
* 该值会在登录时根据服务器下发的配置自动进行更新.
* @since 2.6
*/
public var statHeartbeatPeriodMillis: Long = 300.secondsToMillis

/**
* 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
Expand Down Expand Up @@ -545,4 +552,4 @@ internal val deviceInfoStub: (Bot) -> DeviceInfo = {
MiraiLogger.TopLevel.warning("未指定设备信息, 已使用随机设备信息. 请查看 BotConfiguration.deviceInfo 以获取更多信息.")
MiraiLogger.TopLevel.warning("Device info isn't specified. Please refer to BotConfiguration.deviceInfo for more information")
DeviceInfo.random()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo

private var _packetReceiverJob: Job? = null
private var heartbeatJob: Job? = null
private var statHeartbeatJob: Job? = null

private val packetReceiveLock: Mutex = Mutex()

Expand Down Expand Up @@ -93,6 +94,25 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
}.also { _packetReceiverJob = it }
}

private fun startStatHeartbeatJobOrKill(cancelCause: CancellationException? = null): Job {
statHeartbeatJob?.cancel(cancelCause)

return this@QQAndroidBotNetworkHandler.launch(CoroutineName("statHeartbeatJob")) statHeartbeatJob@{
while (this.isActive) {
delay(bot.configuration.statHeartbeatPeriodMillis)
val failException = doStatHeartbeat()
if (failException != null) {
delay(bot.configuration.firstReconnectDelayMillis)

bot.launch {
BotOfflineEvent.Dropped(bot, failException).broadcast()
}
return@statHeartbeatJob
}
}
}.also { statHeartbeatJob = it }
}

private fun startHeartbeatJobOrKill(cancelCause: CancellationException? = null): Job {
heartbeatJob?.cancel(cancelCause)

Expand All @@ -118,6 +138,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
override suspend fun closeEverythingAndRelogin(host: String, port: Int, cause: Throwable?, step: Int) {
heartbeatJob?.cancel(CancellationException("relogin", cause))
heartbeatJob?.join()
statHeartbeatJob?.cancel(CancellationException("relogin", cause))
statHeartbeatJob?.join()
_packetReceiverJob?.cancel(CancellationException("relogin", cause))
_packetReceiverJob?.join()
if (::channel.isInitialized) {
Expand Down Expand Up @@ -177,14 +199,18 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo

// println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
registerClientOnline()
startStatHeartbeatJobOrKill()
startHeartbeatJobOrKill()
bot.eventChannel.subscribeOnce<BotOnlineEvent>(this.coroutineContext) {
val bot = (bot as QQAndroidBot)
if (bot.firstLoginSucceed && bot.client.wLoginSigInfoInitialized) {
launch {
while (isActive) {
bot.client.wLoginSigInfo.run {
delay(10.minutes)
bot.client.wLoginSigInfo.vKey.run {
//由过期时间最短的且不会被skey更换更新的vkey计算重新登录的时间
val delay = (expireTime - creationTime).seconds - 5.minutes
logger.info { "Scheduled refresh login session in ${delay.toHumanReadableString()}." }
delay(delay)
}
runCatching {
doFastLogin()
Expand All @@ -194,6 +220,20 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
}
}
}
launch {
while (isActive) {
bot.client.wLoginSigInfo.sKey.run {
val delay = (expireTime - creationTime).seconds - 5.minutes
logger.info { "Scheduled key refresh in ${delay.toHumanReadableString()}." }
delay(delay)
}
runCatching {
refreshKeys()
Him188 marked this conversation as resolved.
Show resolved Hide resolved
}.onFailure {
logger.error("Failed to refresh key.", it)
}
}
}
}
}
}
Expand Down Expand Up @@ -461,6 +501,17 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
logger.info { "Syncing friend message history: Success." }
}

private suspend fun doStatHeartbeat(): Throwable? {
return retryCatching(2) {
StatSvc.SimpleGet(bot.client)
.sendAndExpect<StatSvc.SimpleGet.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 2
)
return null
}.exceptionOrNull()
}

private suspend fun doHeartBeat(): Throwable? {
return retryCatching(2) {
Heartbeat.Alive(bot.client)
Expand Down
14 changes: 13 additions & 1 deletion mirai-core/src/commonMain/kotlin/network/keys.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUShort
import kotlinx.serialization.Serializable
import net.mamoe.mirai.internal.network.getRandomByteArray
import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger
import net.mamoe.mirai.internal.utils.crypto.TEA
Expand Down Expand Up @@ -111,8 +112,19 @@ internal class WLoginSigInfo(
// val pt4Token: ByteArray,
var wtSessionTicket: WtSessionTicket,
var wtSessionTicketKey: ByteArray,
var deviceToken: ByteArray
var deviceToken: ByteArray,
var encryptedDownloadSession: EncryptedDownloadSession? = null
) {

//图片加密下载
//是否加密从bigdatachannel处得知
@Serializable
internal class EncryptedDownloadSession(
val appId: Long,//1600000226L
val stKey: ByteArray,
val stSig: ByteArray
)

override fun toString(): String {
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ internal object KnownPacketFactories {
WtLogin.ExchangeEmp,
StatSvc.Register,
StatSvc.GetOnlineStatus,
StatSvc.SimpleGet,
StatSvc.GetDevLoginInfo,
MessageSvcPbGetMsg,
MessageSvcPushForceOffline,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,29 @@ internal class StatSvc {
}
}

internal object SimpleGet : OutgoingPacketFactory<SimpleGet.Response>("StatSvc.SimpleGet") {
internal object Response : Packet {
override fun toString(): String = "Response(SimpleGet.Response)"
}

operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(
client,
bodyType = 1,
extraData = client.wLoginSigInfo.d2.data,
key = client.wLoginSigInfo.d2Key
) {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) {

}
}

override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
return Response
}
}

internal object Register : OutgoingPacketFactory<Register.Response>("StatSvc.register") {

internal class Response(
Expand All @@ -104,7 +127,7 @@ internal class StatSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val packet = readUniPacket(SvcRespRegister.serializer())
packet.iHelloInterval.let {
// bot.configuration.heartbeatPeriodMillis = it.times(1000).toLong()
bot.configuration.statHeartbeatPeriodMillis = it.times(1000).toLong()
}

return Response(packet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,13 @@ internal class WtLogin {

tlvMap119[0x118]?.let { client.mainDisplayName = it }
tlvMap119[0x108]?.let { client.ksid = it }

tlvMap119[0x11a]?.read {
val faceId = readShort().toInt()
val age = readByte().toInt()
val gender = readByte().toInt()
val nickLength = readByte().toInt()
bot.nick = readString(nickLength)
}
var openId: ByteArray? = null
var openKey: ByteArray? = null
tlvMap119[0x125]?.read {
Expand Down Expand Up @@ -303,6 +309,7 @@ internal class WtLogin {
}.getOrElse { ByteReadPacket(byteArrayOf()) }
)
}

tlvMap119[0x167]?.let {
val imgType = byteArrayOf(readByte())
val imgFormat = byteArrayOf(readByte())
Expand Down Expand Up @@ -402,8 +409,12 @@ internal class WtLogin {
client.wLoginSigInfo.wtSessionTicket.data
), creationTime
)

wtSessionTicketKey = tlvMap119.getOrDefault(0x134, client.wLoginSigInfo.wtSessionTicketKey)
deviceToken = tlvMap119.getOrDefault(0x322, deviceToken)
encryptedDownloadSession = tlvMap119[0x11d]?.let {
client.analysisTlv11d(it)
} ?: encryptedDownloadSession
}
} else {
var a1: ByteArray? = tlvMap119.getOrFail(0x106)
Expand Down Expand Up @@ -494,7 +505,10 @@ internal class WtLogin {
da2 = tlvMap119.getOrEmpty(0x203),
wtSessionTicket = WtSessionTicket(tlvMap119.getOrEmpty(0x133), creationTime),
wtSessionTicketKey = tlvMap119.getOrEmpty(0x134),
deviceToken = tlvMap119.getOrEmpty(0x322)
deviceToken = tlvMap119.getOrEmpty(0x322),
encryptedDownloadSession = tlvMap119[0x11d]?.let {
client.analysisTlv11d(it)
}
)
}
//bot.network.logger.error(client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import kotlinx.io.core.*
import net.mamoe.mirai.internal.network.LoginExtraData
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.WLoginSigInfo
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.Tlv
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
Expand Down Expand Up @@ -116,6 +117,23 @@ internal interface WtLoginExt { // so as not to register to global extension
}
}

/**
* Encrypt sig and key for pic downloading
*/
fun QQAndroidClient.analysisTlv11d(t11d: ByteArray): WLoginSigInfo.EncryptedDownloadSession = t11d.read {
val appid = readInt().toLong().and(4294967295L)
val stKey = ByteArray(16)
readAvailable(stKey)
val stSigLength = readUShort().toInt()
val stSig = ByteArray(stSigLength)
readAvailable(stSig)
WLoginSigInfo.EncryptedDownloadSession(
appid,
stKey,
stSig
)
}

/**
* pwd flag
*/
Expand Down