Highway big data channel (#917)

- Fix retryWithServers exception suppress
- ConfigPush BDH session
- ConfigPush SSO change server
- Upload group PTT silk, #577
- Support upload private image through highway, close #194;
- Add fallback strategies for uploading group voice #577 and private image #916
- Upload all resources through highway BDH
- Support concurrent uploading
- Add BotConfiguration.highwayUploadCoroutineCount

close #916, close #577, close #194
This commit is contained in:
Him188 2021-01-26 19:08:01 +08:00 committed by GitHub
parent 8ad0b91974
commit d8b1505181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1230 additions and 979 deletions

View File

@ -5329,6 +5329,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun getFirstReconnectDelayMillis ()J public final fun getFirstReconnectDelayMillis ()J
public final fun getHeartbeatPeriodMillis ()J public final fun getHeartbeatPeriodMillis ()J
public final fun getHeartbeatTimeoutMillis ()J public final fun getHeartbeatTimeoutMillis ()J
public final fun getHighwayUploadCoroutineCount ()I
public final fun getJson ()Lkotlinx/serialization/json/Json; public final fun getJson ()Lkotlinx/serialization/json/Json;
public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver; public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver;
public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1;
@ -5366,6 +5367,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun setFirstReconnectDelayMillis (J)V public final fun setFirstReconnectDelayMillis (J)V
public final fun setHeartbeatPeriodMillis (J)V public final fun setHeartbeatPeriodMillis (J)V
public final fun setHeartbeatTimeoutMillis (J)V public final fun setHeartbeatTimeoutMillis (J)V
public final fun setHighwayUploadCoroutineCount (I)V
public final fun setJson (Lkotlinx/serialization/json/Json;)V public final fun setJson (Lkotlinx/serialization/json/Json;)V
public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V
public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V

View File

@ -12,7 +12,7 @@
import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.Attribute
object Versions { object Versions {
const val project = "2.2.0-dev-1" const val project = "2.2.0-dev-bdh-2"
const val kotlinCompiler = "1.4.21" const val kotlinCompiler = "1.4.21"
const val kotlinStdlib = "1.4.21" const val kotlinStdlib = "1.4.21"

View File

@ -120,6 +120,16 @@ public open class BotConfiguration { // open for Java
/** 使用协议类型 */ /** 使用协议类型 */
public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE
/**
* Highway 通道上传图片, 语音, 文件等资源时的协程数量.
*
* 每个协程的速度约为 200KB/s. 协程数量越多越快, 同时也更要求性能.
* 默认 [CPU 核心数][Runtime.availableProcessors].
*
* @since 2.2
*/
public var highwayUploadCoroutineCount: Int = Runtime.getRuntime().availableProcessors()
/** /**
* 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息. * 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息.
* @see fileBasedDeviceInfo 使用指定文件存储设备信息 * @see fileBasedDeviceInfo 使用指定文件存储设备信息

View File

@ -12,10 +12,11 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DeprecatedCallableAddReplaceWith") @Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DeprecatedCallableAddReplaceWith")
@Deprecated( @Deprecated(
@ -30,4 +31,14 @@ public suspend inline fun <R> runBIO(
public suspend inline fun <R> runBIO( public suspend inline fun <R> runBIO(
noinline block: () -> R noinline block: () -> R
): R = runInterruptible(context = Dispatchers.IO, block = block) ): R = runInterruptible(context = Dispatchers.IO, block = block)
public inline fun CoroutineScope.launchWithPermit(
semaphore: Semaphore,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline block: suspend () -> Unit
): Job {
return launch(coroutineContext) {
semaphore.withPermit { block() }
}
}

View File

@ -20,6 +20,24 @@ public inline fun <reified T> Any?.safeCast(): T? = this as? T
public inline fun <reified T> Any?.castOrNull(): T? = this as? T public inline fun <reified T> Any?.castOrNull(): T? = this as? T
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly
@kotlin.internal.LowPriorityInOverloadResolution
public inline fun <R, T : R> Result<T>.recoverCatchingSuppressed(transform: (exception: Throwable) -> R): Result<R> {
return when (val exception = exceptionOrNull()) {
null -> this
else -> {
try {
Result.success(transform(exception))
} catch (e: Throwable) {
e.addSuppressed(exception)
Result.failure(e)
}
}
}
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly @kotlin.internal.InlineOnly
@kotlin.internal.LowPriorityInOverloadResolution @kotlin.internal.LowPriorityInOverloadResolution

View File

@ -68,6 +68,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
} }
// region network // region network
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
val network: N get() = _network val network: N get() = _network

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.* import net.mamoe.mirai.internal.network.protocol.packet.chat.*
@ -745,17 +746,18 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
) )
).toByteArray(LongMsg.ReqBody.serializer()) ).toByteArray(LongMsg.ReqBody.serializer())
Highway.uploadResource( body.toExternalResource().use { resource ->
bot, Highway.uploadResourceBdh(
response.proto.uint32UpIp.zip(response.proto.uint32UpPort), bot = bot,
response.proto.msgSig, resource = resource,
body.toExternalResource(null), kind = when (isLong) {
when (isLong) { true -> ResourceKind.GROUP_LONG_MESSAGE
true -> "group long message" false -> ResourceKind.GROUP_FORWARD_MESSAGE
false -> "group forward message" },
}, commandId = 27,
27 initialTicket = response.proto.msgSig
) )
}
} }
} }

View File

@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.packet.chat.* import net.mamoe.mirai.internal.network.protocol.packet.chat.*
import net.mamoe.mirai.internal.network.useNextServers
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -63,6 +64,8 @@ internal class QQAndroidBot constructor(
return client return client
} }
override val bot: QQAndroidBot get() = this
internal var firstLoginSucceed: Boolean = false internal var firstLoginSucceed: Boolean = false
inline val json get() = configuration.json inline val json get() = configuration.json

View File

@ -22,15 +22,16 @@ import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.ImageUploadEvent
import net.mamoe.mirai.internal.message.OfflineFriendImage import net.mamoe.mirai.internal.message.OfflineFriendImage
import net.mamoe.mirai.internal.message.getImageType import net.mamoe.mirai.internal.message.getImageType
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind.PRIVATE_IMAGE
import net.mamoe.mirai.internal.network.highway.postImage import net.mamoe.mirai.internal.network.highway.postImage
import net.mamoe.mirai.internal.network.highway.sizeToString import net.mamoe.mirai.internal.network.highway.tryServers
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
import kotlin.time.measureTime
internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info
@ -53,7 +54,7 @@ internal abstract class AbstractUser(
if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) { if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
} }
val response = bot.network.run { val resp = bot.network.run {
LongConn.OffPicUp( LongConn.OffPicUp(
bot.client, Cmd0x352.TryUpImgReq( bot.client, Cmd0x352.TryUpImgReq(
srcUin = bot.id.toInt(), srcUin = bot.id.toInt(),
@ -73,56 +74,64 @@ internal abstract class AbstractUser(
is Member -> "temp" is Member -> "temp"
else -> "unknown" else -> "unknown"
} }
return when (response) { return when (resp) {
is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage( is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(
imageId = generateImageIdFromResourceId( imageId = generateImageIdFromResourceId(
resourceId = response.resourceId, resourceId = resp.resourceId,
format = getImageType(response.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME } format = getImageType(resp.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME }
?: resource.formatName ?: resource.formatName
) ?: response.resourceId ) ?: resp.resourceId
).also { ).also {
ImageUploadEvent.Succeed(this, resource, it).broadcast() ImageUploadEvent.Succeed(this, resource, it).broadcast()
} }
is LongConn.OffPicUp.Response.RequireUpload -> { is LongConn.OffPicUp.Response.RequireUpload -> {
bot.network.logger.verbose {
"[Http] Uploading $kind image, size=${resource.size.sizeToString()}"
}
val time = measureTime { kotlin.runCatching {
Mirai.Http.postImage( Highway.uploadResourceBdh(
"0x6ff0070", bot = bot,
bot.id, resource = resource,
null, kind = PRIVATE_IMAGE,
imageInput = resource, commandId = 1,
uKeyHex = response.uKey.toUHexString("") initialTicket = resp.uKey
) )
} }.recoverCatchingSuppressed {
tryServers(
bot.network.logger.verbose { bot = bot,
"[Http] Uploading $kind image: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" servers = resp.serverIp.zip(resp.serverPort),
} resourceSize = resource.size,
resourceKind = PRIVATE_IMAGE,
/* channelKind = ChannelKind.HTTP
HighwayHelper.uploadImageToServers( ) { ip, port ->
bot, Mirai.Http.postImage(
response.serverIp.zip(response.serverPort), serverIp = ip, serverPort = port,
response.uKey, htcmd = "0x6ff0070",
image, uin = bot.id,
kind = "friend", groupcode = null,
commandId = 1 imageInput = resource,
)*/ uKeyHex = resp.uKey.toUHexString("")
// 为什么不能 ?? )
}
}.recoverCatchingSuppressed {
Mirai.Http.postImage(
serverIp = "htdata2.qq.com",
htcmd = "0x6ff0070",
uin = bot.id,
groupcode = null,
imageInput = resource,
uKeyHex = resp.uKey.toUHexString("")
)
}.getOrThrow()
OfflineFriendImage( OfflineFriendImage(
generateImageIdFromResourceId(response.resourceId, resource.formatName) ?: response.resourceId generateImageIdFromResourceId(resp.resourceId, resource.formatName) ?: resp.resourceId
).also { ).also {
ImageUploadEvent.Succeed(this, resource, it).broadcast() ImageUploadEvent.Succeed(this, resource, it).broadcast()
} }
} }
is LongConn.OffPicUp.Response.Failed -> { is LongConn.OffPicUp.Response.Failed -> {
ImageUploadEvent.Failed(this, resource, -1, response.message).broadcast() ImageUploadEvent.Failed(this, resource, -1, resp.message).broadcast()
error(response.message) error(resp.message)
} }
} }
} }

View File

