Commit 78ee01de authored by Him188's avatar Him188

Fix bugs

parent b6c15536
...@@ -14,7 +14,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId ...@@ -14,7 +14,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ClosedChannelException import net.mamoe.mirai.utils.io.ClosedChannelException
import net.mamoe.mirai.utils.io.PlatformDatagramChannel import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.utils.io.ReadPacketInternalException import net.mamoe.mirai.utils.io.ReadPacketInternalException
import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
...@@ -25,16 +25,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -25,16 +25,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job]) override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
private val channel: PlatformDatagramChannel = PlatformDatagramChannel("wtlogin.qq.com", 8000) private lateinit var channel: PlatformSocket
override suspend fun login() { override suspend fun login() {
channel = PlatformSocket()
channel.connect("113.96.13.208", 8080)
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
println("Sending login")
LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>() LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()
println("Login sent")
} }
private suspend inline fun processReceive() { private suspend fun processReceive() {
while (channel.isOpen) { while (channel.isOpen) {
val rawInput = try { val rawInput = try {
channel.read() channel.read()
...@@ -52,17 +54,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -52,17 +54,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
launch(CoroutineName("Incoming Packet handler")) { launch(CoroutineName("Incoming Packet handler")) {
try { rawInput.debugPrint("Received").use {
rawInput.debugPrint("Received") if (it.remaining == 0L) {
} catch (e: Exception) { bot.logger.error("Empty packet received. Consider if bad packet was sent.")
bot.logger.error(e) return@launch
} }
}
rawInput.use {
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int -> KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
if (PacketReceivedEvent(packet).broadcast().cancelled) { if (PacketReceivedEvent(packet).broadcast().cancelled) {
return return@parseIncomingPacket
} }
packetListeners.forEach { listener -> packetListeners.forEach { listener ->
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) { if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
...@@ -73,11 +72,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -73,11 +72,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
} }
} }
}
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E { suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
val handler = PacketListener(packetId = packetId, sequenceId = sequenceId) val handler = PacketListener(packetId = packetId, sequenceId = sequenceId)
packetListeners.addLast(handler) packetListeners.addLast(handler)
check(channel.send(delegate)) { packetListeners.remove(handler); "Cannot send packet" } //println(delegate.readBytes().toUHexString())
println("Sending length=" + delegate.remaining)
channel.send(delegate)//) { packetListeners.remove(handler); "Cannot send packet" }
println("Packet sent")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return handler.await() as E return handler.await() as E
} }
......
...@@ -4,7 +4,10 @@ import kotlinx.atomicfu.AtomicInt ...@@ -4,7 +4,10 @@ import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.qqandroid.utils.* import net.mamoe.mirai.qqandroid.utils.Context
import net.mamoe.mirai.qqandroid.utils.DeviceInfo
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
...@@ -31,7 +34,7 @@ internal open class QQAndroidClient( ...@@ -31,7 +34,7 @@ internal open class QQAndroidClient(
val ecdh: ECDH = ECDH(), val ecdh: ECDH = ECDH(),
val device: DeviceInfo = SystemDeviceInfo(context) val device: DeviceInfo = SystemDeviceInfo(context)
) { ) {
val tgtgtKey: ByteArray = generateTgtgtKey(device.guid) val tgtgtKey: ByteArray = ByteArray(16) // generateTgtgtKey(device.guid)
var miscBitMap: Int = 184024956 // 也可能是 150470524 ? var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
var mainSigMap: Int = 16724722 var mainSigMap: Int = 16724722
......
...@@ -11,7 +11,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI ...@@ -11,7 +11,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.encryptAndWrite import net.mamoe.mirai.utils.cryptor.encryptAndWrite
import net.mamoe.mirai.utils.cryptor.encryptBy
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
/** /**
...@@ -47,6 +46,7 @@ private val EMPTY_BYTE_ARRAY = ByteArray(0) ...@@ -47,6 +46,7 @@ private val EMPTY_BYTE_ARRAY = ByteArray(0)
* *
* byte[] body encrypted by 16 zero * byte[] body encrypted by 16 zero
*/ */
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket( internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
client: QQAndroidClient, client: QQAndroidClient,
subAppId: Long, subAppId: Long,
...@@ -54,9 +54,11 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket( ...@@ -54,9 +54,11 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
name: String? = null, name: String? = null,
id: PacketId = this.id, id: PacketId = this.id,
ssoExtraData: ByteReadPacket = BRP_STUB, ssoExtraData: ByteReadPacket = BRP_STUB,
sequenceId: Int = PacketFactory.atomicNextSequenceId(),
body: BytePacketBuilder.(sequenceId: Int) -> Unit body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket { ): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId()
return OutgoingPacket(name, id, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) { writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0A) writeInt(0x00_00_00_0A)
writeByte(0x02) writeByte(0x02)
...@@ -77,7 +79,8 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket( ...@@ -77,7 +79,8 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
} }
} }
} }
}) })
}
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY) private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
...@@ -141,7 +144,7 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket( ...@@ -141,7 +144,7 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket(
writeInt(4) writeInt(4)
client.device.ksid.let { client.device.ksid.let {
writeInt(it.length + 4) writeShort((it.length + 2).toShort())
writeStringUtf8(it) writeStringUtf8(it)
} }
...@@ -190,7 +193,7 @@ internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket( ...@@ -190,7 +193,7 @@ internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket(
interface EncryptMethod { interface EncryptMethod {
val id: Int val id: Int
fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket
} }
internal interface EncryptMethodSessionKey : EncryptMethod { internal interface EncryptMethodSessionKey : EncryptMethod {
...@@ -208,14 +211,14 @@ internal interface EncryptMethodSessionKey : EncryptMethod { ...@@ -208,14 +211,14 @@ internal interface EncryptMethodSessionKey : EncryptMethod {
* fully encrypted * fully encrypted
* } * }
*/ */
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) { override fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" } require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" }
writeByte(1) // const writeByte(1) // const
writeByte(if (currentLoginState == 2) 3 else 2) writeByte(if (currentLoginState == 2) 3 else 2)
writeFully(sessionKey) writeFully(sessionKey)
writeShort(258) // const writeShort(258) // const
writeShort(0) // const, length of publicKey writeShort(0) // const, length of publicKey
body.encryptBy(sessionKey) { encrypted -> writeFully(encrypted) } encryptAndWrite(sessionKey, body)
} }
} }
...@@ -248,13 +251,22 @@ internal interface EncryptMethodECDH : EncryptMethod { ...@@ -248,13 +251,22 @@ internal interface EncryptMethodECDH : EncryptMethod {
* byte[] [ECDH.publicKey] * byte[] [ECDH.publicKey]
* byte[] encrypted `body()` by [ECDH.shareKey] * byte[] encrypted `body()` by [ECDH.shareKey]
*/ */
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) = ecdh.run { override fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
writeByte(1) // const writeByte(1) // const
writeByte(1) // const writeByte(1) // const
writeFully(keyPair.privateKey.getEncoded()) writeFully(ByteArray(16))
writeShort(258) // const writeShort(258) // const
writeShortLVByteArray(keyPair.publicKey.getEncoded().drop(23).toByteArray().also { check(it.size == 49) { "Bad publicKey generated. Expected size=49, got${it.size}" } })
body.encryptBy(keyPair.shareKey) { encrypted -> writeFully(encrypted) } writeShortLVByteArray("04 CB 36 66 98 56 1E 93 6E 80 C1 57 E0 74 CA B1 3B 0B B6 8D DE B2 82 45 48 A1 B1 8D D4 FB 61 22 AF E1 2F E4 8C 52 66 D8 D7 26 9D 76 51 A8 EB 6F E7".hexToBytes())
/*writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also {
it.toUHexString().debugPrint("PUBLIC KEY")
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" }
//check(ecdh.calculateShareKeyByPeerPublicKey(it.adjustToPublicKey()).contentEquals(ecdh.keyPair.shareKey)) { "PublicKey Validation failed" }
})*/
encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body)
//encryptAndWrite(ecdh.keyPair.shareKey, body)
} }
} }
...@@ -278,22 +290,20 @@ internal interface EncryptMethodECDH : EncryptMethod { ...@@ -278,22 +290,20 @@ internal interface EncryptMethodECDH : EncryptMethod {
* byte 3 // tail * byte 3 // tail
*/ */
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeOicqRequestPacket( internal fun BytePacketBuilder.writeOicqRequestPacket(
client: QQAndroidClient, client: QQAndroidClient,
encryptMethod: EncryptMethod, encryptMethod: EncryptMethod,
packetId: PacketId, packetId: PacketId,
bodyBlock: BytePacketBuilder.() -> Unit bodyBlock: BytePacketBuilder.() -> Unit
) { ) {
val body = encryptMethod.run { val body = encryptMethod.makeBody(bodyBlock)
buildPacket { encryptAndWrite(buildPacket { bodyBlock() }) } // writeIntLVPacket(lengthOffset = { it + 4 }) {
}
writeIntLVPacket(lengthOffset = { it + 4 }) {
// Head // Head
writeByte(0x02) // head writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion) writeShort(client.protocolVersion)
writeShort(1) // const??
writeShort(packetId.commandId.toShort()) writeShort(packetId.commandId.toShort())
writeShort(1) // const??
writeQQ(client.account.id) writeQQ(client.account.id)
writeByte(3) // originally const writeByte(3) // originally const
writeByte(encryptMethod.id.toByte()) writeByte(encryptMethod.id.toByte())
...@@ -307,7 +317,7 @@ internal inline fun BytePacketBuilder.writeOicqRequestPacket( ...@@ -307,7 +317,7 @@ internal inline fun BytePacketBuilder.writeOicqRequestPacket(
// Tail // Tail
writeByte(0x03) // tail writeByte(0x03) // tail
} // }
} }
/* /*
00 00 01 64 00 00 01 64
......
package net.mamoe.mirai.qqandroid.network.protocol.packet package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.IoBuffer
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
...@@ -41,25 +39,11 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt ...@@ -41,25 +39,11 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
* **解码**服务器的回复数据包 * **解码**服务器的回复数据包
*/ */
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket
companion object {
private val sequenceId: AtomicInt = atomic(1)
fun atomicNextSequenceId(): Int {
TODO("使用 SSO ")
val id = sequenceId.getAndAdd(1)
if (id > Short.MAX_VALUE.toInt() * 2) {
sequenceId.value = 0
return atomicNextSequenceId()
}
// return id.toShort()
}
}
} }
private val DECRYPTER_16_ZERO = ByteArray(16) private val DECRYPTER_16_ZERO = ByteArray(16)
internal typealias PacketConsumer = (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit internal typealias PacketConsumer = suspend (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit
internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListOf() { internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListOf() {
...@@ -67,20 +51,25 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO ...@@ -67,20 +51,25 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
fun findPacketFactory(commandId: Int): PacketFactory<*, *> = this.first { it.id.commandName == commandName } fun findPacketFactory(commandId: Int): PacketFactory<*, *> = this.first { it.id.commandName == commandName }
suspend inline fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) = // do not inline. Exceptions thrown will not be reported correctly
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
rawInput.debugPrintIfFail("Incoming packet") { rawInput.debugPrintIfFail("Incoming packet") {
require(rawInput.remaining < Int.MAX_VALUE) { "rawInput is too long" } require(remaining < Int.MAX_VALUE) { "rawInput is too long" }
val expectedLength = readInt() - 4 val expectedLength = readInt() - 4
check(rawInput.remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " } check(remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " }
// login // login
when (val flag1 = readInt()) { when (val flag1 = readInt()) {
0x0A -> when (val flag2 = readByte().toInt()) { 0x0A -> when (val flag2 = readByte().toInt()) {
0x02 -> { 0x02 -> {
val extraData = readIoBuffer(readInt() - 4).debugCopyUse {
this.debugPrint("Extra data")
}
val flag3 = readByte().toInt() val flag3 = readByte().toInt()
check(flag3 == 0) { "Illegal flag3. Expected 0, got $flag3" } check(flag3 == 0) { "Illegal flag3. Expected 0, got $flag3" }
discardExact(readInt() - 4) // uinAccount bot.logger.verbose(readString(readInt() - 4)) // uinAccount
//debugPrint("remaining")
parseLoginSsoPacket(bot, decryptBy(DECRYPTER_16_ZERO), consumer) parseLoginSsoPacket(bot, decryptBy(DECRYPTER_16_ZERO), consumer)
} }
else -> error("Illegal flag2. Expected 0x02, got $flag2") else -> error("Illegal flag2. Expected 0x02, got $flag2")
...@@ -90,7 +79,7 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO ...@@ -90,7 +79,7 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
} }
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
private suspend inline fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) = private suspend fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
rawInput.debugPrintIfFail("Login sso packet") { rawInput.debugPrintIfFail("Login sso packet") {
val commandName: String val commandName: String
val ssoSequenceId: Int val ssoSequenceId: Int
...@@ -136,13 +125,13 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO ...@@ -136,13 +125,13 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
} }
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
internal inline fun <I : Closeable, R> I.withUse(block: I.() -> R): R { internal inline fun <I : IoBuffer, R> I.withUse(block: I.() -> R): R {
contract { contract {
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE) callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
} }
return try { return try {
block(this) block(this)
} finally { } finally {
close() this.release(IoBuffer.Pool)
} }
} }
\ No newline at end of file
...@@ -37,7 +37,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: String) { ...@@ -37,7 +37,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: String) {
writeInt(Random.nextInt()) writeInt(Random.nextInt())
writeInt(uin.toInt()) writeInt(uin.toInt())
writeTime() writeTime()
writeIP(ip) writeFully(ByteArray(4))
writeShort(0) writeShort(0)
} shouldEqualsTo 20 } shouldEqualsTo 20
} }
...@@ -273,8 +273,8 @@ fun BytePacketBuilder.t109( ...@@ -273,8 +273,8 @@ fun BytePacketBuilder.t109(
) { ) {
writeShort(0x109) writeShort(0x109)
writeShortLVPacket { writeShortLVPacket {
writeFully(androidId) writeFully(md5(androidId))
} } shouldEqualsTo 16
} }
fun BytePacketBuilder.t52d( fun BytePacketBuilder.t52d(
...@@ -283,6 +283,9 @@ fun BytePacketBuilder.t52d( ...@@ -283,6 +283,9 @@ fun BytePacketBuilder.t52d(
writeShort(0x52d) writeShort(0x52d)
writeShortLVPacket { writeShortLVPacket {
writeFully(androidDevInfo) writeFully(androidDevInfo)
// 0A 07 75 6E 6B 6E 6F 77 6E 12 7E 4C 69 6E 75 78 20 76 65 72 73 69 6F 6E 20 34 2E 39 2E 33 31 20 28 62 75 69 6C 64 40 42 75 69 6C 64 32 29 20 28 67 63 63 20 76 65 72 73 69 6F 6E 20 34 2E 39 20 32 30 31 35 30 31 32 33 20 28 70 72 65 72 65 6C 65 61 73 65 29 20 28 47 43 43 29 20 29 20 23 31 20 53 4D 50 20 50 52 45 45 4D 50 54 20 54 68 75 20 44 65 63 20 31 32 20 31 35 3A 33 30 3A 35 35 20 49 53 54 20 32 30 31 39 1A 03 52 45 4C 22 03 33 32 37 2A 41 4F 6E 65 50 6C 75 73 2F 4F 6E 65 50 6C 75 73 35 2F 4F 6E 65 50 6C 75 73 35 3A 37 2E 31 2E 31 2F 4E 4D 46 32 36 58 2F 31 30 31 37 31 36 31 37 3A 75 73 65 72 2F 72 65 6C 65 61 73 65 2D 6B 65 79 73 32 24 36 63 39 39 37 36 33 66 2D 66 62 34 32 2D 34 38 38 31 2D 62 37 32 65 2D 63 37 61 61 38 61 36 63 31 63 61 34 3A 10 65 38 63 37 30 35 34 64 30 32 66 33 36 33 64 30 42 0A 6E 6F 20 6D 65 73 73 61 67 65 4A 03 33 32 37
} }
} }
...@@ -301,7 +304,6 @@ fun BytePacketBuilder.t124( ...@@ -301,7 +304,6 @@ fun BytePacketBuilder.t124(
writeShort(networkType.value.toShort()) writeShort(networkType.value.toShort())
writeShortLVByteArrayLimitedLength(simInfo, 16) writeShortLVByteArrayLimitedLength(simInfo, 16)
writeShortLVByteArrayLimitedLength(unknown, 32) writeShortLVByteArrayLimitedLength(unknown, 32)
writeShort(0)
writeShortLVByteArrayLimitedLength(apn, 16) writeShortLVByteArrayLimitedLength(apn, 16)
} }
} }
...@@ -385,7 +387,7 @@ fun BytePacketBuilder.t147( ...@@ -385,7 +387,7 @@ fun BytePacketBuilder.t147(
) { ) {
writeShort(0x147) writeShort(0x147)
writeShortLVPacket { writeShortLVPacket {
writeLong(appId) writeInt(appId.toInt())
writeShortLVByteArrayLimitedLength(apkVersionName, 32) writeShortLVByteArrayLimitedLength(apkVersionName, 32)
writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32) writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
} }
...@@ -516,16 +518,18 @@ fun BytePacketBuilder.t188( ...@@ -516,16 +518,18 @@ fun BytePacketBuilder.t188(
writeShort(0x188) writeShort(0x188)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(androidId)) writeFully(md5(androidId))
} } shouldEqualsTo 16
} }
fun BytePacketBuilder.t194( fun BytePacketBuilder.t194(
imsiMd5: ByteArray imsiMd5: ByteArray
) { ) {
imsiMd5 requireSize 16
writeShort(0x194) writeShort(0x194)
writeShortLVPacket { writeShortLVPacket {
writeFully(imsiMd5) writeFully(imsiMd5)
} } shouldEqualsTo 16
} }
fun BytePacketBuilder.t191( fun BytePacketBuilder.t191(
...@@ -572,7 +576,7 @@ fun BytePacketBuilder.t177( ...@@ -572,7 +576,7 @@ fun BytePacketBuilder.t177(
writeByte(1) writeByte(1)
writeInt(unknown1.toInt()) writeInt(unknown1.toInt())
writeShortLVString(unknown2) writeShortLVString(unknown2)
} } shouldEqualsTo 0x11
} }
fun BytePacketBuilder.t516( // 1302 fun BytePacketBuilder.t516( // 1302
...@@ -581,7 +585,7 @@ fun BytePacketBuilder.t516( // 1302 ...@@ -581,7 +585,7 @@ fun BytePacketBuilder.t516( // 1302
writeShort(0x516) writeShort(0x516)
writeShortLVPacket { writeShortLVPacket {
writeInt(sourceType) writeInt(sourceType)
} } shouldEqualsTo 4
} }
fun BytePacketBuilder.t521( // 1313 fun BytePacketBuilder.t521( // 1313
...@@ -592,7 +596,7 @@ fun BytePacketBuilder.t521( // 1313 ...@@ -592,7 +596,7 @@ fun BytePacketBuilder.t521( // 1313
writeShortLVPacket { writeShortLVPacket {
writeInt(productType) writeInt(productType)
writeShort(unknown) writeShort(unknown)
} } shouldEqualsTo 6
} }
fun BytePacketBuilder.t536( // 1334 fun BytePacketBuilder.t536( // 1334
...@@ -626,8 +630,8 @@ fun BytePacketBuilder.t318( ...@@ -626,8 +630,8 @@ fun BytePacketBuilder.t318(
private fun Boolean.toByte(): Byte = if (this) 1 else 0 private fun Boolean.toByte(): Byte = if (this) 1 else 0
private fun Boolean.toInt(): Int = if (this) 1 else 0 private fun Boolean.toInt(): Int = if (this) 1 else 0
private infix fun Int.shouldEqualsTo(int: Int) = require(this == int) { "Required $int, but found $this" } private infix fun Int.shouldEqualsTo(int: Int) = check(this == int) { "Required $int, but found $this" }
private fun ByteArray.requireSize(exactSize: Int) = require(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" } private infix fun ByteArray.requireSize(exactSize: Int) = check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
fun randomAndroidId(): String = buildString(15) { fun randomAndroidId(): String = buildString(15) {
repeat(15) { append(Random.nextInt(10)) } repeat(15) { append(Random.nextInt(10)) }
......
...@@ -31,9 +31,10 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log ...@@ -31,9 +31,10 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
operator fun invoke( operator fun invoke(
client: QQAndroidClient client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, subAppId) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, subAppId) { sequenceId ->
writeOicqRequestPacket(client, EncryptMethodECDH135(client.ecdh), id) { writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), id) {
writeShort(9) // subCommand writeShort(9) // subCommand
writeShort(LoginType.PASSWORD.value.toShort()) writeShort(0x17)
//writeShort(LoginType.PASSWORD.value.toShort())
t18(appId, client.appClientVersion, client.account.id) t18(appId, client.appClientVersion, client.account.id)
t1(client.account.id, client.device.ipAddress) t1(client.account.id, client.device.ipAddress)
...@@ -91,6 +92,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log ...@@ -91,6 +92,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
tgtgtKey = client.tgtgtKey tgtgtKey = client.tgtgtKey
) )
//this.build().debugPrint("傻逼")
t145(client.device.guid) t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5) t147(appId, client.apkVersionName, client.apkSignatureMd5)
...@@ -156,6 +158,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log ...@@ -156,6 +158,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
writeByte(0) // data count writeByte(0) // data count
}.readBytes()) }.readBytes())
}) })
// this.build().debugPrint("傻逼")
// ignored t318 because not logging in by QR // ignored t318 because not logging in by QR
} }
......
package net.mamoe.mirai.utils.io
import io.ktor.network.sockets.Socket
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.io.readAvailable
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 多平台适配的 TCP Socket.
*/
@MiraiInternalAPI
actual class PlatformSocket : Closeable {
@UseExperimental(KtorExperimentalAPI::class)
lateinit var channel: Socket
@UseExperimental(KtorExperimentalAPI::class)
actual val isOpen: Boolean
get() = channel.socketContext.isActive
override fun close() = channel.dispose()
@PublishedApi
internal val writeChannel = channel.openWriteChannel(true)
@PublishedApi
internal val readChannel = channel.openReadChannel()
/**
* @throws SendPacketInternalException
*/
actual suspend inline fun send(packet: ByteReadPacket) {
writeChannel.writePacket(packet)
}
/**
* @throws ReadPacketInternalException
*/
actual suspend inline fun read(): ByteReadPacket {
// do not use readChannel.readRemaining() !!! this function never returns
ByteArrayPool.useInstance { buffer ->
val count = readChannel.readAvailable(buffer)
return buffer.toReadPacket(0, count)
}
}
@UseExperimental(KtorExperimentalAPI::class)
actual suspend fun connect(serverHost: String, serverPort: Int) {
channel = aSocket(io.ktor.network.selector.ActorSelectorManager(kotlinx.coroutines.Dispatchers.IO)).tcp().connect(serverHost, serverPort)
}
}
\ No newline at end of file
...@@ -94,17 +94,17 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain ...@@ -94,17 +94,17 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain
// region ByteReadPacket extension // region ByteReadPacket extension
fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = key.size - offset): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) } fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
fun ByteReadPacket.decryptBy(key: IoBuffer, offset: Int = 0, length: Int = key.readRemaining - offset): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) } fun ByteReadPacket.decryptBy(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = key.size - offset, consumer: (ByteArray) -> R): R = inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
readFully(it, offset, length) readFully(it, offset, length)
consumer(it.decryptBy(key, length)) consumer(it.decryptBy(key, length))
}.also { close() } }.also { close() }
inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = key.readRemaining - offset, consumer: (ByteArray) -> R): R = inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
readFully(it, offset, length) readFully(it, offset, length)
consumer(it.decryptBy(key, length)) consumer(it.decryptBy(key, length))
......
package net.mamoe.mirai.utils.io
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.errors.IOException
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 多平台适配的 TCP Socket.
*/
@MiraiInternalAPI
expect class PlatformSocket() : Closeable {
suspend fun connect(serverHost: String, serverPort: Int)
/**
* @throws SendPacketInternalException
*/
suspend inline fun send(packet: ByteReadPacket)
/**
* @throws ReadPacketInternalException
*/
suspend inline fun read(): ByteReadPacket
val isOpen: Boolean
}
\ No newline at end of file
package net.mamoe.mirai.utils.cryptor package net.mamoe.mirai.utils.cryptor
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.* import java.security.*
import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
import javax.crypto.KeyAgreement import javax.crypto.KeyAgreement
...@@ -15,7 +17,7 @@ actual class ECDHKeyPair( ...@@ -15,7 +17,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public actual val publicKey: ECDHPublicKey get() = delegate.public
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, publicKey) actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
} }
@Suppress("FunctionName") @Suppress("FunctionName")
...@@ -23,8 +25,12 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair()) ...@@ -23,8 +25,12 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object { actual companion object {
init {
Security.addProvider(BouncyCastleProvider())
}
actual fun generateKeyPair(): ECDHKeyPair { actual fun generateKeyPair(): ECDHKeyPair {
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair()) return ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair())
} }
actual fun calculateShareKey( actual fun calculateShareKey(
......
package net.mamoe.mirai.utils.io
import io.ktor.network.sockets.Socket
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.readAvailable
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 多平台适配的 TCP Socket.
*/
@MiraiInternalAPI
actual class PlatformSocket : Closeable {
@UseExperimental(KtorExperimentalAPI::class)
lateinit var socket: Socket
@UseExperimental(KtorExperimentalAPI::class)
actual val isOpen: Boolean
get() = socket.socketContext.isActive
override fun close() = socket.dispose()
@PublishedApi
internal lateinit var writeChannel: ByteWriteChannel
@PublishedApi
internal lateinit var readChannel: ByteReadChannel
/**
* @throws SendPacketInternalException
*/
actual suspend inline fun send(packet: ByteReadPacket) {
writeChannel.writePacket(packet)
}
/**
* @throws ReadPacketInternalException
*/
actual suspend inline fun read(): ByteReadPacket {
// do not use readChannel.readRemaining() !!! this function never returns
ByteArrayPool.useInstance { buffer ->
val count = readChannel.readAvailable(buffer)
return buffer.toReadPacket(0, count)
}
}
@UseExperimental(KtorExperimentalAPI::class)
actual suspend fun connect(serverHost: String, serverPort: Int) {
socket = aSocket(io.ktor.network.selector.ActorSelectorManager(kotlinx.coroutines.Dispatchers.IO)).tcp().connect(serverHost, serverPort)
writeChannel = socket.openWriteChannel(true)
readChannel = socket.openReadChannel()
}
}
\ No newline at end of file
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
class TypeConversionTest { class TypeConversionTest {
...@@ -13,6 +17,7 @@ class TypeConversionTest { ...@@ -13,6 +17,7 @@ class TypeConversionTest {
assertEquals("7F", byteArrayOf(0x7F).toUHexString()) assertEquals("7F", byteArrayOf(0x7F).toUHexString())
assertEquals("FF", ubyteArrayOf(0xffu).toUHexString()) assertEquals("FF", ubyteArrayOf(0xffu).toUHexString())
assertEquals("7F", ubyteArrayOf(0x7fu).toUHexString()) assertEquals("7F", ubyteArrayOf(0x7fu).toUHexString())
assertTrue { 1994701021.toByteArray().contentEquals("76 E4 B8 DD".hexToBytes()) }
assertEquals(byteArrayOf(0, 0, 0, 0x01).toUHexString(), 1.toByteArray().toUHexString()) assertEquals(byteArrayOf(0, 0, 0, 0x01).toUHexString(), 1.toByteArray().toUHexString())
assertEquals(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString()) assertEquals(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString())
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment