1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-15 07:37:08 +08:00

Implement mirai-core

This commit is contained in:
Him188 2022-05-30 21:30:52 +01:00
parent b7e4ed75d7
commit b94d431cd8
No known key found for this signature in database
GPG Key ID: BA439CDDCF652375
30 changed files with 691 additions and 132 deletions
buildSrc/src/main/kotlin
gradle.properties
mirai-core-api/src/nativeMain/kotlin/utils
mirai-core-utils/src/nativeMain/kotlin
mirai-core

View File

@ -28,10 +28,10 @@ object Versions {
const val kotlinCompilerForIdeaPlugin = "1.7.0-RC"
const val coroutines = "1.6.1"
const val coroutines = "1.6.1-native-mt"
const val atomicFU = "0.17.2"
const val serialization = "1.3.2"
const val ktor = "1.6.7"
const val ktor = "1.6.8"
const val binaryValidator = "0.4.0"
@ -108,6 +108,8 @@ val `ktor-serialization` = ktor("serialization", Versions.ktor)
val `ktor-client-core` = ktor("client-core", Versions.ktor)
val `ktor-client-cio` = ktor("client-cio", Versions.ktor)
val `ktor-client-curl` = ktor("client-curl", Versions.ktor)
val `ktor-client-ios` = ktor("client-ios", Versions.ktor)
val `ktor-client-okhttp` = ktor("client-okhttp", Versions.ktor)
val `ktor-client-android` = ktor("client-android", Versions.ktor)
val `ktor-client-logging` = ktor("client-logging", Versions.ktor)

View File

@ -23,4 +23,5 @@ gnsp.disableApplyOnlyOnRootProjectEnforcement=true
mirai.android.target.api.level=24
# Enable if you want to use mavenLocal for both Gradle plugin and project dependencies resolutions.
systemProp.use.maven.local=false
org.gradle.caching=true
org.gradle.caching=true
kotlin.native.ignoreIncorrectDependencies=true

View File

@ -75,7 +75,7 @@ public actual abstract class LoginSolver actual constructor() {
* @return `SwingSolver` `StandardCharImageLoginSolver` `null`
*/
public actual val Default: LoginSolver?
get() = TODO("Not yet implemented")
get() = null
@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
@Suppress("unused")

View File

@ -215,5 +215,5 @@ internal class PosixInputForFile(val file: CPointer<FILE>) : AbstractInput() {
}
@OptIn(ExperimentalIoApi::class)
internal fun PosixException.wrapIO(): IOException =
public fun PosixException.wrapIO(): IOException =
IOException("I/O operation failed due to posix error code $errno", this)

View File

@ -44,6 +44,12 @@ public object Services {
}
}
public fun print(): String {
lock.withLock {
return registered.entries.joinToString { "${it.key}:${it.value}" }
}
}
}
@Suppress("UNCHECKED_CAST")
@ -57,7 +63,7 @@ public actual fun <T : Any> loadService(
clazz: KClass<out T>,
fallbackImplementation: String?
): T = loadServiceOrNull(clazz, fallbackImplementation)
?: error("Could not load service '${clazz.qualifiedName ?: clazz}'")
?: error("Could not load service '${clazz.qualifiedName ?: clazz}'. Current services: ${Services.print()}")
public actual fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> =
Services.implementations(qualifiedNameOrFail(clazz))?.asSequence().orEmpty().castUp()

View File

@ -9,10 +9,12 @@
package net.mamoe.mirai.utils
private val properties: MutableMap<String, String> = ConcurrentHashMap()
internal actual fun getProperty(name: String, default: String): String? {
TODO("Not yet implemented")
return properties.getOrElse(name) { default }
}
internal actual fun setProperty(name: String, value: String) {
TODO("Not yet implemented")
properties[name] = value
}

View File

