Commit 71d5dda2 authored by Him188's avatar Him188

ByteArrayPool

parent b9129576
......@@ -34,4 +34,7 @@ mirai.iml
/test
.gradle/
\ No newline at end of file
.gradle/
local.properties
\ No newline at end of file
......@@ -4,11 +4,12 @@ buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
google()
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
}
......@@ -20,8 +21,8 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
google()
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
}
}
\ No newline at end of file
apply plugin: 'kotlinx-atomicfu'
apply plugin: "kotlin-multiplatform"
kotlin {
targets {
fromPreset(presets.jvm, "jvm")
//fromPreset(presets.jvm, "android")
//fromPreset(presets.mingwX64, "mingwX64")
}
jvm{
......@@ -35,6 +33,7 @@ kotlin {
implementation "com.soywiz.korlibs.klock:klock:$klock_version"
api group: 'io.ktor', name: 'ktor-client-core', version: ktor_version
api group: 'io.ktor', name: 'ktor-network', version: ktor_version
//api group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version
//api group: 'io.ktor', name: 'ktor-client', version: ktor_version
api group: 'io.ktor', name: 'ktor-http', version: ktor_version
......@@ -87,6 +86,28 @@ kotlin {
apply plugin: 'java'
}
androidMain{
dependencies{
api 'com.google.android:android:4.1.1.4'
api 'com.android.support:support-annotations:26.1.0'
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
api group: 'org.jetbrains.kotlinx', name: 'atomicfu', version: atomicfu_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version
// api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io-jvm', version: kotlinxio_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-io', version: coroutinesio_version
api group: 'io.ktor', name: 'ktor-http-cio', version: ktor_version
api group: 'io.ktor', name: 'ktor-http', version: ktor_version
api group: 'io.ktor', name: 'ktor-client-core-jvm', version: ktor_version
api group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version
}
}
all {
languageSettings.enableLanguageFeature("InlineClasses")
}
......
plugins {
id("kotlinx-atomicfu")
kotlin("multiplatform")
//id("com.android.library")
//id("kotlin-android-extensions")
}
val kotlinVersion = rootProject.ext["kotlin_version"].toString()
val atomicFuVersion = rootProject.ext["atomicfu_version"].toString()
val coroutinesVersion = rootProject.ext["coroutines_version"].toString()
val kotlinXIoVersion = rootProject.ext["kotlinxio_version"].toString()
val coroutinesIoVersion = rootProject.ext["coroutinesio_version"].toString()
val klockVersion = rootProject.ext["klock_version"].toString()
val ktorVersion = rootProject.ext["ktor_version"].toString()
/*
//apply()
android {
compileSdkVersion(29)
buildToolsVersion("29.0.2")
defaultConfig {
applicationId = "com.youngfeng.kotlindsl"
minSdkVersion(15)
targetSdkVersion(27)
versionCode = 1
versionName = "1.0"
// testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
//proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
sourceSets.forEach {
println(it)
// it.languageSettings.enableLanguageFeature("InlineClasses")
}
}
*/
kotlin {
// android("android")
jvm("jvm")
sourceSets["commonMain"].apply {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("com.soywiz.korlibs.klock:klock:$klockVersion")
api("io.ktor:ktor-client-core:$ktorVersion")
api("io.ktor:ktor-network:$ktorVersion")
api("io.ktor:ktor-http:$ktorVersion")
}
}
/*
sourceSets["androidMain"].apply {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("io.ktor:ktor-http-cio:$ktorVersion")
implementation("io.ktor:ktor-http:$ktorVersion")
implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
}
languageSettings.enableLanguageFeature("InlineClasses")
}*/
sourceSets["jvmMain"].apply {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("io.ktor:ktor-http-cio:$ktorVersion")
implementation("io.ktor:ktor-http:$ktorVersion")
implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
}
}
sourceSets.forEach {
it.languageSettings.enableLanguageFeature("InlineClasses")
it.dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
implementation("org.jetbrains.kotlinx:kotlinx-io:$kotlinXIoVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-io:$coroutinesIoVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
}
......@@ -183,7 +183,6 @@ class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
/**
* 如果消息的前缀是 [prefix], 就执行 [onEvent]
* @param
*/
suspend fun startsWith(prefix: String, removePrefix: Boolean = false, onEvent: @MessageDsl suspend T.(String) -> Unit) =
content({ it.startsWith(prefix) }) {
......@@ -258,4 +257,12 @@ class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
*/
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
@DslMarker
internal annotation class MessageDsl
\ No newline at end of file
internal annotation class MessageDsl
fun main() {
println('B')
println("\u7154225")
println('B' - 'b')
}
\ No newline at end of file
......@@ -5,7 +5,6 @@ package net.mamoe.mirai.message.internal
import kotlinx.io.core.*
import net.mamoe.mirai.message.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.toUHexString
internal fun IoBuffer.parseMessageFace(): Face {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
......@@ -105,7 +104,7 @@ internal fun ByteReadPacket.readMessage(): Message? {
//后面似乎还有一节?
//discardExact(7)//02 00 04 00 00 00 23
return PlainText(value.toUHexString())
//return PlainText(value.toUHexString())
}
0x0E -> {
......
......@@ -23,8 +23,11 @@ import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.BotNetworkConfiguration
import net.mamoe.mirai.utils.OnlineStatus
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.log
import net.mamoe.mirai.utils.solveCaptcha
import kotlin.coroutines.CoroutineContext
/**
......@@ -35,7 +38,9 @@ import kotlin.coroutines.CoroutineContext
internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
override val coroutineContext: CoroutineContext =
Dispatchers.Default + CoroutineExceptionHandler { _, e -> bot.logger.log(e) } + SupervisorJob()
Dispatchers.Default + CoroutineExceptionHandler { _, e ->
bot.logger.log(e)
} + SupervisorJob()
override lateinit var socket: BotSocketAdapter
......@@ -56,13 +61,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override suspend fun login(configuration: BotNetworkConfiguration): LoginResult = withContext(this.coroutineContext) {
TIMProtocol.SERVER_IP.forEach { ip ->
bot.logger.logInfo("Connecting server $ip")
bot.logger.logPurple("Connecting server $ip")
socket = BotSocketAdapter(ip, configuration)
loginResult = CompletableDeferred()
socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it }
println()
bot.logger.logPurple("Timeout. Retrying next server")
socket.close()
......@@ -124,6 +130,9 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
try {
channel.read(buffer)// JVM: withContext(IO)
} catch (e: ClosedChannelException) {
close()
return
} catch (e: ReadPacketInternalException) {
bot.logger.logError("Socket channel read failed: ${e.message}")
continue
......@@ -133,6 +142,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
continue
} finally {
if (!buffer.canRead() || buffer.readRemaining == 0) {//size==0
//bot.logger.logDebug("processReceive: Buffer cannot be read")
buffer.release(IoBuffer.Pool)
continue
}// sometimes exceptions are thrown without this `if` clause
......@@ -153,7 +163,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
}
}
internal suspend fun resendTouch(): LoginResult = coroutineScope {
internal suspend fun resendTouch(): LoginResult /* = coroutineScope */ {
if (::loginHandler.isInitialized) loginHandler.close()
loginHandler = LoginHandler(configuration)
......@@ -168,7 +178,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
}
sendPacket(TouchPacket(bot.qqAccount, serverIp))
return@coroutineScope loginResult.await()
return loginResult.await()
}
private suspend inline fun <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
......@@ -183,7 +193,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
return receiving
}
override suspend fun distributePacket(packet: ServerPacket) = coroutineScope {
override suspend fun distributePacket(packet: ServerPacket) {
try {
packet.decode()
} catch (e: Exception) {
......@@ -213,7 +223,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
}
if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled) {
return@coroutineScope
return
}
// They should be called in sequence because packet is lock-free
......@@ -267,11 +277,12 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
return@withContext
}
packet.packet.use { build ->
packet.buildAndUse { build ->
val buffer = IoBuffer.Pool.borrow()
try {
build.readAvailable(buffer)
channel.send(buffer)//JVM: withContext(IO)
val shouldBeSent = buffer.readRemaining
check(channel.send(buffer) == shouldBeSent) { "Buffer is not entirely sent. Required sent length=$shouldBeSent, but after channel.send, buffer remains ${buffer.readBytes().toUHexString()}" }//JVM: withContext(IO)
} catch (e: SendPacketInternalException) {
bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}")
bot.reinitializeNetworkHandler(configuration, e)
......
......@@ -30,33 +30,22 @@ abstract class OutgoingPacket : Packet(), Closeable {
internal fun atomicNextSequenceId() = sequenceIdInternal.getAndIncrement().toUShort()
}
/**
* 务必 [ByteReadPacket.close] 或 [close] 或使用 [Closeable.use]
*/
var packet: ByteReadPacket = Uninitialized
get() {
if (field === Uninitialized) build()
return field
}
private set
inline fun buildAndUse(block: (ByteReadPacket) -> Unit) {
buildPacket().use(block)
}
private fun build(): ByteReadPacket {
packet = buildPacket {
writeHex(TIMProtocol.head)
writeHex(TIMProtocol.ver)
writePacketId()
encode(this)
writeHex(TIMProtocol.tail)
}
return packet
@PublishedApi
internal fun buildPacket(): ByteReadPacket = buildPacket {
writeHex(TIMProtocol.head)
writeHex(TIMProtocol.ver)
writePacketId()
encode(this)
writeHex(TIMProtocol.tail)
}
override fun toString(): String = packetToString()
override fun close() {
if (this.packet !== Uninitialized) {
this.packet.close()
}
}
private fun BytePacketBuilder.writePacketId() {
......
......@@ -2,7 +2,7 @@
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.utils.toUHexString
import net.mamoe.mirai.utils.io.toUHexString
/**
* 数据包.
......
......@@ -6,11 +6,11 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.cutTail
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.parseServerPacket
import net.mamoe.mirai.utils.io.readRemainingBytes
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.properties.Delegates
......@@ -41,16 +41,32 @@ fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
return this
}
fun ServerPacket.decryptBy(key: ByteArray): ByteReadPacket = ByteReadPacket(decryptAsByteArray(key))
fun ServerPacket.decryptBy(key: IoBuffer): ByteReadPacket = ByteReadPacket(decryptAsByteArray(key))
fun ServerPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ServerPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ServerPacket.decryptBy(keyHex: String): ByteReadPacket = this.decryptBy(keyHex.hexToBytes())
fun ServerPacket.decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket = TEA.decrypt(this.decryptAsByteArray(key1), key2).toReadPacket()
fun ServerPacket.decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket =
this.decryptAsByteArray(key1) { data ->
data.decryptBy(key2).toReadPacket()
}
fun ServerPacket.decryptBy(key1: String, key2: ByteArray): ByteReadPacket = this.decryptBy(key1.hexToBytes(), key2)
fun ServerPacket.decryptBy(key1: String, key2: IoBuffer): ByteReadPacket = this.decryptBy(key1.hexToBytes(), key2.readBytes())
fun ServerPacket.decryptBy(key1: ByteArray, key2: String): ByteReadPacket = this.decryptBy(key1, key2.hexToBytes())
fun ServerPacket.decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket = this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
fun ServerPacket.decryptAsByteArray(key: ByteArray): ByteArray = TEA.decrypt(input.readRemainingBytes().cutTail(1), key)
fun ServerPacket.decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes())
fun ServerPacket.decryptAsByteArray(buffer: IoBuffer): ByteArray = this.decryptAsByteArray(buffer.readBytes())
\ No newline at end of file
inline fun <R> ServerPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance {
val length = input.remaining.toInt() - 1
input.readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.also { input.close() }
inline fun <R> ServerPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R = this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
inline fun <R> ServerPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance {
val length = input.remaining.toInt() - 1
input.readFully(it, 0, length)
consumer(it.decryptBy(key, length))
}.also { input.close() }
......@@ -10,7 +10,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.applySequence
import net.mamoe.mirai.network.protocol.tim.packet.decryptBy
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.toByteArray
/**
* 事件的识别 ID. 在 [事件确认包][ServerEventPacket.ResponsePacket] 中被使用.
......
......@@ -84,8 +84,8 @@ class SubmitCaptchaPacket(
*/
@PacketId(0x00_BAu)
class OutgoingCaptchaRefreshPacket(
private val qq: UInt,
private val token0825: ByteArray
private val qq: UInt,
private val token0825: ByteArray
) : OutgoingPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
......@@ -164,14 +164,14 @@ abstract class ServerCaptchaPacket(input: ByteReadPacket) : ServerPacket(input)
@PacketId(0x00_BAu)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerCaptchaPacket {
val data = this.decryptAsByteArray(TIMProtocol.key00BA)
return when (data.size) {
66,
95 -> CaptchaCorrectPacket(data.toReadPacket())
//66 -> ServerCaptchaUnknownPacket(data.toReadPacket())
else -> CaptchaTransmissionResponsePacket(data.toReadPacket())
}.applySequence(sequenceId)
return this.decryptAsByteArray(TIMProtocol.key00BA) { data ->
when (data.size) {
66,
95 -> CaptchaCorrectPacket(data.toReadPacket())
//66 -> ServerCaptchaUnknownPacket(data.toReadPacket())
else -> CaptchaTransmissionResponsePacket(data.toReadPacket())
}.applySequence(sequenceId)
}
}
}
}
......@@ -8,8 +8,7 @@ import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.writeCRC32
......@@ -90,7 +89,7 @@ private fun BytePacketBuilder.writePart1(
this.writeHex(TIMProtocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag
this.writeHex("00 40")//length
this.writeFully(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey))
this.writeFully(TIMProtocol.passwordSubmissionTLV2.hexToBytes().encryptBy(privateKey))
this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2)
this.writeQQ(qq)
......
......@@ -4,12 +4,14 @@ package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.applySequence
import net.mamoe.mirai.network.protocol.tim.packet.decryptBy
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.io.readBoolean
import net.mamoe.mirai.utils.io.readIoBuffer
import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.properties.Delegates
@PacketId(0x08_36u)
......@@ -82,18 +84,20 @@ class LoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePac
discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
discardExact(when (readUByte().toUInt()) {
0x00u -> when (readUByte().toUInt()) {
0x33u -> 28
discardExact(
when (readUByte().toUInt()) {
0x00u -> when (readUByte().toUInt()) {
0x33u -> 28
else -> null
}
0x01u -> when (readUByte().toUInt()) {
0x07u -> 0
0x10u -> 64
else -> null
}
else -> null
}
0x01u -> when (readUByte().toUInt()) {
0x07u -> 0
0x10u -> 64
else -> null
}
else -> null
} ?: error("Unknown length flag"))
} ?: error("Unknown length flag")
)
discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
......@@ -156,6 +160,6 @@ class LoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginRespons
@PacketId(0x08_36u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): LoginResponseCaptchaInitPacket = LoginResponseCaptchaInitPacket(decryptAsByteArray(TIMProtocol.shareKey).toReadPacket()).applySequence(sequenceId)
fun decrypt(): LoginResponseCaptchaInitPacket = LoginResponseCaptchaInitPacket(decryptBy(TIMProtocol.shareKey)).applySequence(sequenceId)
}
}
\ No newline at end of file
......@@ -8,9 +8,7 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.toUHexString
/**
* The packet received when logging in, used to redirect server address
......
......@@ -3,6 +3,7 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.io.getRandomByteArray
private const val GTK_BASE_VALUE: Int = 5381
......
......@@ -12,6 +12,7 @@ import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.message.image
import net.mamoe.mirai.message.sendTo
import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
import net.mamoe.mirai.utils.io.toUHexString
@Suppress("FunctionName")
fun ExternalImage(
......
......@@ -17,7 +17,7 @@ interface MiraiLogger {
*/
companion object : MiraiLogger by DefaultLogger("TOP Level")
var identity: String?
val identity: String?
/**
* 随从. 在 this 中调用所有方法后都应继续往 [follower] 传递调用.
......@@ -74,7 +74,8 @@ interface MiraiLogger {
/**
* 平台基类.
* 实现了 [follower] 的调用传递
* 实现了 [follower] 的调用传递.
* 若要自行实现日志记录, 请优先考虑继承 [PlatformLogger]
*/
abstract class MiraiLoggerPlatformBase : MiraiLogger {
final override var follower: MiraiLogger? = null
......@@ -145,7 +146,7 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
/**
* 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, 如 [Bot], 都会通过这个函数构造日志记录器
*/
var DefaultLogger: (identity: String?) -> PlatformLogger = { PlatformLogger() }
var DefaultLogger: (identity: String?) -> MiraiLogger = { PlatformLogger() }
/**
* 当前平台的默认的日志记录器.
......@@ -153,7 +154,39 @@ var DefaultLogger: (identity: String?) -> PlatformLogger = { PlatformLogger() }
*
* 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
*/
expect class PlatformLogger @JvmOverloads internal constructor(identity: String? = null) : MiraiLoggerPlatformBase
expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = null) : MiraiLoggerPlatformBase
/**
* 不作任何事情的 logger
*/
@Suppress("unused")
object NoLogger : PlatformLogger() {
override val identity: String? = null
override fun log0(e: Throwable) {
}
override fun log0(any: Any?) {
}
override fun logError0(any: Any?) {
}
override fun logDebug0(any: Any?) {
}
override fun logCyan0(any: Any?) {
}
override fun logPurple0(any: Any?) {
}
override fun logGreen0(any: Any?) {
}
override fun logBlue0(any: Any?) {
}
}
/**
* 在顶层日志记录这个异常
......
......@@ -22,6 +22,8 @@ enum class OnlineStatus(
BUSY(0x32u);
// TODO: 2019/10/29 what is 0x20u
companion object {
fun ofId(id: UByte): OnlineStatus? = values().firstOrNull { it.id == id }
}
......
......@@ -12,6 +12,16 @@ internal fun Long.coerceAtLeastOrFail(value: Long): Long {
return this
}
@PublishedApi
internal fun Int.coerceAtMostOrFail(maximumValue: Int): Int =
if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
else this
@PublishedApi
internal fun Long.coerceAtMostOrFail(maximumValue: Long): Long =
if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
else this
/**
* 表示这个参数必须为正数
*/
......
package net.mamoe.mirai.utils
package net.mamoe.mirai.utils.io
import kotlinx.io.pool.DefaultPool
import kotlinx.io.pool.ObjectPool
internal const val DEFAULT_BUFFER_SIZE = 4098
internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 2048
internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 128
internal const val DEFAULT_BYTE_ARRAY_SIZE = 2048
/**
* The default ktor byte buffer pool
*/
val ByteArrayPool: ObjectPool<ByteArray> = ByteBufferPool()
val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl
class ByteBufferPool : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) {
override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BUFFER_SIZE)
private object ByteArrayPoolImpl : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) {
override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE)
override fun clearInstance(instance: ByteArray): ByteArray = instance.apply { map { 0 } }
override fun clearInstance(instance: ByteArray): ByteArray = instance
}
......@@ -33,7 +33,7 @@ fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString
return@joinToString ret
}
fun ByteArray.toReadPacket() = ByteReadPacket(this)
fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size) = ByteReadPacket(this, offset = offset, length = length)
fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t)
......
......@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.*
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toIoBuffer
internal object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug")
......@@ -78,5 +76,8 @@ internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstCons
println()
}
/**
* TODO 这两个方法不应该 MPP
*/
expect fun printCompareHex(hex1s: String, hex2s: String): String
expect fun String.printColorize(ignoreUntilFirstConst: Boolean = false): String
......@@ -84,7 +84,7 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
0x00_58u -> ResponsePacket.Encrypted<HeartbeatPacket.Response>(this)
0x03_88u -> ResponsePacket.Encrypted<GroupImageIdRequestPacket.Response>(this)
0x03_52u -> ResponsePacket.Encrypted<FriendImageIdRequestPacket.Response>(this)
0x01_BDu -> ResponsePacket.Encrypted<SubmitImageFilenamePacket.Response>(this)
// 0x01_BDu -> ResponsePacket.Encrypted<SubmitImageFilenamePacket.Response>(this)
else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applySequence(sequenceId)
......
......@@ -3,14 +3,15 @@
package net.mamoe.mirai.utils.io
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.coerceAtMostOrFail
import kotlin.random.Random
import kotlin.random.nextInt
fun BytePacketBuilder.writeZero(count: Int) = repeat(count) { this.writeByte(0) }
fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
......@@ -26,28 +27,25 @@ fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) {
}
// will box, but it doesn't matter
private fun <N : Comparable<N>> N.coerceAtMostOrFail(maximumValue: N): N =
if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
else this
fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = with(BytePacketBuilder().apply(builder).build()) {
if (tag != null) {
writeUByte(tag)
fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
with(BytePacketBuilder().apply(builder).build()) {
if (tag != null) {
writeUByte(tag)
}
writeUShort((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL).toUShort())
writePacket(this)
this.release()
}
writeUShort((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL).toUShort())
writePacket(this)
this.release()
}
fun BytePacketBuilder.writeUVarintLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = with(BytePacketBuilder().apply(builder).build()) {
if (tag != null) {
writeUByte(tag)
fun BytePacketBuilder.writeUVarintLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) =
with(BytePacketBuilder().apply(builder).build()) {
if (tag != null) {
writeUByte(tag)
}
writeUVarInt((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL))
writePacket(this)
this.release()
}
writeUVarInt((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL))
writePacket(this)
this.release()
}
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeShortLVString(str: String) = this.writeShortLVByteArray(str.toByteArray())
......@@ -100,10 +98,17 @@ fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
this.writeFully(value)
}
fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.readBytes(), encoder)
fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = writeFully(TEA.encrypt(BytePacketBuilder().apply(encoder).use {
it.build().readBytes()
}, key))
/**
* 会使用 [ByteArrayPool] 缓存
*/
fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) }
fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance {
key.readFully(it, 0, key.readRemaining)
encryptAndWrite(it, encoder)
}
fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) {
......
......@@ -9,8 +9,6 @@ import kotlinx.io.errors.IOException
*/
expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
// TODO: 2019/10/27 使用 Ktor 的 socket
suspend fun read(buffer: IoBuffer): Int
suspend fun send(buffer: IoBuffer): Int
......@@ -25,9 +23,9 @@ expect class ClosedChannelException : IOException
/**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/
expect class SendPacketInternalException(cause: Throwable?) : IOException
class SendPacketInternalException(cause: Throwable?) : Exception(cause)
/**
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
*/
expect class ReadPacketInternalException(cause: Throwable?) : IOException
\ No newline at end of file
class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils
package net.mamoe.mirai.utils.io
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.io.toUHexString
import kotlinx.io.pool.ObjectPool
import kotlin.jvm.Synchronized
import kotlin.random.Random
import kotlin.random.nextInt
/*
* 类型转换 Utils.
* 这些函数为内部函数, 可能会改变
*/
/**
* 255 -> 00 00 00 FF
*/
fun Int.toByteArray(): ByteArray = byteArrayOf(
(ushr(24) and 0xFF).toByte(),
(ushr(16) and 0xFF).toByte(),
(ushr(8) and 0xFF).toByte(),
(ushr(0) and 0xFF).toByte()
(shr(24) and 0xFF).toByte(),
(shr(16) and 0xFF).toByte(),
(shr(8) and 0xFF).toByte(),
(shr(0) and 0xFF).toByte()
)
/**
......@@ -24,8 +30,8 @@ fun Int.toByteArray(): ByteArray = byteArrayOf(
*/
fun UShort.toByteArray(): ByteArray = with(toUInt()) {
byteArrayOf(
(shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte()
(shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte()
)
}
......@@ -33,32 +39,80 @@ fun UShort.toByteArray(): ByteArray = with(toUInt()) {
* 255u -> 00 00 00 FF
*/
fun UInt.toByteArray(): ByteArray = byteArrayOf(
(shr(24) and 255u).toByte(),
(shr(16) and 255u).toByte(),
(shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte()
(shr(24) and 255u).toByte(),
(shr(16) and 255u).toByte(),
(shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte()
)
/**
* 转 [ByteArray] 后再转 hex
*/
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
/**
* 转无符号十六进制表示, 并补充首位 `0`.
* 转换结果示例: `FF`, `0E`
*/
fun Byte.toUHexString(): String = this.toUByte().toString(16).toUpperCase().let {
if (it.length == 1) "0$it"
else it
}
fun String.hexToBytes(): ByteArray = HexCache.hexToBytes(this)
/**
* 将无符号 Hex 转为 [ByteArray], 有根据 hex 的 [hashCode] 建立的缓存.
*/
fun String.hexToBytes(withCache: Boolean = true): ByteArray =
if (withCache) HexCache.getCacheOrConvert(this)
else this.split(" ")
.filterNot { it.isEmpty() }
.map { s -> s.toUByte(16).toByte() }
.toByteArray()
/**
* 将 Hex 转为 UByteArray, 有根据 hex 的 [hashCode] 建立的缓存.
* 将无符号 Hex 转为 [UByteArray], 有根据 hex 的 [hashCode] 建立的缓存.
*/
fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this)
fun String.hexToUBytes(withCache: Boolean = true): UByteArray =
if (withCache) HexCache.getUCacheOrConvert(this)
else this.split(" ")
.filterNot { it.isEmpty() }
.map { s -> s.toUByte(16) }
.toUByteArray()
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
/**
* 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray]
*/
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/**
* 随机生成长度为 [length] 的 [String].
*/
fun getRandomString(length: Int): String = getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9')
/**
* 根据所给 [charRange] 随机生成长度为 [length] 的 [String].
*/
fun getRandomString(length: Int, charRange: CharRange): String = String(CharArray(length) { charRange.random() })
/**
* 根据所给 [charRanges] 随机生成长度为 [length] 的 [String].
*/
fun getRandomString(length: Int, vararg charRanges: CharRange): String = String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() })
/**
* 将 [this] 前 4 个 [Byte] 的 bits 合并为一个 [Int]
*
* 详细解释:
* 一个 [Byte] 有 8 bits
* 一个 [Int] 有 32 bits
* 本函数将 4 个 [Byte] 的 bits 连接得到 [Int]
*/
fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0)
/**
* 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入.
* 注意回收 ([ObjectPool.recycle])
*/
fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it }
/**
......@@ -69,20 +123,28 @@ internal object HexCache {
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
@Synchronized
internal fun hexToBytes(hex: String): ByteArray = hex.hashCode().let { id ->
internal fun getCacheOrConvert(hex: String): ByteArray = hex.hashCode().let { id ->
if (hexToByteArrayCacheMap.containsKey(id)) {
return hexToByteArrayCacheMap[id]!!.copyOf()
return hexToByteArrayCacheMap[id]!!
} else {
hexToUBytes(hex).toByteArray().let {
hexToByteArrayCacheMap[id] = it.copyOf()
hex.hexToBytes(withCache = false).let {
hexToByteArrayCacheMap[id] = it
return it
}
}
}
internal fun hexToUBytes(hex: String): UByteArray =
hex.split(" ")
.filterNot { it.isEmpty() }
.map { s -> s.toUByte(16) }
.toUByteArray()
private val hexToUByteArrayCacheMap: MutableMap<Int, UByteArray> = mutableMapOf()
@Synchronized
internal fun getUCacheOrConvert(hex: String): UByteArray = hex.hashCode().let { id ->
if (hexToUByteArrayCacheMap.containsKey(id)) {
return hexToUByteArrayCacheMap[id]!!
} else {
hex.hexToUBytes(withCache = false).let {
hexToUByteArrayCacheMap[id] = it
return it
}
}
}
}
\ No newline at end of file
......@@ -55,6 +55,9 @@ private object IgnoreIdListInclude : List<String> by listOf(
"RefVolatile"
)
/**
* 这个方法会翻倍内存占用, 考虑修改.
*/
@Suppress("UNCHECKED_CAST")
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields
.filterNot { field ->
......
......@@ -9,7 +9,7 @@ actual typealias PlatformLogger = Console
* JVM 控制台日志实现
*/
open class Console @JvmOverloads internal constructor(
override var identity: String? = null
override val identity: String? = null
) : MiraiLoggerPlatformBase() {
override fun logGreen0(any: Any?) = println(any.toString(), LoggerTextFormat.GREEN)
override fun logPurple0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_PURPLE)
......@@ -17,7 +17,7 @@ open class Console @JvmOverloads internal constructor(
override fun logCyan0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_CYAN)
override fun logError0(any: Any?) = println(any.toString(), LoggerTextFormat.RED)
override fun log0(e: Throwable) = e.printStackTrace()
override fun log0(any: Any?) = println(any.toString())//kotlin println
override fun log0(any: Any?) = println(any.toString(), LoggerTextFormat.WHITE)
override fun logDebug0(any: Any?) {
if (DEBUGGING) {
println(any.toString(), LoggerTextFormat.YELLOW)
......
package net.mamoe.mirai.utils
import java.nio.ByteBuffer
import java.util.*
import kotlin.experimental.and
import kotlin.experimental.xor
/**
* TEA 加密
*
* @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java
*/
internal actual object TEA {
private const val UINT32_MASK = 0xffffffffL
internal actual fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray {
val mRandom = Random()
lateinit var mOutput: ByteArray
lateinit var mInBlock: ByteArray
var mIndexPos: Int
lateinit var mIV: ByteArray
var mOutPos = 0
var mPreOutPos = 0
var isFirstBlock = true
val mKey = LongArray(4)
for (i in 0..3) {
mKey[i] = pack(key, i * 4, 4)
}
fun rand(): Int {
return mRandom.nextInt()
}
fun encode(bytes: ByteArray): ByteArray {
var v0 = pack(bytes, 0, 4)
var v1 = pack(bytes, 4, 4)
var sum: Long = 0
val delta = 0x9e3779b9L
for (i in 0..15) {
sum = sum + delta and UINT32_MASK
v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1]
v0 = v0 and UINT32_MASK
v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3]
v1 = v1 and UINT32_MASK
}
return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array()
}
fun decode(bytes: ByteArray, offset: Int): ByteArray {
var v0 = pack(bytes, offset, 4)
var v1 = pack(bytes, offset + 4, 4)
val delta = 0x9e3779b9L
var sum = delta shl 4 and UINT32_MASK
for (i in 0..15) {
v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3]
v1 = v1 and UINT32_MASK
v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1]
v0 = v0 and UINT32_MASK
sum = sum - delta and UINT32_MASK
}
return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array()
}
fun encodeOneBlock() {
mIndexPos = 0
while (mIndexPos < 8) {
mInBlock[mIndexPos] = if (isFirstBlock)
mInBlock[mIndexPos]
else
(mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos])
mIndexPos++
}
System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8)
mIndexPos = 0
while (mIndexPos < 8) {
val outPos = mOutPos + mIndexPos
mOutput[outPos] = (mOutput[outPos] xor mIV[mIndexPos])
mIndexPos++
}
System.arraycopy(mInBlock, 0, mIV, 0, 8)
mPreOutPos = mOutPos
mOutPos += 8
mIndexPos = 0
isFirstBlock = false
}
fun decodeOneBlock(ciphertext: ByteArray, offset: Int, len: Int): Boolean {
mIndexPos = 0
while (mIndexPos < 8) {
if (mOutPos + mIndexPos < len) {
mIV[mIndexPos] = (mIV[mIndexPos] xor ciphertext[mOutPos + offset + mIndexPos])
mIndexPos++
continue
}
return true
}
mIV = decode(mIV, 0)
mOutPos += 8
mIndexPos = 0
return true
}
@Suppress("NAME_SHADOWING")
fun encrypt(plaintext: ByteArray, offset: Int, len: Int): ByteArray {
var len = len
var offset = offset
mInBlock = ByteArray(8)
mIV = ByteArray(8)
mOutPos = 0
mPreOutPos = 0
isFirstBlock = true
mIndexPos = (len + 10) % 8
if (mIndexPos != 0) {
mIndexPos = 8 - mIndexPos
}
mOutput = ByteArray(mIndexPos + len + 10)
mInBlock[0] = (rand() and 0xf8 or mIndexPos).toByte()
for (i in 1..mIndexPos) {
mInBlock[i] = (rand() and 0xff).toByte()
}
++mIndexPos
for (i in 0..7) {
mIV[i] = 0
}
var g = 0
while (g < 2) {
if (mIndexPos < 8) {
mInBlock[mIndexPos++] = (rand() and 0xff).toByte()
++g
}
if (mIndexPos == 8) {
encodeOneBlock()
}
}
while (len > 0) {
if (mIndexPos < 8) {
mInBlock[mIndexPos++] = plaintext[offset++]
}
if (mIndexPos == 8) {
encodeOneBlock()
}
len--
}
g = 0
while (g < 7) {
if (mIndexPos < 8) {
mInBlock[mIndexPos++] = 0.toByte()
}
if (mIndexPos == 8) {
encodeOneBlock()
}
g++
}
return mOutput
}
fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray {
require(!(len % 8 != 0 || len < 16)) { "data must len % 8 == 0 && len >= 16 but given $len" }
mIV = decode(cipherText, offset)
mIndexPos = (mIV[0] and 7).toInt()
var plen = len - mIndexPos - 10
isFirstBlock = true
if (plen < 0) {
fail()
}
mOutput = ByteArray(plen)
mPreOutPos = 0
mOutPos = 8
++mIndexPos
var g = 0
while (g < 2) {
if (mIndexPos < 8) {
++mIndexPos
++g
}
if (mIndexPos == 8) {
isFirstBlock = false
if (!decodeOneBlock(cipherText, offset, len)) {
fail()
}
}
}
var outpos = 0
while (plen != 0) {
if (mIndexPos < 8) {
mOutput[outpos++] = if (isFirstBlock)
mIV[mIndexPos]
else
(cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos])
++mIndexPos
}
if (mIndexPos == 8) {
mPreOutPos = mOutPos - 8
isFirstBlock = false
if (!decodeOneBlock(cipherText, offset, len)) {
fail()
}
}
plen--
}
g = 0
while (g < 7) {
if (mIndexPos < 8) {
if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) {
fail()
} else {
++mIndexPos
}
}
if (mIndexPos == 8) {
mPreOutPos = mOutPos
if (!decodeOneBlock(cipherText, offset, len)) {
fail()
}
}
g++
}
return mOutput
}
return if (encrypt) {
encrypt(data, 0, data.size)
} else {
decrypt(data, 0, data.size)
}
}
private fun fail(): Nothing = throw DecryptionFailedException()
@JvmStatic
actual fun encrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, true)
}
@JvmStatic
actual fun decrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, false)
}
@Suppress("SameParameterValue")
private fun pack(bytes: ByteArray, offset: Int, len: Int): Long {
var result: Long = 0
val maxOffset = if (len > 8) offset + 8 else offset + len
for (index in offset until maxOffset) {
result = result shl 8 or (bytes[index].toLong() and 0xffL)
}
return result shr 32 or (result and UINT32_MASK)
}
}
class DecryptionFailedException : Exception()
\ No newline at end of file
package net.mamoe.mirai.utils.config
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
/**
* YAML-TYPE CONFIG
* Thread SAFE
*
* @author NaturalHG
*/
class MiraiConfig(private val root: File) : MiraiConfigSection<Any>(parse(Objects.requireNonNull(root))) {
@Synchronized
fun save() {
val dumperOptions = DumperOptions()
dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
val yaml = Yaml(dumperOptions)
val content = yaml.dump(this)
try {
ByteArrayInputStream(content.toByteArray()).transferTo(FileOutputStream(this.root))
} catch (e: IOException) {
e.printStackTrace()
}
}
companion object {
@Suppress("UNCHECKED_CAST")
private fun parse(file: File): MutableMap<String, Any>? {
/*
if (!file.toURI().getPath().contains(MiraiServer.getInstance().getParentFolder().getPath())) {
file = new File(MiraiServer.getInstance().getParentFolder().getPath(), file.getName());
}*/
if (!file.exists()) {
try {
if (!file.createNewFile()) {
return linkedMapOf()
}
} catch (e: IOException) {
e.printStackTrace()
return linkedMapOf()
}
}
val dumperOptions = DumperOptions()
dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
val yaml = Yaml(dumperOptions)
return yaml.loadAs<LinkedHashMap<*, *>>(file.readLines(Charset.defaultCharset()).joinToString("\n"), LinkedHashMap::class.java) as MutableMap<String, Any>?
}
}
}
package net.mamoe.mirai.utils.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
/**
* @author NaturalHG
*/
public class MiraiConfigSection<T> extends LinkedHashMap<String, T> {
public MiraiConfigSection(){
super();
}
public MiraiConfigSection(Map<String, T> copyOfMap) {
super(new LinkedHashMap<>(copyOfMap));
}
public int getInt(String key){
return Integer.parseInt(this.get(key).toString());
}
public int getIntOrDefault(String key, int defaultV){
Object result = this.getOrDefault(key, null);
try {
return result == null ? defaultV : Integer.parseInt(result.toString());
}catch (NumberFormatException ignored){
return defaultV;
}
}
public int getIntOrThrow(String key, Callable<Throwable> throwableCallable) throws Throwable {
Object result = this.getOrDefault(key, null);
if(result == null){
throw throwableCallable.call();
}
try {
return Integer.parseInt(result.toString());
}catch (NumberFormatException ignored){
throw throwableCallable.call();
}
}
public double getDouble(String key){
return Double.parseDouble(this.get(key).toString());
}
public double getDoubleOrDefault(String key, double defaultV){
Object result = this.getOrDefault(key, null);
try {
return result == null ? defaultV : Double.parseDouble(result.toString());
}catch (NumberFormatException ignored){
return defaultV;
}
}
public double getDoubleOrThrow(String key, Callable<Throwable> throwableCallable) throws Throwable {
Object result = this.getOrDefault(key, null);
if(result == null){
throw throwableCallable.call();
}
try {
return Double.parseDouble(result.toString());
}catch (NumberFormatException ignored){
throw throwableCallable.call();
}
}
public float getFloat(String key){
return Float.parseFloat(this.get(key).toString());
}
public float getFloatOrDefault(String key, float defaultV){
Object result = this.getOrDefault(key, null);
try {
return result == null ? defaultV : Float.parseFloat(result.toString());
}catch (NumberFormatException ignored){
return defaultV;
}
}
public float getFloatOrThrow(String key, Callable<Throwable> throwableCallable) throws Throwable {
Object result = this.getOrDefault(key, null);
if(result == null){
throw throwableCallable.call();
}
try {
return Float.parseFloat(result.toString());
}catch (NumberFormatException ignored){
throw throwableCallable.call();
}
}
public long getLong(String key){
return Long.parseLong(this.get(key).toString());
}
public long getLongOrDefault(String key, long defaultV){
Object result = this.getOrDefault(key, null);
try {
return result == null ? defaultV : Long.parseLong(result.toString());
}catch (NumberFormatException ignored){
return defaultV;
}
}
public long getLongOrThrow(String key, Callable<Throwable> throwableCallable) throws Throwable {
Object result = this.getOrDefault(key, null);
if(result == null){
throw throwableCallable.call();
}
try {
return Long.parseLong(result.toString());
}catch (NumberFormatException ignored){
throw throwableCallable.call();
}
}
public String getString(String key){
return String.valueOf(this.get(key));
}
public String getStringOrDefault(String key, String defaultV){
Object result = this.getOrDefault(key, null);
return result==null?defaultV:result.toString();
}
public String getStringOrThrow(String key, Supplier<Throwable> exceptionSupplier) throws Throwable {
Object result = this.getOrDefault(key, null);
if(result == null){
throw exceptionSupplier.get();
}
return result.toString();
}
@Override
public T put(String key, T value) {
return super.put(key, value);
}
@SuppressWarnings("unchecked")
public <D> MiraiConfigSection<D> getTypedSection(String key){
var content = this.get(key);
if(content instanceof MiraiConfigSection){
return (MiraiConfigSection<D>) content;
}
if(content instanceof Map){
return new MiraiConfigSection<>(
(LinkedHashMap<String, D>) content
);
}
return null;
}
public MiraiConfigSection<Object> getSection(String key){
return this.getTypedSection(key);
}
@SuppressWarnings("unchecked")
public <D extends T> D getAsOrDefault(String key, D defaultV){
return (D)this.getOrDefault(key,defaultV);
}
@SuppressWarnings("unchecked")
public <D extends T> D getAsOrDefault(String key, Supplier<D> supplier) {
D d = (D) this.get(key);
if (d != null) {
return d;
}
return supplier.get();
}
@SuppressWarnings("unchecked")
public <D extends T> D getAs(String key) {
return (D) this.get(key);
}
}
......@@ -3,7 +3,6 @@
package net.mamoe.mirai.utils.io
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.toUHexString
import java.lang.reflect.Field
import java.util.*
import kotlin.math.max
......
......@@ -4,7 +4,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.errors.IOException
import kotlinx.io.nio.read
import java.net.InetSocketAddress
import java.nio.channels.DatagramChannel
......@@ -43,6 +42,56 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
actual class SendPacketInternalException actual constructor(cause: Throwable?) : IOException(cause)
actual class ReadPacketInternalException actual constructor(cause: Throwable?) : IOException(cause)
\ No newline at end of file
/*
actual class PlatformDatagramChannel actual constructor(serverHost: String, serverPort: Short) : Closeable {
private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt())
@KtorExperimentalAPI
val socket = runBlocking { aSocket(ActorSelectorManager(Dispatchers.IO)).tcp()
.connect(remoteAddress = serverAddress) }
@KtorExperimentalAPI
val readChannel = socket.openReadChannel()
@KtorExperimentalAPI
val writeChannel = socket.openWriteChannel(true)
@KtorExperimentalAPI
@Throws(ReadPacketInternalException::class)
actual suspend fun read(buffer: IoBuffer) =
try {
readChannel.readAvailable(buffer)
} catch (e: ClosedChannelException) {
throw e
} catch (e: Throwable) {
throw ReadPacketInternalException(e)
}
@KtorExperimentalAPI
@Throws(SendPacketInternalException::class)
actual suspend fun send(buffer: IoBuffer) =
buffer.readDirect {
try {
writeChannel.writeFully(it)
} catch (e: ClosedChannelException) {
throw e
} catch (e: Throwable) {
throw SendPacketInternalException(e)
}
}
@KtorExperimentalAPI
@Throws(IOException::class)
override fun close() {
socket.close()
}
@KtorExperimentalAPI
actual val isOpen: Boolean
get() = socket.isClosed.not()
}
*/
\ No newline at end of file
package net.mamoe.mirai.utils.setting
import org.ini4j.Profile
import java.io.IOException
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock
/**
* @author NaturalHG
*/
class MiraiSettingListSection : Vector<Any>(), MiraiSettingSection {
private val lock = ReentrantLock()
@Suppress("UNCHECKED_CAST")
fun <T> getAs(index: Int): T {
return super.get(index) as T
}
fun getInt(index: Int): Int {
return this.getAs(index)
}
fun getDouble(index: Int): Int {
return this.getAs(index)
}
fun getString(index: Int): Int {
return this.getAs(index)
}
fun getFloat(index: Int): Int {
return this.getAs(index)
}
@Synchronized
override fun saveAsSection(section: Profile.Section) {
section.clear()
val integer = AtomicInteger(0)
this.forEach { a -> section.put(integer.getAndAdd(1).toString(), a) }
}
@Throws(IOException::class)
override fun close() {
}
}
package net.mamoe.mirai.utils.setting
import org.ini4j.Profile
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import kotlin.streams.toList
/**
* @author NaturalHG
*/
class MiraiSettingMapSection : ConcurrentHashMap<String, Any>(), MiraiSettingSection {
operator fun <T> get(key: String?, defaultValue: T): T {
if (key == null || key.isEmpty()) {
return defaultValue
}
return if (super.containsKey(key)) {
@Suppress("UNCHECKED_CAST")
super.get(key) as T
} else defaultValue
}
operator fun set(key: String, value: Any) {
this[key] = value
}
fun getInt(key: String): Int {
return this.getInt(key, 0)
}
fun getInt(key: String, defaultValue: Int): Int {
return Integer.parseInt(this[key, defaultValue].toString())
}
fun getDouble(key: String): Double {
return this.getDouble(key, 0.0)
}
fun getDouble(key: String, defaultValue: Double): Double {
return java.lang.Double.parseDouble(this[key, defaultValue].toString())
}
fun getFloat(key: String): Float {
return this.getFloat(key, 0f)
}
fun getFloat(key: String, defaultValue: Float): Float {
return java.lang.Float.parseFloat(this[key, defaultValue].toString())
}
fun getString(key: String): String {
return this.getString(key, "")
}
fun getString(key: String, defaultValue: String): String {
return this[key, defaultValue].toString()
}
fun getObject(key: String): Any? {
return this[key]
}
@Suppress("UNCHECKED_CAST")
fun <T> asList(): List<T> {
return this.values.stream().map { a -> a as T }.toList()
}
@Synchronized
override fun saveAsSection(section: Profile.Section) {
section.clear()
this.forEach{ key, value -> section.put(key, value) }
}
@Throws(IOException::class)
override fun close() {
}
}
package net.mamoe.mirai.utils.setting
import org.ini4j.Profile
import java.io.Closeable
/**
* @author NaturalHG
*/
interface MiraiSettingSection : Closeable {
fun saveAsSection(section: Profile.Section)
}
\ No newline at end of file
package net.mamoe.mirai.utils.setting
import org.ini4j.Config
import org.ini4j.Ini
import java.io.File
import java.io.IOException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* Thread-safe Mirai Config <br></br>
* Only supports `INI` format <br></br>
* Supports [Map] and [List]
*
* @author NaturalHG
*/
class MiraiSettings(file: File)
/*
public MiraiSettings(MiraiPluginBase pluginBase, String filename) {
// TODO: 2019/9/6 每个插件独立文件夹存放
this(new File(filename));
}*/ {
private val file: File
private var ini: Ini
private val cacheSection = ConcurrentHashMap<String, MiraiSettingSection>()
init {
val f = file.takeIf { it.name.contains(".") } ?: File(file.path + ".ini")
this.file = f
if (!f.exists() && !f.createNewFile()) {
throw RuntimeException("cannot create config file $f")
}
val config = Config()
config.isMultiSection = true
ini = Ini()
ini.config = config
ini.load(this.file.toURI().toURL())
}
@Synchronized
fun setSection(key: String, section: MiraiSettingSection) {
cacheSection[key] = section
}
@Synchronized
fun getMapSection(key: String): MiraiSettingMapSection {
if (!cacheSection.containsKey(key)) {
val section = MiraiSettingMapSection()
if (ini.containsKey(key)) {
section.putAll(ini[key]!!)
}
cacheSection[key] = section
}
return cacheSection[key] as MiraiSettingMapSection
}
@Synchronized
fun getListSection(key: String): MiraiSettingListSection {
if (!cacheSection.containsKey(key)) {
val section = MiraiSettingListSection()
if (ini.containsKey(key)) {
section.addAll(ini[key]!!.values)
}
cacheSection[key] = section
}
return cacheSection[key] as MiraiSettingListSection
}
@Synchronized
fun save() {
cacheSection.forEach { (k, a) ->
if (!ini.containsKey(k)) {
ini.put(k, "", HashMap<Any, Any>())
}
a.saveAsSection(ini[k]!!)
}
this.clearCache()
try {
ini.store(file)
} catch (e: IOException) {
e.printStackTrace()
}
}
@Synchronized
fun clearCache() {
cacheSection.clear()
}
}
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
import net.mamoe.mirai.contact.groupId
import net.mamoe.mirai.contact.toInternalId
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupImageIdRequestPacket
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.readRemainingBytes
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.toExternalImage
import java.io.File
import javax.imageio.ImageIO
val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes()
fun main() = println({
val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.gif").readBytes().inputStream()).toExternalImage("png")
// File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes())
GroupImageIdRequestPacket(
1994701021u,
580266363u.groupId().toInternalId(),
image,
sessionKey
).packet.readRemainingBytes().toUHexString()
}())
@file:Suppress("UNUSED_VARIABLE")
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.stringOfWitch
import net.mamoe.mirai.utils.io.toUHexString
import java.io.ByteArrayInputStream
......
......@@ -7,7 +7,7 @@ import javafx.scene.layout.Region
import javafx.scene.paint.Color
import javafx.scene.text.FontWeight
import kotlinx.coroutines.*
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.stringOfWitch
import net.mamoe.mirai.utils.readUnsignedVarInt
......
......@@ -15,9 +15,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.event.UnknownServerEventPacket
import net.mamoe.mirai.utils.DecryptionFailedException
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.toUHexString
import org.pcap4j.core.BpfProgram.BpfCompileMode
import org.pcap4j.core.PacketListener
import org.pcap4j.core.PcapNetworkInterface
......@@ -135,7 +133,6 @@ object Main {
println("密文body=" + remaining.toUHexString())
println("解密body=解密失败")
}
}
}
......
......@@ -16,9 +16,9 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess
import net.mamoe.mirai.network.session
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.toExternalImage
import java.io.File
......@@ -36,6 +36,37 @@ private fun readTestAccount(): BotAccount? {
}
}
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(
readTestAccount() ?: BotAccount(//填写你的账号
id = 1994701121u,
password = "123456"
)
)
// 覆盖默认的配置
bot.login {
randomDeviceName = false
}.requireSuccess()
bot.messageDSL()
directlySubscribe(bot)
//DSL 监听
subscribeAll<FriendMessageEvent> {
always {
//获取第一个纯文本消息
val firstText = it.message.firstOrNull<PlainText>()
}
}
demo2()
bot.network.awaitDisconnection()//等到直到断开连接
}
/**
* 使用 dsl 监听消息事件
*
......@@ -237,38 +268,6 @@ suspend fun directlySubscribe(bot: Bot) {
}
}
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(
readTestAccount() ?: BotAccount(//填写你的账号
id = 1994701121u,
password = "123456"
)
)
// 覆盖默认的配置
bot.login {
randomDeviceName = false
}.requireSuccess()
bot.messageDSL()
directlySubscribe(bot)
//DSL 监听
subscribeAll<FriendMessageEvent> {
always {
//获取第一个纯文本消息
val firstText = it.message.firstOrNull<PlainText>()
}
}
demo2()
bot.network.awaitDisconnection()//等到直到断开连接
}
/**
* 实现功能:
* 对机器人说 "记笔记", 机器人记录之后的所有消息.
......
rootProject.name = 'mirai'
include(':mirai-core')
include(':mirai-console')
include(':mirai-api')
include(':mirai-demos:mirai-demo-1')
......
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