@ -13,6 +13,7 @@
package net.mamoe.mirai.internal.contact package net.mamoe.mirai.internal.contact
import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
@ -21,12 +22,16 @@ import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.*
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_IMAGE
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_VOICE
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -152,16 +157,16 @@ internal class GroupImpl(
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() } .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
} }
is ImgStore.GroupPicUp.Response.RequireUpload -> { is ImgStore.GroupPicUp.Response.RequireUpload -> {
Highway.uploadResource( // val servers = response.uploadIpList.zip(response.uploadPortList)
bot, Highway.uploadResourceBdh(
response.uploadIpList.zip(response.uploadPortList), bot = bot,
response.uKey, resource = resource,
resource, kind = GROUP_IMAGE,
kind = "group image", commandId = 2,
commandId = 2 initialTicket = response.uKey
) )
val resourceId = resource.calculateResourceId()
return OfflineGroupImage(imageId = resourceId) return OfflineGroupImage(imageId = resource.calculateResourceId())
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() } .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
} }
} }
@ -169,20 +174,36 @@ internal class GroupImpl(
} }
override suspend fun uploadVoice(resource: ExternalResource): Voice { override suspend fun uploadVoice(resource: ExternalResource): Voice {
if (resource.size > 1048576) {
throw OverFileSizeMaxException()
}
return bot.network.run { return bot.network.run {
val response: PttStore.GroupPttUp.Response.RequireUpload = kotlin.runCatching {
PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect() val (_) = Highway.uploadResourceBdh(
bot = bot,
resource = resource,
kind = GROUP_VOICE,
commandId = 29,
extendInfo = PttStore.GroupPttUp.createTryUpPttPack(bot.id, id, resource)
.toByteArray(Cmd0x388.ReqBody.serializer()),
)
}.recoverCatchingSuppressed {
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()) {
is PttStore.GroupPttUp.Response.RequireUpload -> {
tryServers(
bot,
resp.uploadIpList.zip(resp.uploadPortList),
resource.size,
GROUP_VOICE,
ChannelKind.HTTP
) { ip, port ->
Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey)
}
}
}
}.getOrThrow()
// val body = resp?.loadAs(Cmd0x388.RspBody.serializer())
// ?.msgTryupPttRsp
// ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey")
Highway.uploadPttToServers(
bot,
response.uploadIpList.zip(response.uploadPortList),
resource,
response.uKey,
response.fileKey,
)
Voice( Voice(
"${resource.md5.toUHexString("")}.amr", "${resource.md5.toUHexString("")}.amr",
resource.md5, resource.md5,

View File

@ -213,20 +213,26 @@ private suspend fun MessageChain.convertToLongMessageIfNeeded(
step: GroupMessageSendingStep, step: GroupMessageSendingStep,
groupImpl: GroupImpl, groupImpl: GroupImpl,
): MessageChain { ): MessageChain {
suspend fun sendLongImpl(): MessageChain {
val resId = groupImpl.uploadGroupLongMessageHighway(this)
return this + RichMessage.longMessage(
brief = takeContent(27),
resId = resId,
timeSeconds = currentTimeSeconds()
) // LongMessageInternal replaces all contents and preserves metadata
}
return when (step) { return when (step) {
GroupMessageSendingStep.FIRST -> { GroupMessageSendingStep.FIRST -> {
// 只需要在第一次发送的时候验证长度 // 只需要在第一次发送的时候验证长度
// 后续重试直接跳过 // 后续重试直接跳过
if (contains(ForceAsLongMessage)) {
sendLongImpl()
}
verityLength(this, groupImpl) verityLength(this, groupImpl)
this this
} }
GroupMessageSendingStep.LONG_MESSAGE -> { GroupMessageSendingStep.LONG_MESSAGE -> {
val resId = groupImpl.uploadGroupLongMessageHighway(this) sendLongImpl()
this + RichMessage.longMessage(
brief = takeContent(27),
resId = resId,
timeSeconds = currentTimeSeconds()
) // LongMessageInternal replaces all contents and preserves metadata
} }
GroupMessageSendingStep.FRAGMENTED -> this GroupMessageSendingStep.FRAGMENTED -> this
} }

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019-2021 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/master/LICENSE
*/
package net.mamoe.mirai.internal.message
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.safeCast
/**
* 内部 flag, 放入 chain 强制作为 long 发送
*/
internal object ForceAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage,
AbstractMessageKey<ForceAsLongMessage>({ it.safeCast() }) {
override val key: MessageKey<ForceAsLongMessage> get() = this
override fun toString(): String = "ForceLongMessage"
}
/**
* Ignore on transformation
*/
internal interface InternalFlagOnlyMessage : SingleMessage

View File

@ -243,6 +243,9 @@ internal fun MessageChain.toRichTextElems(
is RichMessage // already transformed above is RichMessage // already transformed above
-> { -> {
}
is InternalFlagOnlyMessage -> {
// ignore
} }
else -> error("unsupported message type: ${currentMessage::class.simpleName}") else -> error("unsupported message type: ${currentMessage::class.simpleName}")
} }

View File

@ -447,19 +447,26 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
this@QQAndroidBotNetworkHandler.launch(CoroutineName("Awaiting ConfigPushSvc.PushReq")) { this@QQAndroidBotNetworkHandler.launch(CoroutineName("Awaiting ConfigPushSvc.PushReq")) {
logger.info { "Awaiting ConfigPushSvc.PushReq." } logger.info { "Awaiting ConfigPushSvc.PushReq." }
when (val resp: ConfigPushSvc.PushReq.PushReqResponse? = nextEventOrNull(10_000)) { when (val resp: ConfigPushSvc.PushReq.PushReqResponse? = nextEventOrNull(10_000)) {
null -> logger.info { "Missing ConfigPushSvc.PushReq." } null -> {
kotlin.runCatching { bot.client.bdhSession.completeExceptionally(TimeoutCancellationException("Timeout waiting for ConfigPushSvc.PushReq")) }
logger.warning { "Missing ConfigPushSvc.PushReq. File uploading may be affected." }
}
is ConfigPushSvc.PushReq.PushReqResponse.Success -> { is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
logger.info { "ConfigPushSvc.PushReq: Success." } logger.info { "ConfigPushSvc.PushReq: Success." }
} }
is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> { is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
bot.logger.info { "Server requires reconnect." } bot.logger.info { "Server requires reconnect." }
logger.debug { "ChangeServer.unknown = ${resp.unknown}." }
bot.logger.info { "Server list: ${resp.serverList.joinToString()}." } bot.logger.info { "Server list: ${resp.serverList.joinToString()}." }
resp.serverList.forEach { if (resp.serverList.isNotEmpty()) {
bot.client.serverList.add(it.host to it.port) bot.serverList.clear()
resp.serverList.forEach {
bot.serverList.add(it.host to it.port)
}
} }
BotOfflineEvent.RequireReconnect(bot).broadcast()
bot.launch { BotOfflineEvent.RequireReconnect(bot).broadcast() }
return@launch
} }
} }
} }

View File

@ -14,21 +14,21 @@ package net.mamoe.mirai.internal.network
import kotlinx.atomicfu.AtomicBoolean import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.io.core.* import kotlinx.coroutines.CompletableDeferred
import kotlinx.io.core.String
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.data.OnlineStatus
import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.protocol.SyncingCacheList import net.mamoe.mirai.internal.network.protocol.SyncingCacheList
import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger
import net.mamoe.mirai.internal.network.protocol.packet.Tlv import net.mamoe.mirai.internal.network.protocol.packet.Tlv
import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.internal.utils.MiraiProtocolInternal
import net.mamoe.mirai.internal.utils.NetworkType
import net.mamoe.mirai.internal.utils.crypto.ECDH import net.mamoe.mirai.internal.utils.crypto.ECDH
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.NoServerAvailableException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.random.Random import kotlin.random.Random
internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress) internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress)
@ -82,36 +82,8 @@ internal open class QQAndroidClient(
get() = protocol.id get() = protocol.id
internal var strangerSeq: Int = 0 internal var strangerSeq: Int = 0
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
val keys: Map<String, ByteArray> by lazy { val keys: Map<String, ByteArray> by lazy { allKeys() }
mapOf(
"16 zero" to ByteArray(16),
"D2 key" to wLoginSigInfo.d2Key,
"wtSessionTicketKey" to wLoginSigInfo.wtSessionTicketKey,
"userStKey" to wLoginSigInfo.userStKey,
"tgtgtKey" to tgtgtKey,
"tgtKey" to wLoginSigInfo.tgtKey,
"deviceToken" to wLoginSigInfo.deviceToken,
"shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey
//"t108" to wLoginSigInfo.t1,
//"t10c" to t10c,
//"t163" to t163
)
}
internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? {
keys.forEach { (key, value) ->
kotlin.runCatching {
return mapper(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
}
}
return null
}
override fun toString(): String { // extremely slow
return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.toUHexString()}, randomKey=${randomKey.toUHexString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.toUHexString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.toUHexString()}, protocolVersion=$protocolVersion, apkId=${apkId.toUHexString()}, t150=${t150?.value?.toUHexString()}, rollbackSig=${rollbackSig?.toUHexString()}, ipFromT149=${ipFromT149?.toUHexString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.toUHexString()}, t528=${t528?.toUHexString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.toUHexString()}, qrPushSig=${qrPushSig.toUHexString()}, mainDisplayName='$mainDisplayName')"
}
var onlineStatus: OnlineStatus = OnlineStatus.ONLINE var onlineStatus: OnlineStatus = OnlineStatus.ONLINE
@ -126,33 +98,7 @@ internal open class QQAndroidClient(
private val _ssoSequenceId: AtomicInt = atomic(85600) private val _ssoSequenceId: AtomicInt = atomic(85600)
lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin var fileStoragePushFSSvcList: FileStoragePushFSSvcList? = null
internal suspend inline fun useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
if (bot.client.serverList.isEmpty()) {
bot.client.serverList.addAll(DefaultServerList)
}
retryCatchingExceptions(bot.client.serverList.size, except = LoginFailedException::class) l@{
val pair = bot.client.serverList[0]
runCatchingExceptions {
block(pair.first, pair.second)
return@l
}.getOrElse {
bot.client.serverList.remove(pair)
if (it !is LoginFailedException) {
// 不要重复打印.
bot.logger.warning(it)
}
throw it
}
}.getOrElse {
if (it is LoginFailedException) {
throw it
}
bot.client.serverList.addAll(DefaultServerList)
throw NoServerAvailableException(it)
}
}
@MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket") @MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket")
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2) internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
@ -318,7 +264,6 @@ internal open class QQAndroidClient(
lateinit var wFastLoginInfo: WFastLoginInfo lateinit var wFastLoginInfo: WFastLoginInfo
var reserveUinInfo: ReserveUinInfo? = null var reserveUinInfo: ReserveUinInfo? = null
lateinit var wLoginSigInfo: WLoginSigInfo lateinit var wLoginSigInfo: WLoginSigInfo
var tlv113: ByteArray? = null
/** /**
* from tlvMap119 * from tlvMap119
@ -331,193 +276,17 @@ internal open class QQAndroidClient(
var transportSequenceId = 1 var transportSequenceId = 1
lateinit var t104: ByteArray lateinit var t104: ByteArray
}
@Suppress("RemoveRedundantQualifierName") // bug
internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
(getRandomByteArray(16) + guid).md5()
internal class ReserveUinInfo(
val imgType: ByteArray,
val imgFormat: ByteArray,
val imgUrl: ByteArray
) {
override fun toString(): String {
return "ReserveUinInfo(imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()})"
}
}
internal class WFastLoginInfo(
val outA1: ByteReadPacket,
var adUrl: String = "",
var iconUrl: String = "",
var profileUrl: String = "",
var userJson: String = ""
) {
override fun toString(): String {
return "WFastLoginInfo(outA1=$outA1, adUrl='$adUrl', iconUrl='$iconUrl', profileUrl='$profileUrl', userJson='$userJson')"
}
}
internal class WLoginSimpleInfo(
val uin: Long, // uin
val face: Int, // ubyte actually
val age: Int, // ubyte
val gender: Int, // ubyte
val nick: String, // ubyte lv string
val imgType: ByteArray,
val imgFormat: ByteArray,
val imgUrl: ByteArray,
val mainDisplayName: ByteArray
) {
override fun toString(): String {
return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()}, mainDisplayName=${mainDisplayName.toUHexString()})"
}
}
internal class LoginExtraData(
val uin: Long,
val ip: ByteArray,
val time: Int,
val version: Int
) {
override fun toString(): String {
return "LoginExtraData(uin=$uin, ip=${ip.toUHexString()}, time=$time, version=$version)"
}
}
internal class WLoginSigInfo(
val uin: Long,
val encryptA1: ByteArray?, // sigInfo[0]
/** /**
* WARNING, please check [QQAndroidClient.tlv16a] * from ConfigPush.PushReq
*/ */
val noPicSig: ByteArray?, // sigInfo[1] @JvmField
val G: ByteArray, // sigInfo[2] val bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
val dpwd: ByteArray,
val randSeed: ByteArray,
val simpleInfo: WLoginSimpleInfo,
val appPri: Long,
val a2ExpiryTime: Long,
val loginBitmap: Long,
val tgt: ByteArray,
val a2CreationTime: Long,
val tgtKey: ByteArray,
val userStSig: UserStSig,
/**
* TransEmpPacket 加密使用
*/
val userStKey: ByteArray,
val userStWebSig: UserStWebSig,
val userA5: UserA5,
val userA8: UserA8,
val lsKey: LSKey,
val sKey: SKey,
val userSig64: UserSig64,
val openId: ByteArray,
val openKey: OpenKey,
val vKey: VKey,
val accessToken: AccessToken,
val d2: D2,
val d2Key: ByteArray,
val sid: Sid,
val aqSig: AqSig,
val psKeyMap: PSKeyMap,
val pt4TokenMap: Pt4TokenMap,
val superKey: ByteArray,
val payToken: ByteArray,
val pf: ByteArray,
val pfKey: ByteArray,
val da2: ByteArray,
// val pt4Token: ByteArray,
val wtSessionTicket: WtSessionTicket,
val wtSessionTicketKey: ByteArray,
val deviceToken: ByteArray
) {
override fun toString(): String {
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.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()})"
}
} }
internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class BdhSession(
internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : val sigSession: ByteArray,
KeyWithExpiry(data, creationTime, expireTime) val sessionKey: ByteArray,
var ssoAddresses: MutableSet<Pair<Int, Int>> = CopyOnWriteArraySet(),
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : var otherAddresses: MutableSet<Pair<Int, Int>> = CopyOnWriteArraySet(),
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)
internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray())
internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal fun parsePSKeyMapAndPt4TokenMap(
data: ByteArray,
creationTime: Long,
expireTime: Long,
outPSKeyMap: PSKeyMap,
outPt4TokenMap: Pt4TokenMap
) =
data.read {
repeat(readShort().toInt()) {
val domain = readUShortLVString()
val psKey = readUShortLVByteArray()
val pt4token = readUShortLVByteArray()
when {
psKey.isNotEmpty() -> outPSKeyMap[domain] = PSKey(psKey, creationTime, expireTime)
pt4token.isNotEmpty() -> outPt4TokenMap[domain] = Pt4Token(pt4token, creationTime, expireTime)
}
}
}
internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal open class KeyWithExpiry(
data: ByteArray,
creationTime: Long,
val expireTime: Long
) : KeyWithCreationTime(data, creationTime) {
override fun toString(): String {
return "KeyWithExpiry(data=${data.toUHexString()}, creationTime=$creationTime)"
}
}
internal open class KeyWithCreationTime(
val data: ByteArray,
val creationTime: Long
) {
override fun toString(): String {
return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)"
}
}