@ -102,6 +102,29 @@ kotlin {
dependencies {
}
}
val mingwMain by getting {
dependencies {
}
}
configure((LINUX_TARGETS + WIN_TARGETS).map { getByName(it + "Main") }) {
dependencies {
implementation(`ktor-client-curl`)
}
}
configure(MAC_TARGETS.map { getByName(it + "Main") }) {
dependencies {
implementation(`ktor-client-ios`)
}
}
// val unixMain by getting {
// dependencies {
// implementation(`ktor-client-cio`)
// }
// }
}
}

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmName("MiraiImplKt_common")
package net.mamoe.mirai.internal
import io.ktor.client.*
@ -31,25 +33,14 @@ import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl.Companion.impl
import net.mamoe.mirai.internal.event.EventChannelToEventDispatcherAdapter
import net.mamoe.mirai.internal.event.InternalEventMechanism
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
import net.mamoe.mirai.internal.message.EmptyRefineContext
import net.mamoe.mirai.internal.message.RefineContext
import net.mamoe.mirai.internal.message.SimpleRefineContext
import net.mamoe.mirai.internal.message.data.*
import net.mamoe.mirai.internal.message.data.FileMessageImpl
import net.mamoe.mirai.internal.message.data.OfflineAudioImpl
import net.mamoe.mirai.internal.message.data.OnlineAudioImpl
import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl
import net.mamoe.mirai.internal.message.image.*
import net.mamoe.mirai.internal.message.image.OfflineGroupImage
import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl
import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl
import net.mamoe.mirai.internal.message.source.*
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromFriendImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromGroupImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromStrangerImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromTempImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToStrangerImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToTempImpl
import net.mamoe.mirai.internal.message.toMessageChainNoSource
import net.mamoe.mirai.internal.network.components.EventDispatcher
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.ResourceKind
@ -77,14 +68,19 @@ import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmName
internal fun getMiraiImpl() = Mirai as MiraiImpl
@Suppress("FunctionName")
internal expect fun _MiraiImpl_static_init()
@OptIn(LowLevelApi::class)
// not object for ServiceLoader.
internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
companion object {
init {
_MiraiImpl_static_init()
MessageSerializers.registerSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer())
MessageSerializers.registerSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer())
MessageSerializers.registerSerializer(OnlineFriendImageImpl::class, OnlineFriendImageImpl.serializer())
@ -870,17 +866,21 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
return main.toForwardMessageNodes(bot, context)
}
protected open suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
private suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
val msg = this
@Suppress("USELESS_CAST") // compiler bug, do not remove
val senderName = (msg.msgHead.groupInfo?.groupCard
?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
?: msg.msgHead.fromUin.toString()) as String
val chain = listOf(msg)
.toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP)
.refineDeep(bot, refineContext)
return ForwardMessage.Node(
senderId = msg.msgHead.fromUin,
time = msg.msgHead.msgTime,
senderName = msg.msgHead.groupInfo?.groupCard
?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
?: msg.msgHead.fromUin.toString(),
messageChain = listOf(msg)
.toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP)
.refineDeep(bot, refineContext)
senderName = senderName,
messageChain = chain
)
}

View File

