From 553ea9abbc659c146e662bc3de95a9f78f94a398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E8=8E=B9=C2=B7=E7=BA=A4=E7=BB=AB?= Date: Mon, 2 Jan 2023 23:39:36 +0800 Subject: [PATCH] [core] Introduce CacheValidator for validating caches (#2388) * [core] Export DeviceInfo.serializer() to mirai-core * [core] Introduce CacheValidator for validating caches --- .../src/commonMain/kotlin/utils/DeviceInfo.kt | 2 + .../src/commonMain/kotlin/QQAndroidBot.kt | 12 ++ .../components/AccountSecretsManager.kt | 4 +- .../network/components/BdhSessionSyncer.kt | 7 +- .../network/components/CacheValidator.kt | 115 ++++++++++++++++++ .../kotlin/network/components/SsoProcessor.kt | 2 + 6 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/network/components/CacheValidator.kt diff --git a/mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt b/mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt index 6f8a4e0aa..275acba20 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt @@ -118,6 +118,8 @@ public expect class DeviceInfo( */ @JvmStatic public fun random(random: Random): DeviceInfo + + public fun serializer(): KSerializer } /** diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index ce2c3b5fd..55c4e5c58 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -52,6 +52,7 @@ import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.utils.ImagePatcher import net.mamoe.mirai.internal.utils.ImagePatcherImpl +import net.mamoe.mirai.internal.utils.actualCacheDir import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiLogger @@ -208,6 +209,14 @@ internal open class QQAndroidBot constructor( set(SsoProcessorContext, SsoProcessorContextImpl(bot)) set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext))) + + val cacheValidator = CacheValidatorImpl( + get(SsoProcessorContext), + configuration.actualCacheDir().resolve("validator.bin"), + networkLogger.subLogger("CacheValidator"), + ) + set(CacheValidator, cacheValidator) + set(HeartbeatProcessor, HeartbeatProcessorImpl()) set(HeartbeatScheduler, TimeBasedHeartbeatSchedulerImpl(networkLogger.subLogger("HeartbeatScheduler"))) set(HttpClientProvider, HttpClientProviderImpl()) @@ -251,6 +260,9 @@ internal open class QQAndroidBot constructor( configuration.createAccountsSecretsManager(bot.logger.subLogger("AccountSecretsManager")), ) set(ImagePatcher, ImagePatcherImpl()) + + cacheValidator.register(get(AccountSecretsManager)) + cacheValidator.register(get(BdhSessionSyncer)) } /** diff --git a/mirai-core/src/commonMain/kotlin/network/components/AccountSecretsManager.kt b/mirai-core/src/commonMain/kotlin/network/components/AccountSecretsManager.kt index e71d075cb..1a23f3354 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/AccountSecretsManager.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/AccountSecretsManager.kt @@ -35,10 +35,10 @@ import kotlin.jvm.Volatile * @see FileCacheAccountSecretsManager * @see CombinedAccountSecretsManager */ -internal interface AccountSecretsManager { +internal interface AccountSecretsManager : Cacheable { fun saveSecrets(account: BotAccount, secrets: AccountSecrets) fun getSecrets(account: BotAccount): AccountSecrets? - fun invalidate() + override fun invalidate() companion object : ComponentKey } diff --git a/mirai-core/src/commonMain/kotlin/network/components/BdhSessionSyncer.kt b/mirai-core/src/commonMain/kotlin/network/components/BdhSessionSyncer.kt index 531bd1cce..0f4c75da4 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/BdhSessionSyncer.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/BdhSessionSyncer.kt @@ -22,7 +22,7 @@ import net.mamoe.mirai.internal.utils.actualCacheDir import net.mamoe.mirai.utils.* import kotlin.jvm.Volatile -internal interface BdhSessionSyncer { +internal interface BdhSessionSyncer : Cacheable { val bdhSession: CompletableDeferred val hasSession: Boolean @@ -77,6 +77,11 @@ internal class BdhSessionSyncerImpl( private val serverListCacheFile: MiraiFile get() = configuration.actualCacheDir().resolve("servers.json") + override fun invalidate() { + sessionCacheFile.delete() + serverListCacheFile.delete() + } + override fun loadServerListFromCache() { val serverListCacheFile = this.serverListCacheFile if (serverListCacheFile.isFile) { diff --git a/mirai-core/src/commonMain/kotlin/network/components/CacheValidator.kt b/mirai-core/src/commonMain/kotlin/network/components/CacheValidator.kt new file mode 100644 index 000000000..68094bd10 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/components/CacheValidator.kt @@ -0,0 +1,115 @@ +/* + * 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. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.network.components + +import io.ktor.utils.io.core.* +import net.mamoe.mirai.internal.network.ProtoBufForCache +import net.mamoe.mirai.internal.network.component.ComponentKey +import net.mamoe.mirai.internal.utils.MiraiProtocolInternal +import net.mamoe.mirai.internal.utils.io.writeShortLVString +import net.mamoe.mirai.utils.* + +/** + * Validator for checking caches is usable for current bot or not. + */ +internal interface CacheValidator { + fun register(cache: Cacheable) + + fun validate() + + companion object : ComponentKey +} + +internal interface Cacheable { + fun invalidate() +} + +internal class CacheValidatorImpl( + private val ssoProcessorContext: SsoProcessorContext, + private val hashFile: MiraiFile, + private val logger: MiraiLogger, +) : CacheValidator { + private val caches: MutableList = mutableListOf() + + override fun register(cache: Cacheable) { + caches.add(cache) + } + + override fun validate() { + + val hash: ByteArray = buildPacket { + val botConf = ssoProcessorContext.configuration + writeInt(botConf.protocol.ordinal) + val internalProtocol = MiraiProtocolInternal[botConf.protocol] + writeShortLVString(internalProtocol.apkId) + writeLong(internalProtocol.id) + writeShortLVString(internalProtocol.sdkVer) + writeInt(internalProtocol.miscBitMap) + writeInt(internalProtocol.subSigMap) + writeInt(internalProtocol.mainSigMap) + writeShortLVString(internalProtocol.sign) + writeLong(internalProtocol.buildTime) + writeInt(internalProtocol.ssoVersion) + + val device = ssoProcessorContext.device + + @Suppress("INVISIBLE_MEMBER") + writeFully(ProtoBufForCache.encodeToByteArray(DeviceInfo.serializer(), device)) + }.let { pkg -> + try { + pkg.readBytes() + } finally { + pkg.release() + } + }.sha1() + + if (!hashFile.exists()) { + logger.verbose { "Invalidate caches because hash file not available." } + + invalidate() + + kotlin.runCatching { + hashFile.writeBytes(hash) + }.onFailure { logger.warning("Exception in writing hash to validation file", it) } + return + } + if (!hashFile.isFile) { + logger.verbose { "hash file isn't a file." } + invalidate() + + kotlin.runCatching { + hashFile.deleteRecursively() + hashFile.writeBytes(hash) + }.onFailure { logger.warning("Exception in writing hash to validation file", it) } + return + } + + try { + val hashInFile = hashFile.readBytes() + if (hashInFile.contentEquals(hash)) { + logger.verbose { "Validated caches." } + return + } + + logger.verbose { "Hash not match. Invaliding caches....." } + invalidate() + + hashFile.writeBytes(hash) + } catch (e: Throwable) { + logger.warning("Exception in validation. Invalidating.....", e) + invalidate() + } + + } + + private fun invalidate() { + caches.forEach { it.invalidate() } + } +} diff --git a/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt b/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt index 0675b7cd2..76693ec53 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt @@ -146,6 +146,8 @@ internal class SsoProcessorImpl( * Do login. Throws [LoginFailedException] if failed */ override suspend fun login(handler: NetworkHandler) = withExceptionCollector { + components[CacheValidator].validate() + components[BdhSessionSyncer].loadServerListFromCache() try { if (client.wLoginSigInfoInitialized) {