View File

@ -9,10 +9,16 @@
package net.mamoe.mirai.internal.network.highway package net.mamoe.mirai.internal.network.highway
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.runBIO import net.mamoe.mirai.utils.runBIO
import net.mamoe.mirai.utils.toLongUnsigned
import net.mamoe.mirai.utils.withUse import net.mamoe.mirai.utils.withUse
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.atomic.AtomicLong
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
internal class ChunkedFlowSession<T>( internal class ChunkedFlowSession<T>(
private val input: InputStream, private val input: InputStream,
@ -23,14 +29,18 @@ internal class ChunkedFlowSession<T>(
input.close() input.close()
} }
private var offset = 0L private var offset = AtomicLong(0L)
internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) = withUse { internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) {
while (true) { contract { callsInPlace(block, InvocationKind.UNKNOWN) }
val size = runBIO { input.read(buffer) } withUse {
if (size == -1) return while (true) {
block(mapper(buffer, size, offset)) val size = runBIO { input.read(buffer) }
offset += size if (size == -1) return
block(mapper(buffer, size, offset.getAndAdd(size.toLongUnsigned())))
}
} }
} }
internal suspend fun asFlow(): Flow<T> = flow { useAll { emit(it) } } // 'single thread' producer
} }

View File

@ -9,207 +9,224 @@
package net.mamoe.mirai.internal.network.highway package net.mamoe.mirai.internal.network.highway
import io.ktor.client.* import kotlinx.coroutines.*
import io.ktor.client.request.* import kotlinx.coroutines.channels.Channel
import io.ktor.http.* import kotlinx.coroutines.channels.ReceiveChannel
import io.ktor.http.content.* import kotlinx.coroutines.channels.receiveOrNull
import io.ktor.utils.io.* import kotlinx.coroutines.flow.Flow
import io.ktor.utils.io.jvm.javaio.* import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.delay import kotlinx.coroutines.flow.produceIn
import kotlinx.coroutines.isActive import kotlinx.io.core.ByteReadPacket
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.io.core.buildPacket
import kotlinx.io.core.* import kotlinx.io.core.discardExact
import net.mamoe.mirai.Mirai import kotlinx.io.core.writeFully
import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.internal.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
import net.mamoe.mirai.internal.utils.PlatformSocket import net.mamoe.mirai.internal.utils.PlatformSocket
import net.mamoe.mirai.internal.utils.SocketException import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.internal.utils.toIpV4AddressString import net.mamoe.mirai.internal.utils.retryWithServers
import net.mamoe.mirai.internal.utils.sizeToString
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import java.io.InputStream import java.util.concurrent.atomic.AtomicReference
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.time.measureTime import kotlin.time.measureTime
/**
* 在发送完成后将会 [InputStream.close]
*/
internal fun ExternalResource.consumeAsWriteChannelContent(contentType: ContentType?): OutgoingContent.WriteChannelContent {
return object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType? = contentType
override val contentLength: Long = size
override suspend fun writeTo(channel: ByteWriteChannel) {
inputStream().withUse { copyTo(channel) }
}
}
}
@Suppress("SpellCheckingInspection")
internal suspend fun HttpClient.postImage(
htcmd: String,
uin: Long,
groupcode: Long?,
imageInput: ExternalResource,
uKeyHex: String
): Boolean = post<HttpStatusCode> {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toString()
if (groupcode != null) parameters["groupcode"] = groupcode.toString()
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filesize"] = imageInput.size.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
userAgent("QQClient")
}
body = imageInput.consumeAsWriteChannelContent(ContentType.Image.Any)
} == HttpStatusCode.OK
internal object Highway { internal object Highway {
@Suppress("ArrayInDataClass")
data class BdhUploadResponse(
var extendInfo: ByteArray? = null,
)
suspend fun uploadResource( suspend fun uploadResourceBdh(
bot: QQAndroidBot, bot: QQAndroidBot,
servers: List<Pair<Int, Int>>,
uKey: ByteArray,
resource: ExternalResource, resource: ExternalResource,
kind: String, kind: ResourceKind,
commandId: Int commandId: Int, // group image=2, friend image=1, groupPtt=29
) = servers.retryWithServers( extendInfo: ByteArray = EMPTY_BYTE_ARRAY,
(resource.size * 1000 / 1024 / 10).coerceAtLeast(5000), encrypt: Boolean = false,
onFail = { initialTicket: ByteArray? = null,
throw IllegalStateException("cannot upload $kind, failed on all servers.", it) ): BdhUploadResponse {
} val bdhSession = bot.client.bdhSession.await() // no need to care about timeout. proceed by bot init
) { ip, port ->
bot.network.logger.verbose {
"[Highway] Uploading $kind to ${ip}:$port, size=${resource.size.sizeToString()}"
}
val time = measureTime { return tryServers(bot, bdhSession.ssoAddresses, resource.size, kind, ChannelKind.HIGHWAY) { ip, port ->
uploadResourceImpl( val md5 = resource.md5
require(md5.size == 16) { "bad md5. Required size=16, got ${md5.size}" }
val resp = BdhUploadResponse()
highwayPacketSession(
client = bot.client, client = bot.client,
serverIp = ip, appId = bot.client.subAppId.toInt(),
serverPort = port,
resource = resource,
fileMd5 = resource.md5,
ticket = uKey,
commandId = commandId
)
}
bot.network.logger.verbose {
"[Highway] Uploading $kind: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
}
}
private suspend fun uploadResourceImpl(
client: QQAndroidClient,
serverIp: String,
serverPort: Int,
ticket: ByteArray,
resource: ExternalResource,
fileMd5: ByteArray,
commandId: Int // group=2, friend=1
) {
require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
// require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
val socket = PlatformSocket()
while (client.bot.network.areYouOk() && client.bot.isActive) {
try {
socket.connect(serverIp, serverPort)
break
} catch (e: SocketException) {
delay(3000)
}
}
socket.use {
createImageDataPacketSequence(
client = client,
appId = client.subAppId.toInt(),
command = "PicUp.DataUp", command = "PicUp.DataUp",
commandId = commandId, commandId = commandId,
ticket = ticket, initialTicket = initialTicket ?: bdhSession.sigSession,
data = resource, data = resource,
fileMd5 = fileMd5 fileMd5 = md5,
).useAll { extendInfo = if (encrypt) TEA.encrypt(extendInfo, bdhSession.sessionKey) else extendInfo
socket.send(it) ).sendConcurrently(
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 createConnection = { PlatformSocket.connect(ip, port) },
coroutines = bot.configuration.highwayUploadCoroutineCount
socket.read().withUse { ) { head ->
discardExact(1) if (head.rspExtendinfo.isNotEmpty()) {
val headLength = readInt() resp.extendInfo = head.rspExtendinfo
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
} }
} }
} resp
}
suspend fun uploadPttToServers(
bot: QQAndroidBot,
servers: List<Pair<Int, Int>>,
resource: ExternalResource,
uKey: ByteArray,
fileKey: ByteArray,
) {
servers.retryWithServers(10 * 1000, {
throw IllegalStateException("cannot upload ptt, failed on all servers.", it)
}, { s: String, i: Int ->
bot.network.logger.verbose {
"[Highway] Uploading ptt to ${s}:$i, size=${resource.size.sizeToString()}"
}
val time = measureTime {
uploadPttToServer(s, i, resource, uKey, fileKey)
}
bot.network.logger.verbose {
"[Highway] Uploading ptt: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
}
})
}
private suspend fun uploadPttToServer(
serverIp: String,
serverPort: Int,
resource: ExternalResource,
uKey: ByteArray,
fileKey: ByteArray,
) {
Mirai.Http.post<String> {
url("http://$serverIp:$serverPort")
parameter("ver", 4679)
parameter("ukey", uKey.toUHexString(""))
parameter("filekey", fileKey.toUHexString(""))
parameter("filesize", resource.size)
parameter("bmd5", resource.md5.toUHexString(""))
parameter("mType", "pttDu")
parameter("voice_encodec", resource.voiceCodec)
body = resource.consumeAsWriteChannelContent(null)
} }
} }
} }
internal enum class ResourceKind(
private val display: String
) {
PRIVATE_IMAGE("private image"),
GROUP_IMAGE("group image"),
PRIVATE_VOICE("private voice"),
GROUP_VOICE("group voice"),
internal fun createImageDataPacketSequence( GROUP_LONG_MESSAGE("group long message"),
GROUP_FORWARD_MESSAGE("group forward message"),
;
override fun toString(): String = display
}
internal enum class ChannelKind(
private val display: String
) {
HIGHWAY("Highway"),
HTTP("Http")
;
override fun toString(): String = display
}
internal suspend inline fun <reified R> tryServers(
bot: QQAndroidBot,
servers: Collection<Pair<Int, Int>>,
resourceSize: Long,
resourceKind: ResourceKind,
channelKind: ChannelKind,
crossinline implOnEachServer: suspend (ip: String, port: Int) -> R
) = servers.retryWithServers(
(resourceSize * 1000 / 1024 / 10).coerceAtLeast(5000),
onFail = { throw IllegalStateException("cannot upload $resourceKind, failed on all servers.", it) }
) { ip, port ->
bot.network.logger.verbose {
"[${channelKind}] Uploading $resourceKind to ${ip}:$port, size=${resourceSize.sizeToString()}"
}
var resp: R? = null
val time = measureTime {
runCatching {
resp = implOnEachServer(ip, port)
}.onFailure {
bot.network.logger.verbose {
"[${channelKind}] Uploading $resourceKind to ${ip}:$port, size=${resourceSize.sizeToString()} failed: $it"
}
throw it
}
}
bot.network.logger.verbose {
"[${channelKind}] Uploading $resourceKind: succeed at ${(resourceSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
}
resp as R
}
internal suspend fun ChunkedFlowSession<ByteReadPacket>.sendSequentially(
socket: PlatformSocket,
respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {}
) {
contract { callsInPlace(respCallback, InvocationKind.UNKNOWN) }
useAll {
socket.send(it)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse {
discardExact(1)
val headLength = readInt()
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
respCallback(proto)
}
}
}
private fun <T> Flow<T>.produceIn0(coroutineScope: CoroutineScope): ReceiveChannel<T> {
return kotlin.runCatching {
@OptIn(FlowPreview::class)
produceIn(coroutineScope) // this is experimental api
}.getOrElse {
// fallback strategy in case binary changes.
val channel = Channel<T>()
coroutineScope.launch(CoroutineName("Flow collector")) {
collect {
channel.send(it)
}
channel.close()
}
channel
}
}
internal suspend fun ChunkedFlowSession<ByteReadPacket>.sendConcurrently(
createConnection: suspend () -> PlatformSocket,
coroutines: Int = 5,
respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {}
) = coroutineScope {
val channel = asFlow().produceIn0(this)
// 'single thread' producer emits chunks to channel
repeat(coroutines) {
launch(CoroutineName("Worker $it")) {
val socket = createConnection()
while (isActive) {
val next = channel.tryReceive() ?: break // concurrent-safe receive
val result = next.withUse {
socket.sendReceiveHighway(next)
}
respCallback(result)
}
}
}
}
private suspend fun <E : Any> ReceiveChannel<E>.tryReceive(): E? {
return kotlin.runCatching {
@OptIn(ExperimentalCoroutinesApi::class)
receiveOrNull() // this is experimental api
}.recoverCatching {
// in case binary changes
receive()
}.getOrNull()
}
private suspend fun PlatformSocket.sendReceiveHighway(
it: ByteReadPacket,
): CSDataHighwayHead.RspDataHighwayHead {
send(it)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
read().withUse {
discardExact(1)
val headLength = readInt()
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
return proto
}
}
internal fun highwayPacketSession(
// RequestDataTrans // RequestDataTrans
client: QQAndroidClient, client: QQAndroidClient,
command: String, command: String,
@ -217,14 +234,17 @@ internal fun createImageDataPacketSequence(
dataFlag: Int = 4096, dataFlag: Int = 4096,
commandId: Int, commandId: Int,
localId: Int = 2052, localId: Int = 2052,
ticket: ByteArray, initialTicket: ByteArray,
data: ExternalResource, data: ExternalResource,
fileMd5: ByteArray, fileMd5: ByteArray,
sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE,
extendInfo: ByteArray = EMPTY_BYTE_ARRAY,
): ChunkedFlowSession<ByteReadPacket> { ): ChunkedFlowSession<ByteReadPacket> {
ByteArrayPool.checkBufferSize(sizePerPacket) ByteArrayPool.checkBufferSize(sizePerPacket)
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
val ticket = AtomicReference(initialTicket)
return ChunkedFlowSession(data.inputStream(), ByteArray(sizePerPacket)) { buffer, size, offset -> return ChunkedFlowSession(data.inputStream(), ByteArray(sizePerPacket)) { buffer, size, offset ->
val head = CSDataHighwayHead.ReqDataHighwayHead( val head = CSDataHighwayHead.ReqDataHighwayHead(
msgBasehead = CSDataHighwayHead.DataHighwayHead( msgBasehead = CSDataHighwayHead.DataHighwayHead(
@ -235,6 +255,7 @@ internal fun createImageDataPacketSequence(
2 -> client.nextHighwayDataTransSequenceIdForGroup() 2 -> client.nextHighwayDataTransSequenceIdForGroup()
1 -> client.nextHighwayDataTransSequenceIdForFriend() 1 -> client.nextHighwayDataTransSequenceIdForFriend()
27 -> client.nextHighwayDataTransSequenceIdForApplyUp() 27 -> client.nextHighwayDataTransSequenceIdForApplyUp()
29 -> client.nextHighwayDataTransSequenceIdForGroup()
else -> error("illegal commandId: $commandId") else -> error("illegal commandId: $commandId")
}, },
retryTimes = 0, retryTimes = 0,
@ -248,13 +269,13 @@ internal fun createImageDataPacketSequence(
datalength = size, datalength = size,
dataoffset = offset, dataoffset = offset,
filesize = data.size, filesize = data.size,
serviceticket = ticket, serviceticket = ticket.get(),
md5 = buffer.md5(0, size), md5 = buffer.md5(0, size),
fileMd5 = fileMd5, fileMd5 = fileMd5,
flag = 0, flag = 0,
rtcode = 0 rtcode = 0
), ),
reqExtendinfo = EMPTY_BYTE_ARRAY, reqExtendinfo = extendInfo,
msgLoginSigHead = null msgLoginSigHead = null
).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer()) ).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer())
@ -268,35 +289,3 @@ internal fun createImageDataPacketSequence(
} }
} }
} }
internal suspend inline fun List<Pair<Int, Int>>.retryWithServers(
timeoutMillis: Long,
onFail: (exception: Throwable?) -> Unit,
crossinline block: suspend (ip: String, port: Int) -> Unit
) {
require(this.isNotEmpty()) { "receiver of retryWithServers must not be empty" }
var exception: Throwable? = null
for (pair in this) {
return kotlin.runCatching {
withTimeoutOrNull(timeoutMillis) {
block(pair.first.toIpV4AddressString(), pair.second)
}
}.recover {
if (exception != null) {
exception!!.addSuppressed(it)
}
exception = it
null
}.getOrNull() ?: continue
}
onFail(exception)
}
internal fun Int.sizeToString() = this.toLong().sizeToString()
internal fun Long.sizeToString(): String {
return if (this < 1024) {
"$this B"
} else ((this * 100.0 / 1024).roundToInt() / 100.0).toString() + " KiB"
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.internal.network.highway
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.utils.io.*
import io.ktor.utils.io.jvm.javaio.*
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.toUHexString
import net.mamoe.mirai.utils.withUse
import java.io.InputStream
/**
* 在发送完成后将会 [InputStream.close]
*/
internal fun ExternalResource.consumeAsWriteChannelContent(contentType: ContentType?): OutgoingContent.WriteChannelContent {
return object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType? = contentType
override val contentLength: Long = size
override suspend fun writeTo(channel: ByteWriteChannel) {
inputStream().withUse { copyTo(channel) }
}
}
}
internal val FALLBACK_HTTP_SERVER = "htdata2.qq.com" to 0
@Suppress("SpellCheckingInspection")
internal suspend fun HttpClient.postImage(
serverIp: String,
serverPort: Int = DEFAULT_PORT,
htcmd: String,
uin: Long,
groupcode: Long?,
imageInput: ExternalResource,
uKeyHex: String
): Boolean = post<HttpStatusCode> {
url {
protocol = URLProtocol.HTTP
host = serverIp // "htdata2.qq.com"
port = serverPort
path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toString()
if (groupcode != null) parameters["groupcode"] = groupcode.toString()
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filesize"] = imageInput.size.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
userAgent("QQClient")
}
body = imageInput.consumeAsWriteChannelContent(ContentType.Image.Any)
} == HttpStatusCode.OK
internal suspend fun HttpClient.postPtt(
serverIp: String,
serverPort: Int,
resource: ExternalResource,
uKey: ByteArray,
fileKey: ByteArray,
) {
post<String> {
url("http://$serverIp:$serverPort")
parameter("ver", 4679)
parameter("ukey", uKey.toUHexString(""))
parameter("filekey", fileKey.toUHexString(""))
parameter("filesize", resource.size)
parameter("bmd5", resource.md5.toUHexString(""))
parameter("mType", "pttDu")
parameter("voice_encodec", resource.voiceCodec)
body = resource.consumeAsWriteChannelContent(null)
}
}