@ -13,6 +13,7 @@ import kotlinx.serialization.Serializable
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.components.ServerList.Companion.DEFAULT_SERVER_LIST
import net.mamoe.mirai.internal.network.handler.SocketAddress
import net.mamoe.mirai.internal.network.handler.createSocketAddress
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.TestOnly
import net.mamoe.mirai.utils.info
@ -33,7 +34,7 @@ internal data class ServerAddress(
return "$host:$port"
}
fun toSocketAddress(): SocketAddress = SocketAddress(host, port)
fun toSocketAddress(): SocketAddress = createSocketAddress(host, port)
}
/**

View File

@ -27,9 +27,12 @@ internal expect fun interface NetworkHandlerFactory<out H : NetworkHandler> {
}
}
internal expect abstract class SocketAddress {
val host: String
val port: Int
}
internal expect abstract class SocketAddress
internal expect fun SocketAddress(host: String, port: Int): SocketAddress
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal expect fun SocketAddress.getHost(): String
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal expect fun SocketAddress.getPort(): Int
internal expect fun createSocketAddress(host: String, port: Int): SocketAddress

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.utils.coroutineName
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.systemProp
import kotlin.coroutines.coroutineContext
import kotlin.native.concurrent.ThreadLocal
internal class LoggingStateObserver(
val logger: MiraiLogger,
@ -72,6 +73,7 @@ internal class LoggingStateObserver(
)
}
@ThreadLocal
companion object {
/**
* - `on`/`true` for simple logging

View File

@ -7,11 +7,17 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmName("PlatformSocketKt_common")
package net.mamoe.mirai.internal.utils
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import net.mamoe.mirai.internal.network.handler.SocketAddress
import net.mamoe.mirai.internal.network.handler.getHost
import net.mamoe.mirai.internal.network.handler.getPort
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
import kotlin.jvm.JvmName
/**
* TCP Socket.
@ -32,7 +38,6 @@ internal expect class PlatformSocket : Closeable, HighwayProtocolChannel {
* @throws ReadPacketInternalException
*/
override suspend fun read(): ByteReadPacket
suspend fun connect(serverHost: String, serverPort: Int)
companion object {
suspend fun connect(
@ -48,6 +53,10 @@ internal expect class PlatformSocket : Closeable, HighwayProtocolChannel {
}
}
internal suspend inline fun PlatformSocket.Companion.connect(address: SocketAddress): PlatformSocket {
return connect(address.getHost(), address.getPort())
}
internal expect class SocketException : IOException {
constructor()

View File

@ -22,20 +22,20 @@ internal class MessageProtocolFacadeTest : AbstractTest() {
"""
QuoteReplyProtocol
CustomMessageProtocol
FaceProtocol
FileMessageProtocol
FlashImageProtocol
FaceProtocol
ImageProtocol
MarketFaceProtocol
MusicShareProtocol
PokeMessageProtocol
IgnoredMessagesProtocol
PttMessageProtocol
RichMessageProtocol
TextProtocol
VipFaceProtocol
ForwardMessageProtocol
LongMessageProtocol
IgnoredMessagesProtocol
UnsupportedMessageProtocol
GeneralMessageSenderProtocol
""".trimIndent(),

View File

@ -24,11 +24,8 @@ import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
import net.mamoe.mirai.internal.network.component.setAll
import net.mamoe.mirai.internal.network.components.*
import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.*
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
import net.mamoe.mirai.internal.network.handler.SocketAddress
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.utils.subLogger
@ -175,7 +172,7 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
//Use overrideComponents to avoid StackOverflowError when applying components
open fun createAddress(): SocketAddress =
overrideComponents[ServerList].pollAny().let { SocketAddress(it.host, it.port) }
overrideComponents[ServerList].pollAny().let { createSocketAddress(it.host, it.port) }
///////////////////////////////////////////////////////////////////////////
// Assertions

View File

@ -0,0 +1,17 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmName("MiraiImplKt")
package net.mamoe.mirai.internal
@Suppress("FunctionName")
internal actual fun _MiraiImpl_static_init() {
// nop
}

View File

@ -12,8 +12,14 @@ package net.mamoe.mirai.internal.network.handler
import java.net.InetSocketAddress
@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
internal actual typealias SocketAddress = java.net.SocketAddress
internal actual typealias SocketAddress = java.net.InetSocketAddress
internal actual fun SocketAddress(host: String, port: Int): SocketAddress {
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal actual fun SocketAddress.getHost(): String = hostString ?: error("Failed to get host from address '$this'.")
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal actual fun SocketAddress.getPort(): Int = this.port
internal actual fun createSocketAddress(host: String, port: Int): SocketAddress {
return InetSocketAddress.createUnresolved(host, port)
}

View File

@ -27,6 +27,7 @@ import net.mamoe.mirai.internal.network.handler.CommonNetworkHandler
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.debug
import java.net.SocketAddress
import io.netty.channel.Channel as NettyChannel
@ -34,7 +35,7 @@ import io.netty.channel.Channel as NettyChannel
internal open class NettyNetworkHandler(
context: NetworkHandlerContext,
address: SocketAddress,
) : CommonNetworkHandler<NettyChannel>(context, address) {
) : CommonNetworkHandler<NettyChannel>(context, address.cast()) {
override fun toString(): String {
return "NettyNetworkHandler(context=$context, address=$address)"
}

View File

@ -11,7 +11,7 @@ package net.mamoe.mirai.internal.network.impl.netty
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
import java.net.SocketAddress
import net.mamoe.mirai.internal.network.handler.SocketAddress
internal object NettyNetworkHandlerFactory : NetworkHandlerFactory<NettyNetworkHandler> {
override fun create(context: NetworkHandlerContext, address: SocketAddress): NettyNetworkHandler {

View File

@ -86,7 +86,7 @@ internal actual class PlatformSocket : Closeable, HighwayProtocolChannel {
}
}
actual suspend fun connect(serverHost: String, serverPort: Int) {
suspend fun connect(serverHost: String, serverPort: Int) {
runInterruptible(Dispatchers.IO) {
socket = Socket(serverHost, serverPort)
readChannel = socket.getInputStream().buffered()

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -22,6 +22,7 @@ import net.mamoe.mirai.internal.network.components.ServerList
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
import net.mamoe.mirai.internal.network.handler.SocketAddress
import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector
import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler
@ -31,7 +32,6 @@ import java.awt.Component
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.lang.invoke.MethodHandles
import java.net.SocketAddress
import javax.swing.*
import kotlin.reflect.KProperty
import kotlin.reflect.full.declaredMembers

View File

@ -0,0 +1,10 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal

View File

@ -0,0 +1,146 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.utils
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import kotlinx.cinterop.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
import net.mamoe.mirai.internal.network.protocol.packet.login.toIpV4Long
import net.mamoe.mirai.utils.DEFAULT_BUFFER_SIZE
import net.mamoe.mirai.utils.toReadPacket
import net.mamoe.mirai.utils.wrapIO
import platform.posix.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* TCP Socket.
*/
internal actual class PlatformSocket(
private val socket: Int
) : Closeable, HighwayProtocolChannel {
@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher: CoroutineDispatcher = newSingleThreadContext("PlatformSocket#$socket.dispatcher")
private val readLock = Mutex()
private val readBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
private val writeLock = Mutex()
private val writeBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
actual val isOpen: Boolean
get() = write(socket, null, 0) != 0
@OptIn(ExperimentalIoApi::class)
actual override fun close() {
if (close(socket) != 0) {
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
}
}
@OptIn(ExperimentalIoApi::class)
actual suspend fun send(packet: ByteArray, offset: Int, length: Int): Unit = readLock.withLock {
withContext(dispatcher) {
require(offset >= 0) { "offset must >= 0" }
require(length >= 0) { "length must >= 0" }
require(offset + length <= packet.size) { "It must follows offset + length <= packet.size" }
packet.usePinned { pin ->
if (write(socket, pin.addressOf(offset), length.convert()) != 0) {
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
}
}
}
}
/**
* @throws SendPacketInternalException
*/
@OptIn(ExperimentalIoApi::class)
actual override suspend fun send(packet: ByteReadPacket): Unit = readLock.withLock {
withContext(dispatcher) {
val writeBuffer = writeBuffer
val length = packet.readAvailable(writeBuffer.get())
if (write(socket, writeBuffer.addressOf(0), length.convert()) != 0) {
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
}
}
}
/**
* @throws ReadPacketInternalException
*/
actual override suspend fun read(): ByteReadPacket = writeLock.withLock {
withContext(dispatcher) {
val readBuffer = readBuffer
val length = read(socket, readBuffer.addressOf(0), readBuffer.get().size.convert())
readBuffer.get().toReadPacket(length = length)
}
}
actual companion object {
@OptIn(UnsafeNumber::class, ExperimentalIoApi::class)
actual suspend fun connect(
serverIp: String,
serverPort: Int
): PlatformSocket {
val addr = memScoped {
alloc<sockaddr_in>() {
sin_family = AF_INET.convert()
resolveIpFromHost(serverIp)
sin_addr.S_un.S_addr = resolveIpFromHost(serverIp)
}
}.reinterpret<sockaddr>()
val id = socket(AF_INET, 1 /* SOCKET_STREAM */, IPPROTO_TCP)
if (id != 0uL) throw PosixException.forErrno(posixFunctionName = "socket()")
val conn = connect(id, addr.ptr, sizeOf<sockaddr_in>().convert())
if (conn != 0) throw PosixException.forErrno(posixFunctionName = "connect()")
return PlatformSocket(conn)
}
private fun resolveIpFromHost(serverIp: String): UInt {
val host = platform.windows.gethostbyname(serverIp)
?: throw IllegalStateException("Failed to resolve IP from host. host=$serverIp")
// val str = try {
// val hAddrList = host.pointed.h_addr_list
// ?: throw IllegalStateException("Empty IP list resolved from host. host=$serverIp")
//
// hAddrList[0]!!.toKString()
// } finally {
// free(host)
// }
// TODO: 2022/5/30 check memory
return serverIp.toIpV4Long().toUInt()
// return str.toIpV4Long().toUInt()
}
actual suspend inline fun <R> withConnection(
serverIp: String,
serverPort: Int,
block: PlatformSocket.() -> R
): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return connect(serverIp, serverPort).use(block)
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.internal.utils.MiraiCoreServices
public fun initMirai() {
_MiraiImpl_static_init()
}
private val initialized = atomic(false)
@Suppress("FunctionName")
internal actual fun _MiraiImpl_static_init() {
if (!initialized.compareAndSet(expect = false, update = true)) return
MiraiCoreServices.registerAll()
}

View File

@ -0,0 +1,185 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.handler
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import net.mamoe.mirai.internal.network.components.PacketCodec
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.utils.PlatformSocket
import net.mamoe.mirai.internal.utils.connect
import net.mamoe.mirai.utils.childScope
import net.mamoe.mirai.utils.readPacketExact
import net.mamoe.mirai.utils.toLongUnsigned
internal class NativeNetworkHandler(
context: NetworkHandlerContext,
address: SocketAddress
) : CommonNetworkHandler<NativeNetworkHandler.NativeConn>(context, address) {
internal object Factory : NetworkHandlerFactory<NativeNetworkHandler> {
override fun create(context: NetworkHandlerContext, address: SocketAddress): NativeNetworkHandler {
return NativeNetworkHandler(context, address)
}
}
internal inner class NativeConn(
private val socket: PlatformSocket,
) : Closeable, CoroutineScope by coroutineContext.childScope("NativeConn") {
private val decodePipeline: PacketDecodePipeline = PacketDecodePipeline(this.coroutineContext)
private val packetCodec: PacketCodec by lazy { context[PacketCodec] }
private val ssoProcessor: SsoProcessor by lazy { context[SsoProcessor] }
private val sendQueue: Channel<OutgoingPacket> = Channel(Channel.BUFFERED) { undelivered ->
launch { write(undelivered) }
}
private val lengthDelimitedPacketReader = LengthDelimitedPacketReader()
/**
* Not thread-safe
*/
private inner class LengthDelimitedPacketReader : Closeable {
private var missingLength: Long = 0
private val bufferedPackets: MutableList<ByteReadPacket> = ArrayList(10)
fun offer(packet: ByteReadPacket) {
missingLength -= packet.remaining
if (missingLength <= 0) {
emit()
}
}
fun emit() {
when (bufferedPackets.size) {
0 -> {}
1 -> {
val packet = bufferedPackets.first()
if (missingLength == 0L) {
packetCodec.decodeRaw(ssoProcessor.ssoSession, packet)
} else {
check(missingLength < 0L) { "Failed check: remainingLength < 0L" }
val previousPacketLength = missingLength + packet.remaining
decodePipeline.send(
packetCodec.decodeRaw(
ssoProcessor.ssoSession,
packet.readPacketExact(previousPacketLength.toInt())
)
)
// now packet contain new packet.
missingLength = packet.readInt().toLongUnsigned() - 4
bufferedPackets[0] = packet
}
}
else -> {
val combined: ByteReadPacket
if (missingLength == 0L) {
combined = buildPacket(bufferedPackets.sumOf { it.remaining }.toInt()) {
bufferedPackets.forEach { writePacket(it) }
}
bufferedPackets.clear()
} else {
val lastPacket = bufferedPackets.last()
val previousPacketPartLength = missingLength + lastPacket.remaining
val combinedLength =
(bufferedPackets.sumOf { it.remaining } - lastPacket.remaining + previousPacketPartLength).toInt()
combined = buildPacket(combinedLength) {
repeat(bufferedPackets.size - 1) { i ->
writePacket(bufferedPackets[i])
}
writePacket(lastPacket, previousPacketPartLength)
}
bufferedPackets.clear()
// now packet contain new packet.
missingLength = lastPacket.readInt().toLongUnsigned() - 4
bufferedPackets.add(lastPacket)
}
decodePipeline.send(
packetCodec.decodeRaw(
ssoProcessor.ssoSession,
combined
)
)
}
}
}
override fun close() {
bufferedPackets.forEach { it.close() }
}
}
private val sender = launch {
while (isActive) {
val result = sendQueue.receiveCatching()
result.onFailure { if (it is CancellationException) return@launch }
result.getOrNull()?.let { packet ->
try {
socket.send(packet.delegate, 0, packet.delegate.size)
} catch (e: Throwable) {
if (e is CancellationException) return@launch
logger.error("Error while sending packet '${packet.commandName}'", e)
}
}
}
}
private val receiver = launch {
while (isActive) {
try {
val packet = socket.read()
lengthDelimitedPacketReader.offer(packet)
} catch (e: Throwable) {
if (e is CancellationException) return@launch
logger.error("Error while reading packet.", e)
}
}
}
fun write(packet: OutgoingPacket) {
sendQueue.trySend(packet).onFailure {
throw it
?: throw IllegalStateException("Failed to send packet '${packet.commandName}' without reason.")
}
}
override fun close() {
cancel()
}
}
override suspend fun createConnection(): NativeConn {
return NativeConn(PlatformSocket.connect(address))
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
override fun NativeConn.close() {
this.close()
}
override fun NativeConn.writeAndFlushOrCloseAsync(packet: OutgoingPacket) {
write(packet)
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.handler
/**
* Factory for a specific [NetworkHandler] implementation.
*/
internal actual fun interface NetworkHandlerFactory<out H : NetworkHandler> {
actual fun create(
context: NetworkHandlerContext,
host: String,
port: Int
): H = create(context, SocketAddressImpl(host, port))
/**
* Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED]
*/
actual fun create(
context: NetworkHandlerContext,
address: SocketAddress
): H
actual companion object {
actual fun getPlatformDefault(): NetworkHandlerFactory<*> = NativeNetworkHandler.Factory
}
}

View File

@ -9,41 +9,19 @@
package net.mamoe.mirai.internal.network.handler
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
internal actual abstract class SocketAddress(
actual val host: String,
actual val port: Int,
val host: String,
val port: Int,
@Suppress("UNUSED_PARAMETER") constructorMarker: Unit?, // avoid ambiguity with function SocketAddress
)
internal actual fun SocketAddress.getHost(): String = host
internal actual fun SocketAddress.getPort(): Int = port
internal class SocketAddressImpl(host: String, port: Int) : SocketAddress(host, port, null)
internal actual fun SocketAddress(host: String, port: Int): SocketAddress {
internal actual fun createSocketAddress(host: String, port: Int): SocketAddress {
return SocketAddressImpl(host, port)
}
/**
* Factory for a specific [NetworkHandler] implementation.
*/
internal actual fun interface NetworkHandlerFactory<out H : NetworkHandler> {
actual fun create(
context: NetworkHandlerContext,
host: String,
port: Int
): H = create(context, SocketAddressImpl(host, port))
/**
* Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED]
*/
actual fun create(
context: NetworkHandlerContext,
address: SocketAddress
): H
actual companion object {
actual fun getPlatformDefault(): NetworkHandlerFactory<*> {
TODO("Not yet implemented")
}
}
}

View File

@ -9,58 +9,7 @@
package net.mamoe.mirai.internal.utils
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
/**
* TCP Socket.
*/
internal actual class PlatformSocket : Closeable, HighwayProtocolChannel {
actual val isOpen: Boolean
get() = TODO("Not yet implemented")
actual override fun close() {
}
actual suspend fun send(packet: ByteArray, offset: Int, length: Int) {
}
/**
* @throws SendPacketInternalException
*/
actual override suspend fun send(packet: ByteReadPacket) {
}
/**
* @throws ReadPacketInternalException
*/
actual override suspend fun read(): ByteReadPacket {
TODO("Not yet implemented")
}
actual suspend fun connect(serverHost: String, serverPort: Int) {
}
actual companion object {
actual suspend fun connect(
serverIp: String,
serverPort: Int
): PlatformSocket {
TODO("Not yet implemented")
}
actual suspend inline fun <R> withConnection(
serverIp: String,
serverPort: Int,
block: PlatformSocket.() -> R
): R {
TODO("Not yet implemented")
}
}
}
internal actual class SocketException : IOException {
actual constructor() : super("", null)
@ -76,4 +25,4 @@ internal actual class NoRouteToHostException : IOException {
internal actual class UnknownHostException : IOException {
actual constructor() : super("")
actual constructor(message: String) : super(message)
}
}

View File

@ -10,11 +10,13 @@
package net.mamoe.mirai.internal.test
import net.mamoe.mirai.IMirai
import net.mamoe.mirai.internal.initMirai
import net.mamoe.mirai.utils.setSystemProp
import kotlin.test.Test
internal actual fun initPlatform() {
initMirai()
}
internal actual class PlatformInitializationTest actual constructor() : AbstractTest() {
@ -27,6 +29,9 @@ internal actual class PlatformInitializationTest actual constructor() : Abstract
* All test classes should inherit from [AbstractTest]
*/
internal actual abstract class AbstractTest actual constructor() : CommonAbstractTest() {
init {
Companion
}
actual companion object {
init {

View File

@ -0,0 +1,10 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal

View File

@ -0,0 +1,146 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.utils
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import kotlinx.cinterop.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel
import net.mamoe.mirai.internal.network.protocol.packet.login.toIpV4Long
import net.mamoe.mirai.utils.DEFAULT_BUFFER_SIZE
import net.mamoe.mirai.utils.toReadPacket
import net.mamoe.mirai.utils.wrapIO
import platform.posix.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* TCP Socket.
*/
@OptIn(UnsafeNumber::class)
internal actual class PlatformSocket(
private val socket: Int
) : Closeable, HighwayProtocolChannel {
@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher: CoroutineDispatcher = newSingleThreadContext("PlatformSocket#$socket.dispatcher")
private val readLock = Mutex()
private val readBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
private val writeLock = Mutex()
private val writeBuffer = ByteArray(DEFAULT_BUFFER_SIZE).pin()
actual val isOpen: Boolean
get() = write(socket, null, 0).convert<Long>() != 0L
@OptIn(ExperimentalIoApi::class)
actual override fun close() {
if (close(socket) != 0) {
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
}
}
@OptIn(ExperimentalIoApi::class)
actual suspend fun send(packet: ByteArray, offset: Int, length: Int): Unit = readLock.withLock {
withContext(dispatcher) {
require(offset >= 0) { "offset must >= 0" }
require(length >= 0) { "length must >= 0" }
require(offset + length <= packet.size) { "It must follows offset + length <= packet.size" }
packet.usePinned { pin ->
if (write(socket, pin.addressOf(offset), length.convert()).convert<Long>() != 0L) {
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
}
}
}
}
/**
* @throws SendPacketInternalException
*/
@OptIn(ExperimentalIoApi::class)
actual override suspend fun send(packet: ByteReadPacket): Unit = readLock.withLock {
withContext(dispatcher) {
val writeBuffer = writeBuffer
val length = packet.readAvailable(writeBuffer.get())
if (write(socket, writeBuffer.addressOf(0), length.convert()).convert<Long>() != 0L) {
throw PosixException.forErrno(posixFunctionName = "close()").wrapIO()
}
}
}
/**
* @throws ReadPacketInternalException
*/
actual override suspend fun read(): ByteReadPacket = writeLock.withLock {
withContext(dispatcher) {
val readBuffer = readBuffer
val length = read(socket, readBuffer.addressOf(0), readBuffer.get().size.convert()).convert<Long>()
readBuffer.get().toReadPacket(length = length.toInt())
}
}
actual companion object {
@OptIn(UnsafeNumber::class, ExperimentalIoApi::class)
actual suspend fun connect(
serverIp: String,
serverPort: Int
): PlatformSocket {
val addr = memScoped {
alloc<sockaddr_in>() {
sin_family = AF_INET.convert()
resolveIpFromHost(serverIp)
sin_addr.s_addr = resolveIpFromHost(serverIp)
}
}.reinterpret<sockaddr>()
val id = socket(AF_INET, 1 /* SOCKET_STREAM */, IPPROTO_TCP)
if (id != 0) throw PosixException.forErrno(posixFunctionName = "socket()")
val conn = connect(id, addr.ptr, sizeOf<sockaddr_in>().convert())
if (conn != 0) throw PosixException.forErrno(posixFunctionName = "connect()")
return PlatformSocket(conn)
}
private fun resolveIpFromHost(serverIp: String): UInt {
val host = gethostbyname(serverIp)
?: throw IllegalStateException("Failed to resolve IP from host. host=$serverIp")
val str = try {
val hAddrList = host.pointed.h_addr_list
?: throw IllegalStateException("Empty IP list resolved from host. host=$serverIp")
hAddrList[0]!!.toKString()
} finally {
free(host)
}
// TODO: 2022/5/30 check memory
return str.toIpV4Long().toUInt()
}
actual suspend inline fun <R> withConnection(
serverIp: String,
serverPort: Int,
block: PlatformSocket.() -> R
): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return connect(serverIp, serverPort).use(block)
}
}
}