Skip to content

Commit

Permalink
feat: add initial server list ping implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MiniDigger committed Oct 3, 2024
1 parent 0a5fee8 commit 7a740b3
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 15 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ kotlin {
dependencies {
implementation(libs.bundles.ktor.server)
implementation(libs.bundles.ktor.client)
implementation(libs.ktor.network)

implementation(libs.adventure.minimessage)
implementation(libs.adventure.text.serializer.gson)
implementation(libs.adventure.text.serializer.legacy)
implementation(libs.cache4k)
implementation(libs.logback.classic)
}
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ spotless = { id = "com.diffplug.spotless", version = "6.25.0" }
[libraries]
adventure-minimessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" }
adventure-text-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" }
adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" }
cache4k = { group = "io.github.reactivecircus.cache4k", name = "cache4k", version = "0.13.0" }
kotlinx-html = { group = "org.jetbrains.kotlinx", name = "kotlinx-html", version = "0.8.0" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.7.3" }
Expand All @@ -24,6 +25,7 @@ ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.r
ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
ktor-server-caching-headers = { group = "io.ktor", name = "ktor-server-caching-headers", version.ref = "ktor" }
ktor-server-compression = { group = "io.ktor", name = "ktor-server-compression", version.ref = "ktor" }
ktor-network = { group = "io.ktor", name = "ktor-network", version.ref = "ktor" }
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version = "1.5.8" }

zKtlint = { group = "com.pinterest", name = "ktlint", version.ref = "ktlint"}
Expand Down
34 changes: 19 additions & 15 deletions src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/Application.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package net.kyori.adventure.webui.jvm

import io.ktor.http.CacheControl
import io.ktor.http.ContentType
import io.ktor.http.content.CachingOptions
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.plugins.compression.Compression
import io.ktor.server.plugins.compression.deflate
import io.ktor.server.plugins.compression.gzip
import io.ktor.server.routing.routing
import io.ktor.server.websocket.WebSockets
import io.ktor.server.websocket.pingPeriod
import io.ktor.server.websocket.timeout
import io.ktor.websocket.WebSocketDeflateExtension
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.plugins.compression.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.utils.io.*
import io.ktor.websocket.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.kyori.adventure.webui.jvm.minimessage.SocketTest
import okhttp3.internal.and
import java.time.Duration

public fun Application.main() {
Expand Down Expand Up @@ -48,8 +49,11 @@ public fun Application.main() {
trace { route -> this@main.log.debug(route.buildText()) }
}
}

SocketTest().main()
}


/** Reads a string value from the `config` block in `application.conf`. */
public fun Application.getConfigString(key: String): String =
environment.config.property("ktor.config.$key").getString()
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package net.kyori.adventure.webui.jvm.minimessage

import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.utils.io.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.kyori.adventure.text.minimessage.MiniMessage
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import okhttp3.internal.and
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import kotlin.text.Charsets.UTF_8

public class SocketTest {

public fun main() {
// TODO
// 1. make this non blocking somehow, idk how kotlin works
// 2. add api/ui to store into some cache
// 3. parse server address to get stuff from the cache and return that in the status response
runBlocking {
val serverSocket = aSocket(SelectorManager(Dispatchers.IO)).tcp().bind("127.0.0.1", 9002)
println("Server is listening at ${serverSocket.localAddress}")
while (true) {
val socket = serverSocket.accept()
println("Accepted ${socket.remoteAddress}")
launch {
try {
val receiveChannel = socket.openReadChannel()
val sendChannel = socket.openWriteChannel(autoFlush = true)

// handshake
val handshakePacket = receiveChannel.readMcPacket()
val protocolVersion = handshakePacket.readVarInt()
val serverAddress = handshakePacket.readUtf8String()
val serverPort = handshakePacket.readShort()
val nextState = handshakePacket.readVarInt()

if (nextState != 1) {
// send kick
sendChannel.writeMcPacket(0) {
it.writeString(
GsonComponentSerializer.gson()
.serialize(MiniMessage.miniMessage().deserialize("<red>You cant join here!"))
)
}
} else {
// send status response
sendChannel.writeMcPacket(0) {
it.writeString(
"""{
"version": {
"name": "${
LegacyComponentSerializer.legacySection()
.serialize(MiniMessage.miniMessage().deserialize("<rainbow>MiniMessage"))
}",
"protocol": 762
},
"description": ${
GsonComponentSerializer.gson().serialize(
MiniMessage.miniMessage().deserialize("<rainbow>MiniMessage is cool!")
)
}
}""".trimIndent()
)
}
}

sendChannel.close()
} catch (e: Exception) {
println(e)
}

socket.close()
return@launch
}
}
}
}
}

public suspend fun ByteWriteChannel.writeMcPacket(packetId: Int, consumer: (packet: DataOutputStream) -> Unit) {
val stream = ByteArrayOutputStream()
val packet = DataOutputStream(stream)

consumer.invoke(packet)

val data = stream.toByteArray()
writeVarInt(data.size + 1)
writeVarInt(packetId)
writeFully(data)
}

public fun DataOutputStream.writeString(string: String) {
val bytes = string.toByteArray(UTF_8)
writeVarInt(bytes.size)
write(bytes)
}

public fun DataOutputStream.writeVarInt(int: Int) {
var value = int
while (true) {
if ((value and 0x7F.inv()) == 0) {
writeByte(value)
return
}

writeByte((value and 0x7F) or 0x80)

value = value ushr 7
}
}

public suspend fun ByteWriteChannel.writeVarInt(int: Int) {
var value = int
while (true) {
if ((value and 0x7F.inv()) == 0) {
writeByte(value)
return
}

writeByte((value and 0x7F) or 0x80)

value = value ushr 7
}
}

public suspend fun ByteReadChannel.readMcPacket(): ByteReadChannel {
val length = readVarInt()
val packetId = readVarInt()
val data = ByteArray(length)
readFully(data, 0, length)
return ByteReadChannel(data)
}

public suspend fun ByteReadChannel.readVarInt(): Int {
var value = 0
var position = 0
var currentByte: Byte

while (true) {
currentByte = readByte()
value = value or ((currentByte and 0x7F) shl position)

if ((currentByte and 0x80) == 0) break

position += 7

if (position >= 32) throw RuntimeException("VarInt is too big")
}

return value
}

public suspend fun ByteReadChannel.readUtf8String(): String {
val length = readVarInt()
val data = ByteArray(length)
readFully(data, 0, length)
return String(data)
}

0 comments on commit 7a740b3

Please sign in to comment.