abstract server connection more

Server connection is now stateless and is atm always a network connection. It might something else in the future
This commit is contained in:
Moritz Zwerger 2024-08-01 18:13:34 +02:00
parent 57a8985a94
commit d42f9eb214
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
33 changed files with 312 additions and 213 deletions

View File

@ -1,39 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.parser.minosoft.session.selector.properties
import de.bixilon.minosoft.commands.errors.ExpectedArgumentError
import de.bixilon.minosoft.commands.parser.brigadier.bool.BooleanParser.readBoolean
import de.bixilon.minosoft.commands.parser.selector.TargetProperty
import de.bixilon.minosoft.commands.parser.selector.TargetPropertyFactory
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
class ConnectedProperty(
val connected: Boolean,
) : TargetProperty<PlaySession> {
override fun passes(value: PlaySession): Boolean {
val connected = value.network.connected
return connected == this.connected
}
companion object : TargetPropertyFactory<PlaySession> {
override val name: String = "connected"
override fun read(reader: CommandReader): ConnectedProperty {
return ConnectedProperty(reader.readBoolean() ?: throw ExpectedArgumentError(reader))
}
}
}

View File

@ -20,6 +20,5 @@ object SessionTargetProperties : TargetProperties<PlaySession>() {
init {
register(StateProperty)
register(ConnectedProperty)
}
}

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.data.entities.entities.player.local
import de.bixilon.kutil.cast.CastUtil.nullCast
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
import de.bixilon.kutil.concurrent.schedule.TaskScheduler.runLater
import de.bixilon.kutil.latch.AbstractLatch
@ -23,6 +24,7 @@ import de.bixilon.minosoft.data.chat.signature.ChatSignatureProperties
import de.bixilon.minosoft.data.chat.signature.errors.KeyExpiredError
import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageEvent
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.protocol.packets.c2s.play.SessionDataC2SP
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
@ -46,7 +48,8 @@ class SignatureKeyManagement(
private fun registerRefresh(millis: Int) {
runLater(millis) {
if (session.error != null || (session.established && !session.network.connected) || (session.network.connected && !session.network.encrypted)) {
val connected = session.connection.nullCast<NetworkConnection>()?.state != null
if (session.error != null || (session.established && !connected) || (connected && !session.network.encrypted)) {
// session is dead
return@runLater
}
@ -89,7 +92,7 @@ class SignatureKeyManagement(
if (session.version.versionId < ProtocolVersions.V_22W43A) {
return
}
if (session.network.state != ProtocolStates.PLAY || !session.network.connected) {
if (session.connection !is NetworkConnection || session.connection.state != ProtocolStates.PLAY) {
return
}
if (!session.network.encrypted) {

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.example
import de.bixilon.kutil.cast.CastUtil.nullCast
import de.bixilon.kutil.observer.DataObserver.Companion.observe
import de.bixilon.kutil.stream.InputStreamUtil.readAsString
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageEvent
@ -20,6 +21,7 @@ import de.bixilon.minosoft.modding.event.events.session.play.PlaySessionCreateEv
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
import de.bixilon.minosoft.modding.event.master.GlobalEventMaster
import de.bixilon.minosoft.modding.loader.mod.ModMain
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.util.KUtil.toResourceLocation
@ -37,7 +39,8 @@ object ExampleMod : ModMain() {
}
private fun PlaySession.startListening() {
if (this.address.hostname != "localhost" && this.address.hostname != "127.0.0.1") {
val address = this.connection.nullCast<NetworkConnection>()?.address ?: return
if (address.hostname != "localhost" && address.hostname != "127.0.0.1") {
return
}
events.listen<ChatMessageEvent> {

View File

@ -45,6 +45,7 @@ import de.bixilon.minosoft.gui.eros.modding.invoker.JavaFXEventListener
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.ctext
import de.bixilon.minosoft.modding.event.events.KickEvent
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.protocol.network.session.play.PlaySessionStates
import de.bixilon.minosoft.protocol.network.session.play.PlaySessionStates.Companion.disconnected
@ -136,11 +137,12 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
for ((type, name) in server.profiles) {
override[ProfileManagers[type]?.type ?: continue] = name
}
val profiles = SelectedProfiles(override)
val session = PlaySession(
address = ping.realAddress ?: DNSUtil.getServerAddress(server.address),
connection = NetworkConnection(ping.connection?.address ?: DNSUtil.getServerAddress(server.address), native = profiles.other.nativeNetwork),
account = account,
version = version,
profiles = SelectedProfiles(override)
profiles = profiles
)
account.sessions[server] = session
serverCard.sessions += session
@ -151,14 +153,14 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
serverCard.sessions -= session
}
if (ErosProfileManager.selected.general.hideErosOnceConnected) {
if (session.network.connected) {
if (session.connection.active) {
if (session.state == PlaySessionStates.PLAYING) {
Eros.setVisibility(false)
}
} else {
var connected = false
for (entry in PlaySession.ACTIVE_CONNECTIONS.toSynchronizedSet()) {
if (entry.network.connected) {
if (entry.connection.active) {
connected = true
break
}
@ -313,7 +315,8 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
// ToDo: server.connections.clear()
ping.terminate()
ping.address = server.address
ping.reset()
ping.hostname = server.address
ping.ping()
}
JavaFXUtil.runLater { refreshList() }
@ -425,7 +428,7 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
private val SERVER_INFO_PROPERTIES: List<Pair<ResourceLocation, (ServerCard) -> Any?>> = listOf(
"minosoft:server_info.server_name".toResourceLocation() to { it.server.name },
"minosoft:server_info.server_address".toResourceLocation() to { it.server.address },
"minosoft:server_info.real_server_address".toResourceLocation() to { it.ping.realAddress },
"minosoft:server_info.real_server_address".toResourceLocation() to { it.ping.connection?.address },
"minosoft:server_info.forced_version".toResourceLocation() to { it.server.forcedVersion },
TranslatableComponents.GENERAL_EMPTY to { " " },

View File

@ -62,7 +62,7 @@ object CustomServerType : ServerType {
continue
}
DefaultThreadPool += ForcePooledRunnable {
ping.network.disconnect()
ping.terminate()
ping.ping()
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -65,6 +65,7 @@ class FadingTextElement(
fun show() {
val time = millis()
// TODO: extend time on call if already showing text
val phase = times.createPhase(time)
this.phase = phase
updateSize(phase)

View File

@ -129,7 +129,7 @@ class DebugHUDElement(guiRenderer: GUIRenderer) : Element(guiRenderer), Layouted
layout += LineSpacerElement(guiRenderer)
layout += TextElement(guiRenderer, BaseComponent("Account ", session.account.username))
layout += TextElement(guiRenderer, BaseComponent("Address ", session.address))
layout += TextElement(guiRenderer, BaseComponent("Address ", session.connection.identifier))
layout += TextElement(guiRenderer, BaseComponent("Network version ", session.version))
layout += TextElement(guiRenderer, BaseComponent("Server brand ", session.serverInfo.brand)).apply { session.serverInfo::brand.observe(this@DebugHUDElement) { this.text = BaseComponent("Server brand ", it.truncate(50)) } }

View File

@ -107,7 +107,7 @@ class ScreenshotTaker(
val size = Vec2i(context.window.size)
val buffer = context.system.readPixels(Vec2i.EMPTY_INSTANCE, size)
val path = RunConfiguration.HOME_DIRECTORY.resolve("screenshots").resolve(context.session.address.hostname)
val path = RunConfiguration.HOME_DIRECTORY.resolve("screenshots").resolve(context.session.connection.identifier)
val time = millis()
DefaultThreadPool += ForcePooledRunnable(priority = ThreadPool.HIGHER) { store(buffer, path, time) }
} catch (exception: Exception) {

View File

@ -0,0 +1,35 @@
/*
* Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.util.DNSUtil
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogMessageType
class AddressResolver(
val address: String,
) {
private var candidates: MutableList<ServerAddress> = DNSUtil.resolveServerAddress(address).toMutableList()
fun tryNext(): NetworkConnection? {
if (candidates.isEmpty()) return null
val address = candidates.removeAt(0)
Log.log(LogMessageType.NETWORK) { "Trying address: $address" }
return NetworkConnection(address, false)
}
}

View File

@ -0,0 +1,60 @@
/*
* Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol.connection
import de.bixilon.kutil.concurrent.lock.thread.ThreadLock
import de.bixilon.kutil.observer.DataObserver
import de.bixilon.kutil.observer.DataObserver.Companion.observe
import de.bixilon.kutil.observer.DataObserver.Companion.observed
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
import de.bixilon.minosoft.protocol.network.network.client.netty.NettyClient
import de.bixilon.minosoft.protocol.network.session.Session
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
class NetworkConnection(
val address: ServerAddress,
val native: Boolean,
) : ServerConnection {
var client: ClientNetwork? = null
private set
override val identifier = address.toString()
override var active by observed(false)
private set
var state: ProtocolStates? by DataObserver(null, ThreadLock())
init {
this::state.observe(this) { active = it != null }
}
override fun connect(session: Session) {
Log.log(LogMessageType.NETWORK, level = LogLevels.INFO) { "Connecting to server: $address" }
if (client != null) throw IllegalStateException("Already connected???")
val netty = NettyClient(this, session)
this.client = netty
netty.connect()
}
override fun disconnect() {
client?.disconnect()
}
override fun send(packet: C2SPacket) {
client?.send(packet)
}
}

View File

@ -0,0 +1,28 @@
/*
* Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol.connection
import de.bixilon.minosoft.protocol.network.session.Session
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
interface ServerConnection {
val identifier: String
val active: Boolean
fun connect(session: Session)
fun disconnect()
fun send(packet: C2SPacket)
}

View File

@ -13,25 +13,23 @@
package de.bixilon.minosoft.protocol.network.network.client
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.PacketReceiver
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender.PacketSender
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
import javax.crypto.Cipher
interface ClientNetwork {
val connected: Boolean
val connection: NetworkConnection
val encrypted: Boolean
val detached: Boolean
var state: ProtocolStates
val compressionThreshold: Int
val receiver: PacketReceiver
val sender: PacketSender
fun connect(address: ServerAddress, native: Boolean)
fun connect()
fun disconnect()
fun detach()

View File

@ -15,9 +15,9 @@ package de.bixilon.minosoft.protocol.network.network.client.netty
import de.bixilon.kutil.cast.CastUtil.nullCast
import de.bixilon.kutil.exception.ExceptionUtil.catchAll
import de.bixilon.kutil.observer.DataObserver.Companion.observed
import de.bixilon.kutil.observer.DataObserver.Companion.observe
import de.bixilon.minosoft.config.profile.profiles.other.OtherProfileManager
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.NetworkException
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketHandleException
@ -51,14 +51,13 @@ import javax.crypto.Cipher
@ChannelHandler.Sharable
class NettyClient(
override val connection: NetworkConnection,
val session: Session,
) : SimpleChannelInboundHandler<Any>(), ClientNetwork {
@Deprecated("unused")
var state = ProtocolStates.HANDSHAKE // TODO
override val sender = PacketSender(this)
override val receiver = PacketReceiver(this, session)
private var address: ServerAddress? = null
override var connected by observed(false)
private set
override var state by observed(ProtocolStates.HANDSHAKE)
override var compressionThreshold = -1
override var encrypted: Boolean = false
private set
@ -66,20 +65,22 @@ class NettyClient(
override var detached = false
private set
override fun connect(address: ServerAddress, native: Boolean) {
this.address = address
state = ProtocolStates.HANDSHAKE
val natives = if (native) TransportNatives.get() else NioNatives
init {
connection::state.observe(this) { this.state = it ?: ProtocolStates.HANDSHAKE }
}
override fun connect() {
val natives = if (connection.native) TransportNatives.get() else NioNatives
val bootstrap = Bootstrap()
.group(natives.pool)
.channel(natives.channel)
.handler(NetworkPipeline(this))
val address = connection.address
val future = bootstrap.connect(address.hostname, address.port)
future.addListener {
if (!it.isSuccess) {
handleError(it.cause())
}
if (it.isSuccess) return@addListener
handleError(it.cause())
}
}
@ -118,7 +119,7 @@ class NettyClient(
encrypted = false
channel = null
compressionThreshold = -1
connected = false
connection.state = null
}
override fun detach() {
@ -140,15 +141,17 @@ class NettyClient(
override fun channelActive(context: ChannelHandlerContext) {
catchAll { context.channel().config().setOption(ChannelOption.TCP_NODELAY, true) }
context.channel().config().isAutoRead = true
this.channel = context.channel()
connected = true
val channel = context.channel()
this.channel = channel
connection.state = ProtocolStates.HANDSHAKE
channel.config().isAutoRead = true
}
override fun channelInactive(context: ChannelHandlerContext) {
Log.log(LogMessageType.NETWORK, LogLevels.VERBOSE) { "Session closed ($address)" }
Log.log(LogMessageType.NETWORK, LogLevels.VERBOSE) { "Session closed (${connection.address})" }
if (detached) return
connected = false
this.channel = null
connection.state = null
}
override fun handleError(error: Throwable) {
@ -158,11 +161,11 @@ class NettyClient(
} else if (cause is EncoderException) {
cause = error.cause ?: cause
}
if (RunConfiguration.DISABLE_EROS || session !is StatusSession) {
if (RunConfiguration.DISABLE_EROS || session is StatusSession) {
val log = if (cause is PacketHandleException || cause is PacketReadException) cause.cause else cause
Log.log(LogMessageType.NETWORK_IN, LogLevels.WARN) { log }
}
if (cause !is NetworkException || cause is CriticalNetworkException || state == ProtocolStates.LOGIN) {
if (cause !is NetworkException || cause is CriticalNetworkException || connection.state == ProtocolStates.LOGIN) {
session.error = cause
disconnect()
return
@ -175,7 +178,7 @@ class NettyClient(
private fun getChannel(): Channel? {
val channel = this.channel
if (!connected || channel == null) {
if (channel == null || connection.state == null) {
return null
}
return channel

View File

@ -56,7 +56,7 @@ class PacketReceiver(
}
private fun tryHandle(type: PacketType, packet: S2CPacket) {
if (!network.connected) return
if (!network.connection.active) return
try {
handle(packet)
@ -93,7 +93,7 @@ class PacketReceiver(
fun onReceive(type: PacketType, packet: S2CPacket) {
if (network.detached) return
if (!network.connected) return
if (!network.connection.active) return
val discard = notify(packet)
if (discard) return

View File

@ -41,7 +41,7 @@ class PacketSender(
fun send(packet: C2SPacket) {
if (network.detached) return
if (!network.connected) return
if (!network.connection.active) return
val discard = notify(packet)
if (discard) return

View File

@ -38,7 +38,7 @@ class PacketDecoder(
val packetId = buffer.readVarInt()
val data = buffer.readRest()
val state = client.state
val state = client.connection.state ?: throw IllegalStateException("Not connected!")
val type = version?.s2c?.get(state, packetId) ?: DefaultPacketMapping.S2C_PACKET_MAPPING[state, packetId] ?: throw UnknownPacketIdException(packetId, state, version)

View File

@ -74,8 +74,8 @@ class PacketEncoder(
throw PacketNotAvailableException(type, state, version)
}
override fun encode(context: ChannelHandlerContext, packet: C2SPacket, out: MutableList<Any>) {
val state = client.state
private fun encode(packet: C2SPacket): ByteArray {
val state = client.connection.state!!
val type = DefaultPackets.C2S[state]?.get(packet::class) ?: throw UnknownPacketException(packet::class.java)
val id = getPacketId(version, state, type)
@ -86,7 +86,11 @@ class PacketEncoder(
data.writeVarInt(id)
data.writeBareByteArray(packetData.toArray())
out += data.toArray()
return data.toArray()
}
override fun encode(context: ChannelHandlerContext, packet: C2SPacket, out: MutableList<Any>) {
out += encode(packet)
}
companion object {

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -13,6 +13,7 @@
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.length
import de.bixilon.kutil.exception.FastException
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.ciritical.PacketTooLongException
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelHandlerContext
@ -76,6 +77,6 @@ class LengthDecoder(
return varInt
}
private class BufferTooShortException : Exception()
private class BufferTooShortException : FastException()
}
}

View File

@ -16,13 +16,10 @@ package de.bixilon.minosoft.protocol.network.session
import de.bixilon.kutil.observer.DataObserver.Companion.observed
import de.bixilon.minosoft.modding.event.master.EventMaster
import de.bixilon.minosoft.modding.event.master.GlobalEventMaster
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
import de.bixilon.minosoft.protocol.network.network.client.netty.NettyClient
import de.bixilon.minosoft.protocol.versions.Version
import java.util.concurrent.atomic.AtomicInteger
abstract class Session {
val network: ClientNetwork = NettyClient(this)
val events = EventMaster(GlobalEventMaster)
val id = Session.id.getAndIncrement()
var established = false
@ -31,9 +28,7 @@ abstract class Session {
var error: Throwable? by observed(null)
open fun terminate() {
network.disconnect()
}
abstract fun terminate()
companion object {
private val id = AtomicInteger()

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.protocol.network.session.play
import de.bixilon.kutil.cast.CastUtil.unsafeCast
import de.bixilon.kutil.cast.CastUtil.unsafeNull
import de.bixilon.kutil.collections.CollectionUtil.synchronizedSetOf
import de.bixilon.kutil.collections.CollectionUtil.toSynchronizedSet
@ -49,10 +50,12 @@ import de.bixilon.minosoft.gui.rendering.Rendering
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageEvent
import de.bixilon.minosoft.modding.event.events.loading.RegistriesLoadEvent
import de.bixilon.minosoft.modding.event.events.session.play.PlaySessionCreateEvent
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
import de.bixilon.minosoft.modding.event.master.GlobalEventMaster
import de.bixilon.minosoft.modding.loader.phase.DefaultModPhases
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.connection.ServerConnection
import de.bixilon.minosoft.protocol.network.network.client.netty.NettyClient
import de.bixilon.minosoft.protocol.network.session.Session
import de.bixilon.minosoft.protocol.network.session.play.channel.DefaultChannelHandlers
import de.bixilon.minosoft.protocol.network.session.play.channel.SessionChannelHandler
@ -75,11 +78,13 @@ import java.util.concurrent.atomic.AtomicInteger
class PlaySession(
val address: ServerAddress,
val connection: ServerConnection,
val account: Account,
override val version: Version,
val profiles: SelectedProfiles = SelectedProfiles(),
) : Session() {
@Deprecated("connection")
val network = NettyClient(connection.unsafeCast(), this)
val sessionId = KUtil.secureRandomUUID()
val settingsManager = ClientSettingsManager(this)
val registries = Registries().apply { updateFlattened(version.flattened) }
@ -127,15 +132,16 @@ class PlaySession(
RegistriesFixer.register(this)
DefaultChannelHandlers.register(this)
network::connected.observe(this) {
if (it) {
connection.unsafeCast<NetworkConnection>()::state.observe(this) {
if (it != null) {
ACTIVE_CONNECTIONS += this
ERRORED_CONNECTIONS -= this
state = PlaySessionStates.HANDSHAKING
network.send(HandshakeC2SP(address, HandshakeC2SP.Actions.PLAY, version.protocolId))
val address = connection.unsafeCast<NetworkConnection>().address
network.send(HandshakeC2SP(address.hostname, address.port, HandshakeC2SP.Actions.PLAY, version.protocolId))
// after sending it, switch to next state
network.state = ProtocolStates.LOGIN
network.connection.state = ProtocolStates.LOGIN
} else {
established = true
assetsManager.unload()
@ -146,7 +152,7 @@ class PlaySession(
}
}
}
network::state.observe(this) { state ->
connection.unsafeCast<NetworkConnection>()::state.observe(this) { state ->
when (state) {
ProtocolStates.HANDSHAKE, ProtocolStates.STATUS -> Broken("Invalid state!")
ProtocolStates.LOGIN -> {
@ -162,17 +168,17 @@ class PlaySession(
CLI.session = this
}
events.register(CallbackEventListener.of<ChatMessageEvent> {
events.listen<ChatMessageEvent> {
val additionalPrefix = when (it.message.type.position) {
ChatTextPositions.SYSTEM -> "[SYSTEM] "
ChatTextPositions.HOTBAR -> "[HOTBAR] "
else -> ""
}
Log.log(LogMessageType.CHAT_IN, level = if (it.message.type.position == ChatTextPositions.HOTBAR) LogLevels.VERBOSE else LogLevels.INFO, prefix = ChatComponent.of(additionalPrefix)) { it.message.text }
})
}
}
ProtocolStates.CONFIGURATION -> Unit
else -> Unit
}
}
ticker.init()
@ -206,7 +212,7 @@ class PlaySession(
}
val keyManagement = SignatureKeyManagement(this, account)
if (version.requiresSignedChat && !profiles.session.signature.disableKeys) {
if (version.requiresSignedChat && !profiles.session.signature.disableKeys && connection is NetworkConnection) {
taskWorker += WorkerTask(optional = true) { keyManagement.init(latch) }
}
@ -242,8 +248,7 @@ class PlaySession(
private fun establish(latch: AbstractLatch?) {
latch?.dec() // remove initial value
Log.log(LogMessageType.NETWORK, level = LogLevels.INFO) { "Connecting to server: $address" }
network.connect(address, profiles.other.nativeNetwork)
network.connect()
state = PlaySessionStates.ESTABLISHING
}
@ -260,7 +265,7 @@ class PlaySession(
}
override fun terminate() {
super.terminate()
network.disconnect()
state = PlaySessionStates.DISCONNECTED
}

View File

@ -17,7 +17,8 @@ import de.bixilon.kutil.observer.DataObserver.Companion.observe
import de.bixilon.kutil.observer.DataObserver.Companion.observed
import de.bixilon.minosoft.modding.event.events.session.status.StatusSessionCreateEvent
import de.bixilon.minosoft.modding.event.master.GlobalEventMaster
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.AddressResolver
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.Session
import de.bixilon.minosoft.protocol.packets.c2s.handshake.HandshakeC2SP
import de.bixilon.minosoft.protocol.packets.c2s.status.StatusRequestC2SP
@ -27,132 +28,111 @@ import de.bixilon.minosoft.protocol.status.StatusPing
import de.bixilon.minosoft.protocol.status.StatusPong
import de.bixilon.minosoft.protocol.versions.Version
import de.bixilon.minosoft.protocol.versions.Versions
import de.bixilon.minosoft.util.DNSUtil
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogMessageType
class StatusSession(
var address: String,
var hostname: String,
var forcedVersion: Version? = null,
) : Session() {
var connection: NetworkConnection? = null
private var resolver: AddressResolver? = null
var status: ServerStatus? by observed(null)
var ping: StatusPing? by observed(null)
var pong: StatusPong? by observed(null)
var realAddress: ServerAddress? = null
private set
private var addresses: List<ServerAddress>? = null
private var addressIndex = 0
var serverVersion: Version? = null
var state by observed(StatusSessionStates.WAITING)
val timeout = TimeoutHandler(this)
init {
this::error.observe(this) {
if (it == null) return@observe
ping = null
status = null
terminate()
reset()
state = StatusSessionStates.ERROR
timeout.cancel()
network.disconnect()
}
network::connected.observe(this) {
if (it) {
state = StatusSessionStates.HANDSHAKING
network.send(HandshakeC2SP(realAddress!!, HandshakeC2SP.Actions.STATUS, forcedVersion?.protocolId ?: Versions.AUTOMATIC.protocolId))
network.state = ProtocolStates.STATUS
return@observe
}
if (status != null) {
return@observe
}
tryNextAddress()
}
network::state.observe(this) {
when (it) {
ProtocolStates.HANDSHAKE -> {}
ProtocolStates.PLAY, ProtocolStates.LOGIN, ProtocolStates.CONFIGURATION -> throw IllegalStateException("Invalid state!")
ProtocolStates.STATUS -> {
state = StatusSessionStates.QUERYING_STATUS
network.send(StatusRequestC2SP())
}
}
}
GlobalEventMaster.fire(StatusSessionCreateEvent(this))
}
private fun tryNextAddress() {
val addresses = this.addresses ?: return
val nextIndex = ++addressIndex
if (addresses.size > nextIndex) {
val nextAddress = addresses[nextIndex]
Log.log(LogMessageType.NETWORK) { "Could not connect to $address, trying next hostname: $nextAddress" }
realAddress = nextAddress
ping(nextAddress)
this::error.observe(this) {
if (it == null) return@observe
tryNext()
}
}
private fun resolve(): List<ServerAddress> {
state = StatusSessionStates.RESOLVING
private fun NetworkConnection.register() {
this::state.observe(this) {
if (this@StatusSession.connection != this) return@observe
when (it) {
ProtocolStates.HANDSHAKE -> {
// send handshake
send(HandshakeC2SP(hostname, this.address.port, HandshakeC2SP.Actions.STATUS, forcedVersion?.protocolId ?: Versions.AUTOMATIC.protocolId))
state = ProtocolStates.STATUS
}
var addresses = this.addresses
if (addresses == null) {
addresses = DNSUtil.resolveServerAddress(address)
realAddress = addresses.first()
this.addresses = addresses
this.addressIndex = 0
ProtocolStates.STATUS -> {
this@StatusSession.state = StatusSessionStates.QUERYING_STATUS
send(StatusRequestC2SP())
}
null -> Unit // done
else -> throw IllegalStateException("Illegal status state: $it")
}
}
return addresses
}
override fun terminate() {
timeout.cancel()
this.connection?.disconnect()
this.connection = null
state = StatusSessionStates.WAITING
}
fun reset() {
timeout.cancel()
realAddress = null
this.addresses = null
this.addressIndex = 0
status = null
ping = null
pong = null
serverVersion = null
error = null
state = StatusSessionStates.WAITING
}
fun ping(address: ServerAddress) {
if (state == StatusSessionStates.ESTABLISHING || network.connected) {
private fun tryNext(): Boolean {
val network = resolver!!.tryNext() ?: return false
this.connection = network
if (state == StatusSessionStates.ESTABLISHING) {
error("Already connecting!")
}
timeout.register()
Log.log(LogMessageType.NETWORK) { "Pinging $address (from ${this.address})" }
Log.log(LogMessageType.NETWORK) { "Pinging ${network.address} (from ${this.hostname})" }
state = StatusSessionStates.ESTABLISHING
network.connect(address, false)
network.register()
network.connect(this)
return true
}
fun ping() {
if (state == StatusSessionStates.RESOLVING || state == StatusSessionStates.ESTABLISHING || network.connected) {
if (state == StatusSessionStates.RESOLVING || state == StatusSessionStates.ESTABLISHING) {
error("Already connecting!")
}
terminate()
reset()
state = StatusSessionStates.RESOLVING
val addresses: List<ServerAddress>
try {
// TODO: Don't resolve if address is ip
addresses = resolve()
} catch (exception: Exception) {
Log.log(LogMessageType.NETWORK) { "Can not resolve ${this.address}" }
val resolver = AddressResolver(hostname)
this.resolver = resolver
} catch (error: Exception) {
Log.log(LogMessageType.NETWORK) { "Can not resolve ${this.hostname}" }
return
}
ping(addresses.first())
}
override fun terminate() {
super.terminate()
state = StatusSessionStates.WAITING
tryNext()
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -12,7 +12,6 @@
*/
package de.bixilon.minosoft.protocol.packets.c2s.handshake
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.protocol.protocol.buffers.OutByteBuffer
@ -21,20 +20,21 @@ import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
class HandshakeC2SP(
val address: ServerAddress,
val hostname: String,
val port: Int,
val action: Actions = Actions.STATUS,
val protocolId: Int = ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID,
) : C2SPacket {
override fun write(buffer: OutByteBuffer) {
buffer.writeVarInt(protocolId)
buffer.writeString(address.hostname)
buffer.writeShort(address.port)
buffer.writeString(hostname)
buffer.writeShort(port)
buffer.writeVarInt(action.protocolId)
}
override fun log(reducedLog: Boolean) {
Log.log(LogMessageType.NETWORK_OUT, LogLevels.VERBOSE) { "Handshake (protocolId=$protocolId, hostname=${address.hostname}, port=${address.port}, action=$action)" }
Log.log(LogMessageType.NETWORK_OUT, LogLevels.VERBOSE) { "Handshake (protocolId=$protocolId, hostname=${hostname}, port=${port}, action=$action)" }
}
enum class Actions(val protocolId: Int) {

View File

@ -13,10 +13,13 @@
package de.bixilon.minosoft.protocol.packets.registry
import de.bixilon.kutil.cast.CastUtil.nullCast
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketBufferUnderflowException
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.implementation.PacketNotImplementedException
import de.bixilon.minosoft.protocol.network.session.Session
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.protocol.network.session.status.StatusSession
import de.bixilon.minosoft.protocol.packets.registry.factory.PacketFactory
import de.bixilon.minosoft.protocol.packets.types.Packet
import de.bixilon.minosoft.protocol.protocol.buffers.InByteBuffer
@ -31,7 +34,8 @@ class PacketType(
) {
fun create(data: ByteArray, session: Session): Packet {
val factory = this.factory ?: throw PacketNotImplementedException(name, session.network.state, session.version)
val connection = session.nullCast<StatusSession>()?.connection ?: session.nullCast<PlaySession>()?.connection?.nullCast<NetworkConnection>()
val factory = this.factory ?: throw PacketNotImplementedException(name, connection!!.state!!, session.version)
val buffer = if (session is PlaySession) PlayInByteBuffer(data, session) else InByteBuffer(data)
val packet = factory.create(buffer)

View File

@ -28,18 +28,18 @@ class KickS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
val reason: ChatComponent = if (buffer.session.network.state == ProtocolStates.LOGIN && buffer.versionId >= V_23W42A) buffer.readChatComponent() else buffer.readNbtChatComponent()
override fun handle(session: PlaySession) {
if (!session.network.connected) {
if (!session.connection.active) {
return // already disconnected, maybe timed out?
}
session.events.fire(KickEvent(session, reason))
// got kicked
session.network.disconnect()
session.terminate()
if (session.network.state == ProtocolStates.LOGIN) {
session.state = PlaySessionStates.ERROR
} else {
session.state = PlaySessionStates.KICKED
}
Log.log(LogMessageType.NETWORK, LogLevels.WARN) { "Kicked from ${session.address}: $reason" }
Log.log(LogMessageType.NETWORK, LogLevels.WARN) { "Kicked from ${session.connection.identifier}: $reason" }
}
override fun log(reducedLog: Boolean) {

View File

@ -28,7 +28,7 @@ class PongS2CP(buffer: InByteBuffer) : StatusS2CPacket {
override fun handle(session: StatusSession) {
val ping = session.ping ?: return
val latency = nanos() - ping.nanos
session.network.disconnect()
session.terminate()
session.pong = StatusPong(latency)
session.state = StatusSessionStates.PING_DONE
}

View File

@ -41,7 +41,7 @@ class StatusS2CP(buffer: InByteBuffer) : StatusS2CPacket {
val ping = StatusPing()
session.ping = ping
session.state = StatusSessionStates.QUERYING_PING
session.network.send(PingC2SP(ThreadLocalRandom.current().nextLong()))
session.connection?.send(PingC2SP(ThreadLocalRandom.current().nextLong()))
}
override fun log(reducedLog: Boolean) {

View File

@ -19,6 +19,7 @@ import de.bixilon.kutil.shutdown.ShutdownManager
import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.protocol.network.session.play.PlaySessionStates.Companion.disconnected
import de.bixilon.minosoft.protocol.network.session.status.StatusSession
@ -35,7 +36,7 @@ object AutoConnect {
private fun autoConnect(address: ServerAddress, version: Version, account: Account) {
val session = PlaySession(
address = address,
connection = NetworkConnection(address, true), // TODO: native network
account = account,
version = version,
)
@ -67,7 +68,7 @@ object AutoConnect {
if (version == Versions.AUTOMATIC) {
Log.log(LogMessageType.AUTO_CONNECT, LogLevels.INFO) { "Pinging server to get version..." }
val ping = StatusSession(address)
ping::status.observe(this) { autoConnect(ping.realAddress!!, ping.serverVersion ?: throw IllegalArgumentException("Could not determinate server's version!"), account) }
ping::status.observe(this) { autoConnect(ping.connection!!.address, ping.serverVersion ?: throw IllegalArgumentException("Could not determinate server's version!"), account) }
ping::error.observe(this) { exitProcess(1) }
ping.ping()
return

View File

@ -22,6 +22,7 @@ import de.bixilon.minosoft.commands.stack.print.PrintTarget
import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.protocol.address.ServerAddress
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.protocol.network.session.status.StatusSession
import de.bixilon.minosoft.protocol.versions.Version
@ -38,7 +39,7 @@ object ConnectCommand : Command {
if (version == null) {
stack.print.print("Pinging server to get version...")
val ping = StatusSession(address)
ping::status.observe(this) { connect(stack.print, ping.realAddress!!, ping.serverVersion ?: throw IllegalArgumentException("Could not determinate server's version!"), account) }
ping::status.observe(this) { connect(stack.print, ping.connection!!.address, ping.serverVersion ?: throw IllegalArgumentException("Could not determinate server's version!"), account) }
ping::error.observe(this) { stack.print.print("Could not ping $address: $it") }
ping.ping()
return@add
@ -52,7 +53,7 @@ object ConnectCommand : Command {
private fun connect(print: PrintTarget, address: ServerAddress, version: Version, account: Account) {
print.print("Connecting to $address")
val session = PlaySession(address, account, version)
val session = PlaySession(NetworkConnection(address, true), account, version) // TODO: native network
session.connect()
}
}

View File

@ -31,20 +31,20 @@ object SessionManageCommand : Command {
val filtered = it.collect()
if (filtered.isEmpty()) throw CommandException("No session matched your filter!")
it.print.print(table(filtered, "Id", "State", "Address") { c -> arrayOf(c.id, c.state, c.address) })
it.print.print(table(filtered, "Id", "State", "Address") { c -> arrayOf(c.id, c.state, c.connection.identifier) })
})
.addChild(ArgumentNode("filter", SessionParser, executable = true)),
LiteralNode("terminate", aliases = setOf("disconnect")).apply {
addFilter { stack, sessions ->
var count = 0
sessions.filter { it.network.connected }.forEach { it.terminate(); count++ }
sessions.forEach { it.terminate(); count++ }
stack.print.print("Terminated $count sessions.")
}
},
LiteralNode("select").apply {
addFilter(false) { stack, sessions ->
val session = sessions.first()
if (!session.network.connected) {
if (session.network.connection.state == null) {
throw CommandException("Session $session not established anymore!")
}
CLI.session = session

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger
* Copyright (C) 2020-2024 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -22,21 +22,22 @@ import org.xbill.DNS.Type
object DNSUtil {
fun resolveServerAddress(hostname: String): List<ServerAddress> {
val originalAddress = getServerAddress(hostname)
val original = getServerAddress(hostname)
// TODO: Don't resolve if address is ip address
if (":" in hostname) {
// port provided, skip srv check
return listOf(originalAddress)
return listOf(original)
}
val query = "_minecraft._tcp.$hostname"
val records = catchAll { Lookup(query, Type.SRV).run() } ?: return listOf(originalAddress)
val records = catchAll { Lookup(query, Type.SRV).run() } ?: return listOf(original)
val addresses: MutableList<ServerAddress> = mutableListOf()
for (record in records) {
if (record !is SRVRecord) continue
addresses += ServerAddress(record.target.toString(true), record.port)
}
addresses += originalAddress
addresses += original
return addresses
}

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.util.crash.section
import de.bixilon.minosoft.protocol.connection.NetworkConnection
import de.bixilon.minosoft.protocol.network.session.play.PlaySession
class SessionCrashSection(
@ -20,16 +21,19 @@ class SessionCrashSection(
) : ArrayCrashSection<PlaySession>("Sessions", sessions) {
override fun format(entry: PlaySession, builder: StringBuilder, intent: String) {
val connection = entry.connection
builder.appendProperty(intent, "Id", entry.id)
builder.appendProperty(intent, "Version", entry.version)
builder.appendProperty(intent, "Account", entry.account.username)
builder.appendProperty(intent, "Address", entry.address)
builder.appendProperty(intent, "Address", entry.connection.identifier)
builder.appendProperty(intent, "Brand", entry.serverInfo.brand)
builder.appendProperty(intent, "Events", entry.events.size)
builder.appendProperty(intent, "State", entry.state)
builder.appendProperty(intent, "DefaultPacketMapping state", entry.network.state)
builder.appendProperty(intent, "Compression threshold", entry.network.compressionThreshold)
builder.appendProperty(intent, "Encrypted", entry.network.encrypted)
if (connection is NetworkConnection) {
builder.appendProperty(intent, "Network state", connection.state)
builder.appendProperty(intent, "Compression threshold", connection.client?.compressionThreshold)
builder.appendProperty(intent, "Encrypted", connection.client?.encrypted)
}
builder.appendProperty(intent, "Was connected", entry.established)
builder.appendProperty(intent, "Rendering", entry.rendering != null)
builder.appendProperty(intent, "Error", entry.error)

View File

@ -318,6 +318,15 @@ internal class ChatComponentTest {
assertEquals(text, expected)
}
@Test
fun something() { // tree.ac
val string = """"§2Join the Other Server? Find it at §6Port 25566§2!""""
val chat = ChatComponent.of(string)
TODO()
}
private fun assertEquals(expected: ChatComponent, actual: ChatComponent) {
when (expected) {
is BaseComponent -> {