View File

@ -0,0 +1,264 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.internal.network
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUShort
import net.mamoe.mirai.internal.network.getRandomByteArray
import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.NoServerAvailableException
import net.mamoe.mirai.utils.*
internal class ReserveUinInfo(
val imgType: ByteArray,
val imgFormat: ByteArray,
val imgUrl: ByteArray
) {
override fun toString(): String {
return "ReserveUinInfo(imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()})"
}
}
internal class WFastLoginInfo(
val outA1: ByteReadPacket,
var adUrl: String = "",
var iconUrl: String = "",
var profileUrl: String = "",
var userJson: String = ""
) {
override fun toString(): String {
return "WFastLoginInfo(outA1=$outA1, adUrl='$adUrl', iconUrl='$iconUrl', profileUrl='$profileUrl', userJson='$userJson')"
}
}
internal class WLoginSimpleInfo(
val uin: Long, // uin
val face: Int, // ubyte actually
val age: Int, // ubyte
val gender: Int, // ubyte
val nick: String, // ubyte lv string
val imgType: ByteArray,
val imgFormat: ByteArray,
val imgUrl: ByteArray,
val mainDisplayName: ByteArray
) {
override fun toString(): String {
return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()}, mainDisplayName=${mainDisplayName.toUHexString()})"
}
}
internal class LoginExtraData(
val uin: Long,
val ip: ByteArray,
val time: Int,
val version: Int
) {
override fun toString(): String {
return "LoginExtraData(uin=$uin, ip=${ip.toUHexString()}, time=$time, version=$version)"
}
}
internal class WLoginSigInfo(
val uin: Long,
val encryptA1: ByteArray?, // sigInfo[0]
/**
* WARNING, please check [QQAndroidClient.tlv16a]
*/
val noPicSig: ByteArray?, // sigInfo[1]
val G: ByteArray, // sigInfo[2]
val dpwd: ByteArray,
val randSeed: ByteArray,
val simpleInfo: WLoginSimpleInfo,
val appPri: Long,
val a2ExpiryTime: Long,
val loginBitmap: Long,
val tgt: ByteArray,
val a2CreationTime: Long,
val tgtKey: ByteArray,
val userStSig: UserStSig,
/**
* TransEmpPacket 加密使用
*/
val userStKey: ByteArray,
val userStWebSig: UserStWebSig,
val userA5: UserA5,
val userA8: UserA8,
val lsKey: LSKey,
val sKey: SKey,
val userSig64: UserSig64,
val openId: ByteArray,
val openKey: OpenKey,
val vKey: VKey,
val accessToken: AccessToken,
val d2: D2,
val d2Key: ByteArray,
val sid: Sid,
val aqSig: AqSig,
val psKeyMap: PSKeyMap,
val pt4TokenMap: Pt4TokenMap,
val superKey: ByteArray,
val payToken: ByteArray,
val pf: ByteArray,
val pfKey: ByteArray,
val da2: ByteArray,
// val pt4Token: ByteArray,
val wtSessionTicket: WtSessionTicket,
val wtSessionTicketKey: ByteArray,
val deviceToken: ByteArray
) {
override fun toString(): String {
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.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()})"
}
}
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)
internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
internal fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray())
internal fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal fun parsePSKeyMapAndPt4TokenMap(
data: ByteArray,
creationTime: Long,
expireTime: Long,
outPSKeyMap: PSKeyMap,
outPt4TokenMap: Pt4TokenMap
) =
data.read {
repeat(readShort().toInt()) {
val domain = readUShortLVString()
val psKey = readUShortLVByteArray()
val pt4token = readUShortLVByteArray()
when {
psKey.isNotEmpty() -> outPSKeyMap[domain] = PSKey(psKey, creationTime, expireTime)
pt4token.isNotEmpty() -> outPt4TokenMap[domain] = Pt4Token(pt4token, creationTime, expireTime)
}
}
}
internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal open class KeyWithExpiry(
data: ByteArray,
creationTime: Long,
val expireTime: Long
) : KeyWithCreationTime(data, creationTime) {
override fun toString(): String {
return "KeyWithExpiry(data=${data.toUHexString()}, creationTime=$creationTime)"
}
}
internal open class KeyWithCreationTime(
val data: ByteArray,
val creationTime: Long
) {
override fun toString(): String {
return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)"
}
}
internal suspend inline fun QQAndroidClient.useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
if (bot.serverList.isEmpty()) {
bot.serverList.addAll(DefaultServerList)
}
retryCatchingExceptions(bot.serverList.size, except = LoginFailedException::class) l@{
val pair = bot.serverList[0]
runCatchingExceptions {
block(pair.first, pair.second)
return@l
}.getOrElse {
bot.serverList.remove(pair)
if (it !is LoginFailedException) {
// 不要重复打印.
bot.logger.warning(it)
}
throw it
}
}.getOrElse {
if (it is LoginFailedException) {
throw it
}
bot.serverList.addAll(DefaultServerList)
throw NoServerAvailableException(it)
}
}
@Suppress("RemoveRedundantQualifierName") // bug
internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
(getRandomByteArray(16) + guid).md5()
internal inline fun <R> QQAndroidClient.tryDecryptOrNull(
data: ByteArray,
size: Int = data.size,
mapper: (ByteArray) -> R
): R? {
keys.forEach { (key, value) ->
kotlin.runCatching {
return mapper(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
}
}
return null
}
internal fun QQAndroidClient.allKeys() = mapOf(
"16 zero" to ByteArray(16),
"D2 key" to wLoginSigInfo.d2Key,
"wtSessionTicketKey" to wLoginSigInfo.wtSessionTicketKey,
"userStKey" to wLoginSigInfo.userStKey,
"tgtgtKey" to tgtgtKey,
"tgtKey" to wLoginSigInfo.tgtKey,
"deviceToken" to wLoginSigInfo.deviceToken,
"shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey
//"t108" to wLoginSigInfo.t1,
//"t10c" to t10c,
//"t163" to t163
)

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -100,19 +100,23 @@ internal class _339(
@TarsId(9) @JvmField val field1306: String = "" @TarsId(9) @JvmField val field1306: String = ""
) : JceStruct ) : JceStruct
/**
* v8.5.5
*/
@Serializable @Serializable
internal class FileStoragePushFSSvcListFuckKotlin( internal class FileStoragePushFSSvcList(
@TarsId(0) @JvmField val vUpLoadList: List<FileStorageServerListInfo>? = listOf(), @TarsId(0) @JvmField val vUpLoadList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo>? = listOf(), @TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo>? = null, @TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null, @TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null, @TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null, @TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null,
@TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo>? = null, @TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo>? = null, @TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo> = emptyList(),
@TarsId(8) @JvmField val fmtIPInfo: FmtIPInfo? = null, @TarsId(8) @JvmField val fmtIPInfo: FmtIPInfo? = null,
@TarsId(9) @JvmField val domainIpChannel: DomainIpChannel? = null, @TarsId(9) @JvmField val domainIpChannel: DomainIpChannel? = null,
@TarsId(10) @JvmField val pttlist: ByteArray? = null @TarsId(10) @JvmField val pttlist: ByteArray? = null,
) : JceStruct ) : JceStruct
@Serializable @Serializable

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused", "SpellCheckingInspection")
package net.mamoe.mirai.internal.network.protocol.data.proto package net.mamoe.mirai.internal.network.protocol.data.proto
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -16,94 +18,99 @@ import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.ProtoBuf
/**
* v8.5.5
*/
@Serializable @Serializable
internal class BdhExtinfo : ProtoBuf { internal class BdhExtinfo : ProtoBuf {
@Serializable @Serializable
internal class CommFileExtReq( internal class CommFileExtReq(
@ProtoNumber(1) @JvmField val actionType: Int = 0, @JvmField @ProtoNumber(1) val actionType: Int = 0,
@ProtoNumber(2) @JvmField val uuid: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class CommFileExtRsp( internal class CommFileExtRsp(
@ProtoNumber(1) @JvmField val int32Retcode: Int = 0, @JvmField @ProtoNumber(1) val int32Retcode: Int = 0,
@ProtoNumber(2) @JvmField val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class PicInfo( internal class PicInfo(
@ProtoNumber(1) @JvmField val idx: Int = 0, @JvmField @ProtoNumber(1) val idx: Int = 0,
@ProtoNumber(2) @JvmField val size: Int = 0, @JvmField @ProtoNumber(2) val size: Int = 0,
@ProtoNumber(3) @JvmField val binMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(4) @JvmField val type: Int = 0 @JvmField @ProtoNumber(4) val type: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class QQVoiceExtReq( internal class QQVoiceExtReq(
@ProtoNumber(1) @JvmField val qid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val fmt: Int = 0, @JvmField @ProtoNumber(2) val fmt: Int = 0,
@ProtoNumber(3) @JvmField val rate: Int = 0, @JvmField @ProtoNumber(3) val rate: Int = 0,
@ProtoNumber(4) @JvmField val bits: Int = 0, @JvmField @ProtoNumber(4) val bits: Int = 0,
@ProtoNumber(5) @JvmField val channel: Int = 0, @JvmField @ProtoNumber(5) val channel: Int = 0,
@ProtoNumber(6) @JvmField val pinyin: Int = 0 @JvmField @ProtoNumber(6) val pinyin: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class QQVoiceExtRsp( internal class QQVoiceExtRsp(
@ProtoNumber(1) @JvmField val qid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val int32Retcode: Int = 0, @JvmField @ProtoNumber(2) val int32Retcode: Int = 0,
@ProtoNumber(3) @JvmField val msgResult: List<QQVoiceResult> = emptyList() @JvmField @ProtoNumber(3) val msgResult: List<QQVoiceResult> = emptyList()
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class QQVoiceResult( internal class QQVoiceResult(
@ProtoNumber(1) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val text: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val pinyin: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val source: Int = 0 @JvmField @ProtoNumber(3) val source: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ShortVideoReqExtInfo( internal class ShortVideoReqExtInfo(
@ProtoNumber(1) @JvmField val cmd: Int = 0, @JvmField @ProtoNumber(1) val cmd: Int = 0,
@ProtoNumber(2) @JvmField val sessionId: Long = 0L, @JvmField @ProtoNumber(2) val sessionId: Long = 0L,
@ProtoNumber(3) @JvmField val msgThumbinfo: PicInfo? = null, @JvmField @ProtoNumber(3) val msgThumbinfo: PicInfo? = null,
@ProtoNumber(4) @JvmField val msgVideoinfo: VideoInfo? = null, @JvmField @ProtoNumber(4) val msgVideoinfo: VideoInfo? = null,
@ProtoNumber(5) @JvmField val msgShortvideoSureReq: ShortVideoSureReqInfo? = null, @JvmField @ProtoNumber(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null,
@ProtoNumber(6) @JvmField val boolIsMergeCmdBeforeData: Boolean = false @JvmField @ProtoNumber(6) val boolIsMergeCmdBeforeData: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ShortVideoRspExtInfo( internal class ShortVideoRspExtInfo(
@ProtoNumber(1) @JvmField val cmd: Int = 0, @JvmField @ProtoNumber(1) val cmd: Int = 0,
@ProtoNumber(2) @JvmField val sessionId: Long = 0L, @JvmField @ProtoNumber(2) val sessionId: Long = 0L,
@ProtoNumber(3) @JvmField val int32Retcode: Int = 0, @JvmField @ProtoNumber(3) val int32Retcode: Int = 0,
@ProtoNumber(4) @JvmField val errinfo: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(5) @JvmField val msgThumbinfo: PicInfo? = null, @JvmField @ProtoNumber(5) val msgThumbinfo: PicInfo? = null,
@ProtoNumber(6) @JvmField val msgVideoinfo: VideoInfo? = null, @JvmField @ProtoNumber(6) val msgVideoinfo: VideoInfo? = null,
@ProtoNumber(7) @JvmField val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null, @JvmField @ProtoNumber(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null,
@ProtoNumber(8) @JvmField val retryFlag: Int = 0 @JvmField @ProtoNumber(8) val retryFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ShortVideoSureReqInfo( internal class ShortVideoSureReqInfo(
@ProtoNumber(1) @JvmField val fromuin: Long = 0L, @JvmField @ProtoNumber(1) val fromuin: Long = 0L,
@ProtoNumber(2) @JvmField val chatType: Int = 0, @JvmField @ProtoNumber(2) val chatType: Int = 0,
@ProtoNumber(3) @JvmField val touin: Long = 0L, @JvmField @ProtoNumber(3) val touin: Long = 0L,
@ProtoNumber(4) @JvmField val groupCode: Long = 0L, @JvmField @ProtoNumber(4) val groupCode: Long = 0L,
@ProtoNumber(5) @JvmField val clientType: Int = 0, @JvmField @ProtoNumber(5) val clientType: Int = 0,
@ProtoNumber(6) @JvmField val msgThumbinfo: PicInfo? = null, @JvmField @ProtoNumber(6) val msgThumbinfo: PicInfo? = null,
@ProtoNumber(7) @JvmField val msgMergeVideoinfo: List<VideoInfo> = emptyList(), @JvmField @ProtoNumber(7) val msgMergeVideoinfo: List<VideoInfo> = emptyList(),
@ProtoNumber(8) @JvmField val msgDropVideoinfo: List<VideoInfo> = emptyList(), @JvmField @ProtoNumber(8) val msgDropVideoinfo: List<VideoInfo> = emptyList(),
@ProtoNumber(9) @JvmField val businessType: Int = 0, @JvmField @ProtoNumber(9) val businessType: Int = 0,
@ProtoNumber(10) @JvmField val subBusinessType: Int = 0 @JvmField @ProtoNumber(10) val subBusinessType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ShortVideoSureRspInfo( internal class ShortVideoSureRspInfo(
@ProtoNumber(1) @JvmField val fileid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val ukey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val msgVideoinfo: VideoInfo? = null, @JvmField @ProtoNumber(3) val msgVideoinfo: VideoInfo? = null,
@ProtoNumber(4) @JvmField val mergeCost: Int = 0 @JvmField @ProtoNumber(4) val mergeCost: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -111,31 +118,31 @@ internal class BdhExtinfo : ProtoBuf {
@Serializable @Serializable
internal class StoryVideoExtRsp( internal class StoryVideoExtRsp(
@ProtoNumber(1) @JvmField val int32Retcode: Int = 0, @JvmField @ProtoNumber(1) val int32Retcode: Int = 0,
@ProtoNumber(2) @JvmField val msg: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val msg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(4) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(5) @JvmField val fileId: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class UploadPicExtInfo( internal class UploadPicExtInfo(
@ProtoNumber(1) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class VideoInfo( internal class VideoInfo(
@ProtoNumber(1) @JvmField val idx: Int = 0, @JvmField @ProtoNumber(1) val idx: Int = 0,
@ProtoNumber(2) @JvmField val size: Int = 0, @JvmField @ProtoNumber(2) val size: Int = 0,
@ProtoNumber(3) @JvmField val binMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(4) @JvmField val format: Int = 0, @JvmField @ProtoNumber(4) val format: Int = 0,
@ProtoNumber(5) @JvmField val resLen: Int = 0, @JvmField @ProtoNumber(5) val resLen: Int = 0,
@ProtoNumber(6) @JvmField val resWidth: Int = 0, @JvmField @ProtoNumber(6) val resWidth: Int = 0,
@ProtoNumber(7) @JvmField val time: Int = 0, @JvmField @ProtoNumber(7) val time: Int = 0,
@ProtoNumber(8) @JvmField val starttime: Long = 0L, @JvmField @ProtoNumber(8) val starttime: Long = 0L,
@ProtoNumber(9) @JvmField val isAudio: Int = 0 @JvmField @ProtoNumber(9) val isAudio: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -143,142 +150,143 @@ internal class BdhExtinfo : ProtoBuf {
internal class CSDataHighwayHead : ProtoBuf { internal class CSDataHighwayHead : ProtoBuf {
@Serializable @Serializable
internal class C2CCommonExtendinfo( internal class C2CCommonExtendinfo(
@ProtoNumber(1) @JvmField val infoId: Int = 0, @JvmField @ProtoNumber(1) val infoId: Int = 0,
@ProtoNumber(2) @JvmField val msgFilterExtendinfo: FilterExtendinfo? = null @JvmField @ProtoNumber(2) val msgFilterExtendinfo: FilterExtendinfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class DataHighwayHead( internal class DataHighwayHead(
@ProtoNumber(1) @JvmField val version: Int = 0, @JvmField @ProtoNumber(1) val version: Int = 0,
@ProtoNumber(2) @JvmField val uin: String = "", // yes @JvmField @ProtoNumber(2) val uin: String = "",
@ProtoNumber(3) @JvmField val command: String = "", @JvmField @ProtoNumber(3) val command: String = "",
@ProtoNumber(4) @JvmField val seq: Int = 0, @JvmField @ProtoNumber(4) val seq: Int = 0,
@ProtoNumber(5) @JvmField val retryTimes: Int = 0, @JvmField @ProtoNumber(5) val retryTimes: Int = 0,
@ProtoNumber(6) @JvmField val appid: Int = 0, @JvmField @ProtoNumber(6) val appid: Int = 0,
@ProtoNumber(7) @JvmField val dataflag: Int = 0, @JvmField @ProtoNumber(7) val dataflag: Int = 0,
@ProtoNumber(8) @JvmField val commandId: Int = 0, @JvmField @ProtoNumber(8) val commandId: Int = 0,
@ProtoNumber(9) @JvmField val buildVer: String = "", @JvmField @ProtoNumber(9) val buildVer: String = "",
@ProtoNumber(10) @JvmField val localeId: Int = 0 @JvmField @ProtoNumber(10) val localeId: Int = 0,
@JvmField @ProtoNumber(11) val envId: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class DataHole( internal class DataHole(
@ProtoNumber(1) @JvmField val begin: Long = 0L, @JvmField @ProtoNumber(1) val begin: Long = 0L,
@ProtoNumber(2) @JvmField val end: Long = 0L @JvmField @ProtoNumber(2) val end: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class FilterExtendinfo( internal class FilterExtendinfo(
@ProtoNumber(1) @JvmField val filterFlag: Int = 0, @JvmField @ProtoNumber(1) val filterFlag: Int = 0,
@ProtoNumber(2) @JvmField val msgImageFilterRequest: ImageFilterRequest? = null @JvmField @ProtoNumber(2) val msgImageFilterRequest: ImageFilterRequest? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class FilterStyle( internal class FilterStyle(
@ProtoNumber(1) @JvmField val styleId: Int = 0, @JvmField @ProtoNumber(1) val styleId: Int = 0,
@ProtoNumber(2) @JvmField val styleName: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ImageFilterRequest( internal class ImageFilterRequest(
@ProtoNumber(1) @JvmField val sessionId: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val clientIp: Int = 0, @JvmField @ProtoNumber(2) val clientIp: Int = 0,
@ProtoNumber(3) @JvmField val uin: Long = 0L, @JvmField @ProtoNumber(3) val uin: Long = 0L,
@ProtoNumber(4) @JvmField val style: FilterStyle? = null, @JvmField @ProtoNumber(4) val style: FilterStyle? = null,
@ProtoNumber(5) @JvmField val width: Int = 0, @JvmField @ProtoNumber(5) val width: Int = 0,
@ProtoNumber(6) @JvmField val height: Int = 0, @JvmField @ProtoNumber(6) val height: Int = 0,
@ProtoNumber(7) @JvmField val imageData: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ImageFilterResponse( internal class ImageFilterResponse(
@ProtoNumber(1) @JvmField val retCode: Int = 0, @JvmField @ProtoNumber(1) val retCode: Int = 0,
@ProtoNumber(2) @JvmField val imageData: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val costTime: Int = 0 @JvmField @ProtoNumber(3) val costTime: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class LoginSigHead( internal class LoginSigHead(
@ProtoNumber(1) @JvmField val loginsigType: Int = 0, @JvmField @ProtoNumber(1) val loginsigType: Int = 0,
@ProtoNumber(2) @JvmField val loginsig: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class NewServiceTicket( internal class NewServiceTicket(
@ProtoNumber(1) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val signature: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val ukey: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class PicInfoExt( internal class PicInfoExt(
@ProtoNumber(1) @JvmField val picWidth: Int = 0, @JvmField @ProtoNumber(1) val picWidth: Int = 0,
@ProtoNumber(2) @JvmField val picHeight: Int = 0, @JvmField @ProtoNumber(2) val picHeight: Int = 0,
@ProtoNumber(3) @JvmField val picFlag: Int = 0, @JvmField @ProtoNumber(3) val picFlag: Int = 0,
@ProtoNumber(4) @JvmField val busiType: Int = 0, @JvmField @ProtoNumber(4) val busiType: Int = 0,
@ProtoNumber(5) @JvmField val srcTerm: Int = 0, @JvmField @ProtoNumber(5) val srcTerm: Int = 0,
@ProtoNumber(6) @JvmField val platType: Int = 0, @JvmField @ProtoNumber(6) val platType: Int = 0,
@ProtoNumber(7) @JvmField val netType: Int = 0, @JvmField @ProtoNumber(7) val netType: Int = 0,
@ProtoNumber(8) @JvmField val imgType: Int = 0, @JvmField @ProtoNumber(8) val imgType: Int = 0,
@ProtoNumber(9) @JvmField val appPicType: Int = 0 @JvmField @ProtoNumber(9) val appPicType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class PicRspExtInfo( internal class PicRspExtInfo(
@ProtoNumber(1) @JvmField val skey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val skey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val clientIp: Int = 0, @JvmField @ProtoNumber(2) val clientIp: Int = 0,
@ProtoNumber(3) @JvmField val upOffset: Long = 0L, @JvmField @ProtoNumber(3) val upOffset: Long = 0L,
@ProtoNumber(4) @JvmField val blockSize: Long = 0L @JvmField @ProtoNumber(4) val blockSize: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class QueryHoleRsp( internal class QueryHoleRsp(
@ProtoNumber(1) @JvmField val result: Int = 0, @JvmField @ProtoNumber(1) val result: Int = 0,
@ProtoNumber(2) @JvmField val dataHole: List<DataHole> = emptyList(), @JvmField @ProtoNumber(2) val dataHole: List<DataHole> = emptyList(),
@ProtoNumber(3) @JvmField val boolCompFlag: Boolean = false @JvmField @ProtoNumber(3) val boolCompFlag: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ReqDataHighwayHead( internal class ReqDataHighwayHead(
@ProtoNumber(1) @JvmField val msgBasehead: DataHighwayHead? = null, @JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null,
@ProtoNumber(2) @JvmField val msgSeghead: SegHead? = null, @JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null,
@ProtoNumber(3) @JvmField val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(4) @JvmField val timestamp: Long = 0L, @JvmField @ProtoNumber(4) val timestamp: Long = 0L,
@ProtoNumber(5) @JvmField val msgLoginSigHead: LoginSigHead? = null @JvmField @ProtoNumber(5) val msgLoginSigHead: LoginSigHead? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class RspBody( internal class RspBody(
@ProtoNumber(1) @JvmField val msgQueryHoleRsp: QueryHoleRsp? = null @JvmField @ProtoNumber(1) val msgQueryHoleRsp: QueryHoleRsp? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class RspDataHighwayHead( internal class RspDataHighwayHead(
@ProtoNumber(1) @JvmField val msgBasehead: DataHighwayHead? = null, @JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null,
@ProtoNumber(2) @JvmField val msgSeghead: SegHead? = null, @JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null,
@ProtoNumber(3) @JvmField val errorCode: Int = 0, @JvmField @ProtoNumber(3) val errorCode: Int = 0,
@ProtoNumber(4) @JvmField val allowRetry: Int = 0, @JvmField @ProtoNumber(4) val allowRetry: Int = 0,
@ProtoNumber(5) @JvmField val cachecost: Int = 0, @JvmField @ProtoNumber(5) val cachecost: Int = 0,
@ProtoNumber(6) @JvmField val htcost: Int = 0, @JvmField @ProtoNumber(6) val htcost: Int = 0,
@ProtoNumber(7) @JvmField val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(8) @JvmField val timestamp: Long = 0L, @JvmField @ProtoNumber(8) val timestamp: Long = 0L,
@ProtoNumber(9) @JvmField val range: Long = 0L, @JvmField @ProtoNumber(9) val range: Long = 0L,
@ProtoNumber(10) @JvmField val isReset: Int = 0 @JvmField @ProtoNumber(10) val isReset: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class SegHead( internal class SegHead(
@ProtoNumber(1) @JvmField val serviceid: Int = 0, @JvmField @ProtoNumber(1) val serviceid: Int = 0,
@ProtoNumber(2) @JvmField val filesize: Long = 0L, @JvmField @ProtoNumber(2) val filesize: Long = 0L,
@ProtoNumber(3) @JvmField val dataoffset: Long = 0L, @JvmField @ProtoNumber(3) val dataoffset: Long = 0L,
@ProtoNumber(4) @JvmField val datalength: Int = 0, @JvmField @ProtoNumber(4) val datalength: Int = 0,
@ProtoNumber(5) @JvmField val rtcode: Int = 0, @JvmField @ProtoNumber(5) val rtcode: Int = 0,
@ProtoNumber(6) @JvmField val serviceticket: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(7) @JvmField val flag: Int = 0, @JvmField @ProtoNumber(7) val flag: Int = 0,
@ProtoNumber(8) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val md5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(9) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(10) @JvmField val cacheAddr: Int = 0, @JvmField @ProtoNumber(10) val cacheAddr: Int = 0,
@ProtoNumber(11) @JvmField val queryTimes: Int = 0, @JvmField @ProtoNumber(11) val queryTimes: Int = 0,
@ProtoNumber(12) @JvmField val updateCacheip: Int = 0 @JvmField @ProtoNumber(12) val updateCacheip: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -286,31 +294,31 @@ internal class CSDataHighwayHead : ProtoBuf {
internal class HwConfigPersistentPB : ProtoBuf { internal class HwConfigPersistentPB : ProtoBuf {
@Serializable @Serializable
internal class HwConfigItemPB( internal class HwConfigItemPB(
@ProtoNumber(1) @JvmField val ingKey: String = "", @JvmField @ProtoNumber(1) val key: String = "",
@ProtoNumber(2) @JvmField val endPointList: List<HwEndPointPB> = emptyList() @JvmField @ProtoNumber(2) val endPointList: List<HwEndPointPB> = emptyList()
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class HwConfigPB( internal class HwConfigPB(
@ProtoNumber(1) @JvmField val configItemList: List<HwConfigItemPB> = emptyList(), @JvmField @ProtoNumber(1) val configItemList: List<HwConfigItemPB> = emptyList(),
@ProtoNumber(2) @JvmField val netSegConfList: List<HwNetSegConfPB> = emptyList(), @JvmField @ProtoNumber(2) val netSegConfList: List<HwNetSegConfPB> = emptyList(),
@ProtoNumber(3) @JvmField val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(), @JvmField @ProtoNumber(3) val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(),
@ProtoNumber(4) @JvmField val configItemListIp6: List<HwConfigItemPB> = emptyList() @JvmField @ProtoNumber(4) val configItemListIp6: List<HwConfigItemPB> = emptyList()
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class HwEndPointPB( internal class HwEndPointPB(
@ProtoNumber(1) @JvmField val ingHost: String = "", @JvmField @ProtoNumber(1) val host: String = "",
@ProtoNumber(2) @JvmField val int32Port: Int = 0, @JvmField @ProtoNumber(2) val int32Port: Int = 0,
@ProtoNumber(3) @JvmField val int64Timestampe: Long = 0L @JvmField @ProtoNumber(3) val int64Timestampe: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class HwNetSegConfPB( internal class HwNetSegConfPB(
@ProtoNumber(1) @JvmField val int64NetType: Long = 0L, @JvmField @ProtoNumber(1) val int64NetType: Long = 0L,
@ProtoNumber(2) @JvmField val int64SegSize: Long = 0L, @JvmField @ProtoNumber(2) val int64SegSize: Long = 0L,
@ProtoNumber(3) @JvmField val int64SegNum: Long = 0L, @JvmField @ProtoNumber(3) val int64SegNum: Long = 0L,
@ProtoNumber(4) @JvmField val int64CurConnNum: Long = 0L @JvmField @ProtoNumber(4) val int64CurConnNum: Long = 0L
) : ProtoBuf ) : ProtoBuf
} }
@ -318,8 +326,8 @@ internal class HwConfigPersistentPB : ProtoBuf {
internal class HwSessionInfoPersistentPB : ProtoBuf { internal class HwSessionInfoPersistentPB : ProtoBuf {
@Serializable @Serializable
internal class HwSessionInfoPB( internal class HwSessionInfoPB(
@ProtoNumber(1) @JvmField val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY @JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }
@ -327,137 +335,145 @@ internal class HwSessionInfoPersistentPB : ProtoBuf {
internal class Subcmd0x501 : ProtoBuf { internal class Subcmd0x501 : ProtoBuf {
@Serializable @Serializable
internal class ReqBody( internal class ReqBody(
@ProtoNumber(1281) @JvmField val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null @JvmField @ProtoNumber(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class RspBody( internal class RspBody(
@ProtoNumber(1281) @JvmField val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null @JvmField @ProtoNumber(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class SubCmd0x501ReqBody( internal class SubCmd0x501ReqBody(
@ProtoNumber(1) @JvmField val uin: Long = 0L, @JvmField @ProtoNumber(1) val uin: Long = 0L,
@ProtoNumber(2) @JvmField val idcId: Int = 0, @JvmField @ProtoNumber(2) val idcId: Int = 0,
@ProtoNumber(3) @JvmField val appid: Int = 0, @JvmField @ProtoNumber(3) val appid: Int = 0,
@ProtoNumber(4) @JvmField val loginSigType: Int = 0, @JvmField @ProtoNumber(4) val loginSigType: Int = 0,
@ProtoNumber(5) @JvmField val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(6) @JvmField val requestFlag: Int = 0, @JvmField @ProtoNumber(6) val requestFlag: Int = 0,
@ProtoNumber(7) @JvmField val uint32ServiceTypes: List<Int> = emptyList(), @JvmField @ProtoNumber(7) val uint32ServiceTypes: List<Int> = emptyList(),
@ProtoNumber(8) @JvmField val bid: Int = 0, @JvmField @ProtoNumber(8) val bid: Int = 0,
@ProtoNumber(9) @JvmField val term: Int = 0, @JvmField @ProtoNumber(9) val term: Int = 0,
@ProtoNumber(10) @JvmField val plat: Int = 0, @JvmField @ProtoNumber(10) val plat: Int = 0,
@ProtoNumber(11) @JvmField val net: Int = 0, @JvmField @ProtoNumber(11) val net: Int = 0,
@ProtoNumber(12) @JvmField val caller: Int = 0 @JvmField @ProtoNumber(12) val caller: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class SubCmd0x501Rspbody( internal class SubCmd0x501Rspbody(
@ProtoNumber(1) @JvmField val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val msgHttpconnAddrs: List<SrvAddrs> = emptyList(), @JvmField @ProtoNumber(3) val msgHttpconnAddrs: List<SrvAddrs> = emptyList(),
@ProtoNumber(4) @JvmField val preConnection: Int = 0, @JvmField @ProtoNumber(4) val preConnection: Int = 0,
@ProtoNumber(5) @JvmField val csConn: Int = 0, @JvmField @ProtoNumber(5) val csConn: Int = 0,
@ProtoNumber(6) @JvmField val msgIpLearnConf: IpLearnConf? = null, @JvmField @ProtoNumber(6) val msgIpLearnConf: IpLearnConf? = null,
@ProtoNumber(7) @JvmField val msgDynTimeoutConf: DynTimeOutConf? = null, @JvmField @ProtoNumber(7) val msgDynTimeoutConf: DynTimeOutConf? = null,
@ProtoNumber(8) @JvmField val msgOpenUpConf: OpenUpConf? = null, @JvmField @ProtoNumber(8) val msgOpenUpConf: OpenUpConf? = null,
@ProtoNumber(9) @JvmField val msgDownloadEncryptConf: DownloadEncryptConf? = null, @JvmField @ProtoNumber(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null,
@ProtoNumber(10) @JvmField val msgShortVideoConf: ShortVideoConf? = null, @JvmField @ProtoNumber(10) val msgShortVideoConf: ShortVideoConf? = null,
@ProtoNumber(11) @JvmField val msgPtvConf: PTVConf? = null @JvmField @ProtoNumber(11) val msgPtvConf: PTVConf? = null,
@JvmField @ProtoNumber(12) val shareType: Int = 0,
@JvmField @ProtoNumber(13) val shareChannel: Int = 0,
@JvmField @ProtoNumber(14) val fmtPolicy: Int = 0,
@JvmField @ProtoNumber(15) val bigdataPolicy: Int = 0,
@JvmField @ProtoNumber(16) val connAttemptDelay: Int = 0
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
internal class DownloadEncryptConf( internal class DownloadEncryptConf(
@ProtoNumber(1) @JvmField val boolEnableEncryptRequest: Boolean = false, @JvmField @ProtoNumber(1) val boolEnableEncryptRequest: Boolean = false,
@ProtoNumber(2) @JvmField val boolEnableEncryptedPic: Boolean = false, @JvmField @ProtoNumber(2) val boolEnableEncryptedPic: Boolean = false,
@ProtoNumber(3) @JvmField val ctrlFlag: Int = 0 @JvmField @ProtoNumber(3) val ctrlFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class DynTimeOutConf( internal class DynTimeOutConf(
@ProtoNumber(1) @JvmField val tbase2g: Int = 0, @JvmField @ProtoNumber(1) val tbase2g: Int = 0,
@ProtoNumber(2) @JvmField val tbase3g: Int = 0, @JvmField @ProtoNumber(2) val tbase3g: Int = 0,
@ProtoNumber(3) @JvmField val tbase4g: Int = 0, @JvmField @ProtoNumber(3) val tbase4g: Int = 0,
@ProtoNumber(4) @JvmField val tbaseWifi: Int = 0, @JvmField @ProtoNumber(4) val tbaseWifi: Int = 0,
@ProtoNumber(5) @JvmField val torg2g: Int = 0, @JvmField @ProtoNumber(5) val torg2g: Int = 0,
@ProtoNumber(6) @JvmField val torg3g: Int = 0, @JvmField @ProtoNumber(6) val torg3g: Int = 0,
@ProtoNumber(7) @JvmField val torg4g: Int = 0, @JvmField @ProtoNumber(7) val torg4g: Int = 0,
@ProtoNumber(8) @JvmField val torgWifi: Int = 0, @JvmField @ProtoNumber(8) val torgWifi: Int = 0,
@ProtoNumber(9) @JvmField val maxTimeout: Int = 0, @JvmField @ProtoNumber(9) val maxTimeout: Int = 0,
@ProtoNumber(10) @JvmField val enableDynTimeout: Int = 0, @JvmField @ProtoNumber(10) val enableDynTimeout: Int = 0,
@ProtoNumber(11) @JvmField val maxTimeout2g: Int = 0, @JvmField @ProtoNumber(11) val maxTimeout2g: Int = 0,
@ProtoNumber(12) @JvmField val maxTimeout3g: Int = 0, @JvmField @ProtoNumber(12) val maxTimeout3g: Int = 0,
@ProtoNumber(13) @JvmField val maxTimeout4g: Int = 0, @JvmField @ProtoNumber(13) val maxTimeout4g: Int = 0,
@ProtoNumber(14) @JvmField val maxTimeoutWifi: Int = 0, @JvmField @ProtoNumber(14) val maxTimeoutWifi: Int = 0,
@ProtoNumber(15) @JvmField val hbTimeout2g: Int = 0, @JvmField @ProtoNumber(15) val hbTimeout2g: Int = 0,
@ProtoNumber(16) @JvmField val hbTimeout3g: Int = 0, @JvmField @ProtoNumber(16) val hbTimeout3g: Int = 0,
@ProtoNumber(17) @JvmField val hbTimeout4g: Int = 0, @JvmField @ProtoNumber(17) val hbTimeout4g: Int = 0,
@ProtoNumber(18) @JvmField val hbTimeoutWifi: Int = 0, @JvmField @ProtoNumber(18) val hbTimeoutWifi: Int = 0,
@ProtoNumber(19) @JvmField val hbTimeoutDefault: Int = 0 @JvmField @ProtoNumber(19) val hbTimeoutDefault: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class Ip6Addr( internal class Ip6Addr(
@ProtoNumber(1) @JvmField val type: Int = 0, @JvmField @ProtoNumber(1) val type: Int = 0,
@ProtoNumber(2) @JvmField val ip6: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(3) @JvmField val port: Int = 0, @JvmField @ProtoNumber(3) val port: Int = 0,
@ProtoNumber(4) @JvmField val area: Int = 0, @JvmField @ProtoNumber(4) val area: Int = 0,
@ProtoNumber(5) @JvmField val sameIsp: Int = 0 @JvmField @ProtoNumber(5) val sameIsp: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class IpAddr( internal class IpAddr(
@ProtoNumber(1) @JvmField val type: Int = 0, @JvmField @ProtoNumber(1) val type: Int = 0,
@ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(2) @JvmField val ip: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(2) val ip: Int = 0,
@ProtoNumber(3) @JvmField val port: Int = 0, @JvmField @ProtoNumber(3) val port: Int = 0,
@ProtoNumber(4) @JvmField val area: Int = 0, @JvmField @ProtoNumber(4) val area: Int = 0,
@ProtoNumber(5) @JvmField val sameIsp: Int = 0 @JvmField @ProtoNumber(5) val sameIsp: Int = 0
) : ProtoBuf ) : ProtoBuf {
fun decode(): Pair<Int, Int> = ip to port
}
@Serializable @Serializable
internal class IpLearnConf( internal class IpLearnConf(
@ProtoNumber(1) @JvmField val refreshCachedIp: Int = 0, @JvmField @ProtoNumber(1) val refreshCachedIp: Int = 0,
@ProtoNumber(2) @JvmField val enableIpLearn: Int = 0 @JvmField @ProtoNumber(2) val enableIpLearn: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class NetSegConf( internal class NetSegConf(
@ProtoNumber(1) @JvmField val netType: Int = 0, @JvmField @ProtoNumber(1) val netType: Int = 0,
@ProtoNumber(2) @JvmField val segsize: Int = 0, @JvmField @ProtoNumber(2) val segsize: Int = 0,
@ProtoNumber(3) @JvmField val segnum: Int = 0, @JvmField @ProtoNumber(3) val segnum: Int = 0,
@ProtoNumber(4) @JvmField val curconnnum: Int = 0 @JvmField @ProtoNumber(4) val curconnnum: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class OpenUpConf( internal class OpenUpConf(
@ProtoNumber(1) @JvmField val boolEnableOpenup: Boolean = false, @JvmField @ProtoNumber(1) val boolEnableOpenup: Boolean = false,
@ProtoNumber(2) @JvmField val preSendSegnum: Int = 0, @JvmField @ProtoNumber(2) val preSendSegnum: Int = 0,
@ProtoNumber(3) @JvmField val preSendSegnum3g: Int = 0, @JvmField @ProtoNumber(3) val preSendSegnum3g: Int = 0,
@ProtoNumber(4) @JvmField val preSendSegnum4g: Int = 0, @JvmField @ProtoNumber(4) val preSendSegnum4g: Int = 0,
@ProtoNumber(5) @JvmField val preSendSegnumWifi: Int = 0 @JvmField @ProtoNumber(5) val preSendSegnumWifi: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class PTVConf( internal class PTVConf(
@ProtoNumber(1) @JvmField val channelType: Int = 0, @JvmField @ProtoNumber(1) val channelType: Int = 0,
@ProtoNumber(2) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(), @JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = emptyList(),
@ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false @JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ShortVideoConf( internal class ShortVideoConf(
@ProtoNumber(1) @JvmField val channelType: Int = 0, @JvmField @ProtoNumber(1) val channelType: Int = 0,
@ProtoNumber(2) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(), @JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = emptyList(),
@ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false, @JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false,
@ProtoNumber(4) @JvmField val boolSendAheadSignal: Boolean = false @JvmField @ProtoNumber(4) val boolSendAheadSignal: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class SrvAddrs( internal data class SrvAddrs(
@ProtoNumber(1) @JvmField val serviceType: Int = 0, @JvmField @ProtoNumber(1) val serviceType: Int = 0,
@ProtoNumber(2) @JvmField val msgAddrs: List<IpAddr> = emptyList(), @JvmField @ProtoNumber(2) val msgAddrs: List<IpAddr> = emptyList(),
@ProtoNumber(3) @JvmField val fragmentSize: Int = 0, @JvmField @ProtoNumber(3) val fragmentSize: Int = 0,
@ProtoNumber(4) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(), @JvmField @ProtoNumber(4) val msgNetsegconf: List<NetSegConf> = emptyList(),
@ProtoNumber(5) @JvmField val msgAddrsV6: List<Ip6Addr> = emptyList() @JvmField @ProtoNumber(5) val msgAddrsV6: List<Ip6Addr> = emptyList()
) : ProtoBuf ) : ProtoBuf
} }
} }

View File

@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
import net.mamoe.mirai.internal.network.readUShortLVByteArray import net.mamoe.mirai.internal.network.readUShortLVByteArray
import net.mamoe.mirai.internal.network.tryDecryptOrNull
import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -17,6 +17,7 @@ import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
@ -63,14 +64,12 @@ internal class PttStore {
} }
} }
@OptIn(ExperimentalStdlibApi::class) fun createTryUpPttPack(
operator fun invoke(
client: QQAndroidClient,
uin: Long, uin: Long,
groupCode: Long, groupCode: Long,
resource: ExternalResource resource: ExternalResource
): OutgoingPacket { ): Cmd0x388.ReqBody {
val pack = Cmd0x388.ReqBody( return Cmd0x388.ReqBody(
netType = 3, // wifi netType = 3, // wifi
subcmd = 3, subcmd = 3,
msgTryupPttReq = listOf( msgTryupPttReq = listOf(
@ -87,12 +86,22 @@ internal class PttStore {
innerIp = 0, innerIp = 0,
buildVer = "6.5.5.663".encodeToByteArray(), buildVer = "6.5.5.663".encodeToByteArray(),
voiceLength = 1, voiceLength = 1,
codec = 0, // don't use resource.codec codec = resource.voiceCodec, // HTTP 时只支持 0
// 2021/1/26 因为 #577 修改为 resource.voiceCodec
voiceType = 1, voiceType = 1,
boolNewUpChan = true boolNewUpChan = true
) )
) )
) )
}
operator fun invoke(
client: QQAndroidClient,
uin: Long,
groupCode: Long,
resource: ExternalResource
): OutgoingPacketWithRespType<Response> {
val pack = createTryUpPttPack(uin, groupCode, resource)
return buildOutgoingUniPacket(client) { return buildOutgoingUniPacket(client) {
writeProtoBuf(Cmd0x388.ReqBody.serializer(), pack) writeProtoBuf(Cmd0x388.ReqBody.serializer(), pack)
} }

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
import kotlinx.io.streams.readPacketAtMost import kotlinx.io.streams.readPacketAtMost
import kotlinx.io.streams.writePacket import kotlinx.io.streams.writePacket
import net.mamoe.mirai.utils.withUse
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.IOException import java.io.IOException
@ -23,6 +24,8 @@ import java.net.NoRouteToHostException
import java.net.Socket import java.net.Socket
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/** /**
* TCP Socket. * TCP Socket.
@ -97,6 +100,26 @@ internal class PlatformSocket : Closeable {
writeChannel = socket.getOutputStream().buffered() writeChannel = socket.getOutputStream().buffered()
} }
} }
companion object {
suspend fun connect(
serverIp: String,
serverPort: Int,
): PlatformSocket {
val socket = PlatformSocket()
socket.connect(serverIp, serverPort)
return socket
}
suspend inline fun <R> withConnection(
serverIp: String,
serverPort: Int,
block: PlatformSocket.() -> R
): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return connect(serverIp, serverPort).withUse(block)
}
}
} }

View File

@ -0,0 +1,46 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.internal.utils
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.math.roundToInt
internal suspend inline fun <R> Collection<Pair<Int, Int>>.retryWithServers(
timeoutMillis: Long,
onFail: (exception: Throwable?) -> Nothing,
crossinline block: suspend (ip: String, port: Int) -> R
): R {
require(this.isNotEmpty()) { "receiver of retryWithServers must not be empty" }
var exception: Throwable? = null
for (pair in this) {
return kotlin.runCatching {
withTimeoutOrNull(timeoutMillis) {
block(pair.first.toIpV4AddressString(), pair.second)
}
}.recover {
if (exception != null) {
it.addSuppressed(exception!!)
}
exception = it // so as to show last exception followed by suppressed others
null
}.getOrNull() ?: continue
}
onFail(exception)
}
internal fun Int.sizeToString() = this.toLong().sizeToString()
internal fun Long.sizeToString(): String {
return if (this < 1024) {
"$this B"
} else ((this * 100.0 / 1024).roundToInt() / 100.0).toString() + " KiB"
}