diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 3c625ff98..7d0f37f91 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -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.MiraiInternalAPI 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.debugPrint import net.mamoe.mirai.utils.unsafeWeakRef @@ -25,16 +25,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler override val bot: QQAndroidBot by bot.unsafeWeakRef() 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() { + channel = PlatformSocket() + channel.connect("113.96.13.208", 8080) launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } + println("Sending login") LoginPacket.SubCommand9(bot.client).sendAndExpect() - println("Login sent") } - private suspend inline fun processReceive() { + private suspend fun processReceive() { while (channel.isOpen) { val rawInput = try { channel.read() @@ -52,21 +54,19 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler } launch(CoroutineName("Incoming Packet handler")) { - try { - rawInput.debugPrint("Received") - } catch (e: Exception) { - bot.logger.error(e) - } - } - - rawInput.use { - KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int -> - if (PacketReceivedEvent(packet).broadcast().cancelled) { - return + rawInput.debugPrint("Received").use { + if (it.remaining == 0L) { + bot.logger.error("Empty packet received. Consider if bad packet was sent.") + return@launch } - packetListeners.forEach { listener -> - if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) { - listener.complete(packet) + KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int -> + if (PacketReceivedEvent(packet).broadcast().cancelled) { + return@parseIncomingPacket + } + packetListeners.forEach { listener -> + if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) { + listener.complete(packet) + } } } } @@ -77,7 +77,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler suspend fun OutgoingPacket.sendAndExpect(): E { val handler = PacketListener(packetId = packetId, sequenceId = sequenceId) 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") return handler.await() as E } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index afeb13747..a4deec7f6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -4,7 +4,10 @@ import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.atomic import kotlinx.io.core.toByteArray 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.cryptor.ECDH import net.mamoe.mirai.utils.io.hexToBytes @@ -31,7 +34,7 @@ internal open class QQAndroidClient( val ecdh: ECDH = ECDH(), 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 mainSigMap: Int = 16724722 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt index 6844777a5..7d5976bd2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt @@ -11,7 +11,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.cryptor.DecrypterByteArray import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.encryptAndWrite -import net.mamoe.mirai.utils.cryptor.encryptBy import net.mamoe.mirai.utils.io.* /** @@ -47,6 +46,7 @@ private val EMPTY_BYTE_ARRAY = ByteArray(0) * * byte[] body encrypted by 16 zero */ +@UseExperimental(MiraiInternalAPI::class) internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket( client: QQAndroidClient, subAppId: Long, @@ -54,30 +54,33 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket( name: String? = null, id: PacketId = this.id, ssoExtraData: ByteReadPacket = BRP_STUB, - sequenceId: Int = PacketFactory.atomicNextSequenceId(), body: BytePacketBuilder.(sequenceId: Int) -> Unit -): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket { - writeIntLVPacket(lengthOffset = { it + 4 }) { - writeInt(0x00_00_00_0A) - writeByte(0x02) - extraData.let { - writeInt(it.size + 4) - writeFully(it) - } - writeByte(0x00) +): OutgoingPacket { + val sequenceId: Int = client.nextSsoSequenceId() - client.account.id.toString().let { - writeInt(it.length + 4) - writeStringUtf8(it) - } + return OutgoingPacket(name, id, sequenceId, buildPacket { + writeIntLVPacket(lengthOffset = { it + 4 }) { + writeInt(0x00_00_00_0A) + writeByte(0x02) + extraData.let { + writeInt(it.size + 4) + writeFully(it) + } + writeByte(0x00) - encryptAndWrite(KEY_16_ZEROS) { - writeLoginSsoPacket(client, subAppId, id, ssoExtraData, sequenceId) { - body(sequenceId) + client.account.id.toString().let { + writeInt(it.length + 4) + writeStringUtf8(it) + } + + encryptAndWrite(KEY_16_ZEROS) { + writeLoginSsoPacket(client, subAppId, id, ssoExtraData, sequenceId) { + body(sequenceId) + } } } - } -}) + }) +} private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY) @@ -141,7 +144,7 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket( writeInt(4) client.device.ksid.let { - writeInt(it.length + 4) + writeShort((it.length + 2).toShort()) writeStringUtf8(it) } @@ -190,7 +193,7 @@ internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket( interface EncryptMethod { val id: Int - fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) + fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket } internal interface EncryptMethodSessionKey : EncryptMethod { @@ -208,14 +211,14 @@ internal interface EncryptMethodSessionKey : EncryptMethod { * 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" } writeByte(1) // const writeByte(if (currentLoginState == 2) 3 else 2) writeFully(sessionKey) writeShort(258) // const writeShort(0) // const, length of publicKey - body.encryptBy(sessionKey) { encrypted -> writeFully(encrypted) } + encryptAndWrite(sessionKey, body) } } @@ -248,13 +251,22 @@ internal interface EncryptMethodECDH : EncryptMethod { * byte[] [ECDH.publicKey] * 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 - writeFully(keyPair.privateKey.getEncoded()) + writeFully(ByteArray(16)) 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,36 +290,34 @@ internal interface EncryptMethodECDH : EncryptMethod { * byte 3 // tail */ @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) -internal inline fun BytePacketBuilder.writeOicqRequestPacket( +internal fun BytePacketBuilder.writeOicqRequestPacket( client: QQAndroidClient, encryptMethod: EncryptMethod, packetId: PacketId, bodyBlock: BytePacketBuilder.() -> Unit ) { - val body = encryptMethod.run { - buildPacket { encryptAndWrite(buildPacket { bodyBlock() }) } - } - writeIntLVPacket(lengthOffset = { it + 4 }) { - // Head - writeByte(0x02) // head - writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm - writeShort(client.protocolVersion) - writeShort(1) // const?? - writeShort(packetId.commandId.toShort()) - writeQQ(client.account.id) - writeByte(3) // originally const - writeByte(encryptMethod.id.toByte()) - writeByte(0) // const8_always_0 - writeInt(2) // originally const - writeInt(client.appClientVersion) - writeInt(0) // constp_always_0 + val body = encryptMethod.makeBody(bodyBlock) + // writeIntLVPacket(lengthOffset = { it + 4 }) { + // Head + writeByte(0x02) // head + writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm + writeShort(client.protocolVersion) + writeShort(packetId.commandId.toShort()) + writeShort(1) // const?? + writeQQ(client.account.id) + writeByte(3) // originally const + writeByte(encryptMethod.id.toByte()) + writeByte(0) // const8_always_0 + writeInt(2) // originally const + writeInt(client.appClientVersion) + writeInt(0) // constp_always_0 - // Body - writePacket(body) + // Body + writePacket(body) - // Tail - writeByte(0x03) // tail - } + // Tail + writeByte(0x03) // tail + // } } /* 00 00 01 64 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index e820725bd..b0abb665b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -1,9 +1,7 @@ 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.Closeable +import kotlinx.io.core.IoBuffer import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import net.mamoe.mirai.data.Packet @@ -41,25 +39,11 @@ internal abstract class PacketFactory Short.MAX_VALUE.toInt() * 2) { - sequenceId.value = 0 - return atomicNextSequenceId() - } - // return id.toShort() - } - } } 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> by mutableListOf() { @@ -67,20 +51,25 @@ internal object KnownPacketFactories : List> by mutableListO 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") { - require(rawInput.remaining < Int.MAX_VALUE) { "rawInput is too long" } + require(remaining < Int.MAX_VALUE) { "rawInput is too long" } 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 when (val flag1 = readInt()) { 0x0A -> when (val flag2 = readByte().toInt()) { 0x02 -> { + val extraData = readIoBuffer(readInt() - 4).debugCopyUse { + this.debugPrint("Extra data") + } val flag3 = readByte().toInt() 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) } else -> error("Illegal flag2. Expected 0x02, got $flag2") @@ -90,7 +79,7 @@ internal object KnownPacketFactories : List> by mutableListO } @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") { val commandName: String val ssoSequenceId: Int @@ -136,13 +125,13 @@ internal object KnownPacketFactories : List> by mutableListO } @UseExperimental(ExperimentalContracts::class) -internal inline fun I.withUse(block: I.() -> R): R { +internal inline fun I.withUse(block: I.() -> R): R { contract { callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE) } return try { block(this) } finally { - close() + this.release(IoBuffer.Pool) } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index 594ef93da..a2e59737f 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -37,7 +37,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: String) { writeInt(Random.nextInt()) writeInt(uin.toInt()) writeTime() - writeIP(ip) + writeFully(ByteArray(4)) writeShort(0) } shouldEqualsTo 20 } @@ -273,8 +273,8 @@ fun BytePacketBuilder.t109( ) { writeShort(0x109) writeShortLVPacket { - writeFully(androidId) - } + writeFully(md5(androidId)) + } shouldEqualsTo 16 } fun BytePacketBuilder.t52d( @@ -283,6 +283,9 @@ fun BytePacketBuilder.t52d( writeShort(0x52d) writeShortLVPacket { 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( writeShort(networkType.value.toShort()) writeShortLVByteArrayLimitedLength(simInfo, 16) writeShortLVByteArrayLimitedLength(unknown, 32) - writeShort(0) writeShortLVByteArrayLimitedLength(apn, 16) } } @@ -385,7 +387,7 @@ fun BytePacketBuilder.t147( ) { writeShort(0x147) writeShortLVPacket { - writeLong(appId) + writeInt(appId.toInt()) writeShortLVByteArrayLimitedLength(apkVersionName, 32) writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32) } @@ -516,16 +518,18 @@ fun BytePacketBuilder.t188( writeShort(0x188) writeShortLVPacket { writeFully(md5(androidId)) - } + } shouldEqualsTo 16 } fun BytePacketBuilder.t194( imsiMd5: ByteArray ) { + imsiMd5 requireSize 16 + writeShort(0x194) writeShortLVPacket { writeFully(imsiMd5) - } + } shouldEqualsTo 16 } fun BytePacketBuilder.t191( @@ -572,7 +576,7 @@ fun BytePacketBuilder.t177( writeByte(1) writeInt(unknown1.toInt()) writeShortLVString(unknown2) - } + } shouldEqualsTo 0x11 } fun BytePacketBuilder.t516( // 1302 @@ -581,7 +585,7 @@ fun BytePacketBuilder.t516( // 1302 writeShort(0x516) writeShortLVPacket { writeInt(sourceType) - } + } shouldEqualsTo 4 } fun BytePacketBuilder.t521( // 1313 @@ -592,7 +596,7 @@ fun BytePacketBuilder.t521( // 1313 writeShortLVPacket { writeInt(productType) writeShort(unknown) - } + } shouldEqualsTo 6 } fun BytePacketBuilder.t536( // 1334 @@ -626,8 +630,8 @@ fun BytePacketBuilder.t318( private fun Boolean.toByte(): Byte = 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 fun ByteArray.requireSize(exactSize: Int) = require(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" } +private infix fun Int.shouldEqualsTo(int: Int) = check(this == int) { "Required $int, but found $this" } +private infix fun ByteArray.requireSize(exactSize: Int) = check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" } fun randomAndroidId(): String = buildString(15) { repeat(15) { append(Random.nextInt(10)) } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt index 30a2c1919..d38c10b62 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt @@ -31,9 +31,10 @@ internal object LoginPacket : PacketFactory - writeOicqRequestPacket(client, EncryptMethodECDH135(client.ecdh), id) { + writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), id) { writeShort(9) // subCommand - writeShort(LoginType.PASSWORD.value.toShort()) + writeShort(0x17) + //writeShort(LoginType.PASSWORD.value.toShort()) t18(appId, client.appClientVersion, client.account.id) t1(client.account.id, client.device.ipAddress) @@ -91,6 +92,7 @@ internal object LoginPacket : PacketFactory + 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 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt index b921fcfbf..36c7fdbb7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt @@ -94,17 +94,17 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain // 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 ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = key.size - offset, consumer: (ByteArray) -> R): R = +inline fun ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R = ByteArrayPool.useInstance { readFully(it, offset, length) consumer(it.decryptBy(key, length)) }.also { close() } -inline fun ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = key.readRemaining - offset, consumer: (ByteArray) -> R): R = +inline fun ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R = ByteArrayPool.useInstance { readFully(it, offset, length) consumer(it.decryptBy(key, length)) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt new file mode 100644 index 000000000..ed9a245c2 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt @@ -0,0 +1,26 @@ +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 diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt index a0e315c89..cffda0016 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt @@ -1,7 +1,9 @@ package net.mamoe.mirai.utils.cryptor import net.mamoe.mirai.utils.md5 +import org.bouncycastle.jce.provider.BouncyCastleProvider import java.security.* +import java.security.spec.ECGenParameterSpec import java.security.spec.X509EncodedKeySpec import javax.crypto.KeyAgreement @@ -15,7 +17,7 @@ actual class ECDHKeyPair( actual val privateKey: ECDHPrivateKey get() = delegate.private 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") @@ -23,8 +25,12 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair()) actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual companion object { + init { + Security.addProvider(BouncyCastleProvider()) + } + actual fun generateKeyPair(): ECDHKeyPair { - return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair()) + return ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair()) } actual fun calculateShareKey( diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt new file mode 100644 index 000000000..1889c8bba --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt @@ -0,0 +1,59 @@ +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 diff --git a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/TypeConversionTest.kt similarity index 69% rename from mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt rename to mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/TypeConversionTest.kt index 929c5885c..7dd132a16 100644 --- a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/TypeConversionTest.kt @@ -1,7 +1,11 @@ -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.assertEquals +import kotlin.test.assertTrue class TypeConversionTest { @@ -13,6 +17,7 @@ class TypeConversionTest { assertEquals("7F", byteArrayOf(0x7F).toUHexString()) assertEquals("FF", ubyteArrayOf(0xffu).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(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString()) }