Skip to content

Commit

Permalink
Refactor networking module (#501)
Browse files Browse the repository at this point in the history
* Combine inventory data with inventories #497

* Move login and logout out of Player and into PlayerAccounts #497

* Move login response out of PlayerAccounts

* Move password validation earlier in login server

* Reuse account connection for bots

* Move password encryption into networking module

* Move ClientManager into networking

* Move LoginManager into PlayerAccountLoader

* Remove accounts online from PlayerAccountLoader

* Rename ClientManager to ConnectionTracker

* Remove unused NetworkQueue interface

* Move instruct into client

* Rename instruct to instruction

* Move Instruction to client

* Move game server files

* Move login server files

* Move encoders

* Move decoders

* Move decoder

* Move jag extensions

* Move visuals into protocol

* Move connection tracker into client

* Move Protocol

* Add Server tests

* Add PlayerAccountLoader to koin

* Move visual encoders into Encoders.kt

* Add networking code coverage module

* Split PlayerAccounts into AccountManager and SaveQueue

* Add player account tests

* Move ConnectionQueue into networking

* Move indexing into CharacterList

* Remove IndexAllocator

* Add decoder tests

* Remove unused decoders

* Add encoder tests, store all expected byte arrays into .csv resources

* Store test huffman in resources

* Add fallback safe account storage in case of primary failure

* Fix bot spawns
  • Loading branch information
GregHib authored Mar 28, 2024
1 parent 0395799 commit 4f9aaca
Show file tree
Hide file tree
Showing 528 changed files with 4,469 additions and 2,594 deletions.
4 changes: 4 additions & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@ component_management:
name: Engine
paths:
- engine/
- component_id: network_module
name: Network
paths:
- network/
github_checks:
annotations: false
30 changes: 15 additions & 15 deletions cache/src/main/kotlin/world/gregs/voidps/cache/secure/Huffman.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,35 @@ class Huffman {
decryptKeys = IntArray(8)
val freq = IntArray(33)
var key = 0
//For each non-zero frequency
// For each non-zero frequency
for ((index, size) in huffman.map { it.toInt() }.filter { it != 0 }.toIntArray().withIndex()) {
//Calculate maximum frequency
// Calculate maximum frequency
val maximumFreq = 1 shl 32 - size
//zero or the previous minimum
// zero or the previous minimum
val currentFreq = freq[size]
//Store the min
// Store the min
masks!![index] = currentFreq
//Set the frequency to the
freq[size] = if (currentFreq and maximumFreq == 0) {//If the min and max are equal ish?
//Starting from the bottom find the smallest frequency indices
// Set the frequency to the
freq[size] = if (currentFreq and maximumFreq == 0) { // If the min and max are equal ish?
// Starting from the bottom find the smallest frequency indices
for (idx in size - 1 downTo 1) {
val leftFreq = freq[idx]
if (leftFreq != currentFreq) {
break
}
val rightFreq = 1 shl 32 - idx
if (rightFreq and leftFreq != 0) {
//Move up the tree?
// Move up the tree?
freq[idx] = freq[idx - 1]
break
}
//Merge the two smallest trees
// Merge the two smallest trees
freq[idx] = leftFreq + rightFreq
}
//Sum of their frequencies
// Sum of their frequencies
maximumFreq + currentFreq
} else {
//Move up the tree?
// Move up the tree?
freq[size - 1]
}
for (idx in size + 1..32) {
Expand Down Expand Up @@ -139,11 +139,11 @@ class Huffman {
fun compress(message: String): ByteArray {
val writer = BufferWriter(128)
try {
//Format the message
// Format the message
val messageData = formatMessage(message)
//Write message length
// Write message length
writer.writeSmart(messageData.size)
//Write the compressed message
// Write the compressed message
compress(messageData, writer)
} catch (exception: Throwable) {
exception.printStackTrace()
Expand Down Expand Up @@ -185,7 +185,7 @@ class Huffman {
}
}

//Set the packet position to the correct place
// Set the packet position to the correct place
builder.position(7 + position shr 3)
} catch (e: Exception) {
e.printStackTrace()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import world.gregs.voidps.buffer.read.BufferReader
import java.io.File

internal class HuffmanTest {

private lateinit var huffman: Huffman
private lateinit var result: ByteArray
private lateinit var message: String
private val data = File("./src/test/resources/huffman.csv").readText()
.split(", ").map { it.toByte() }.toByteArray()

@BeforeEach
fun setup() {
val data = byteArrayOf(22, 22, 22, 22, 22, 22, 21, 22, 22, 20, 22, 22, 22, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 3, 8, 22, 16, 22, 16, 17, 7, 13, 13, 13, 16, 7, 10, 6, 16, 10, 11, 12, 12, 12, 12, 13, 13, 14, 14, 11, 14, 19, 15, 17, 8, 11, 9, 10, 10, 10, 10, 11, 10, 9, 7, 12, 11, 10, 10, 9, 10, 10, 12, 10, 9, 8, 12, 12, 9, 14, 8, 12, 17, 16, 17, 22, 13, 21, 4, 7, 6, 5, 3, 6, 6, 5, 4, 10, 7, 5, 6, 4, 4, 6, 10, 5, 4, 4, 5, 7, 6, 10, 6, 10, 22, 19, 22, 14, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 22, 21, 22, 22, 22, 21, 22, 22)
huffman = Huffman().load(data)
message = ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class VersionTableBuilderTest {
fun `Build version table with large RSA numbers`() {
val indexCount = 50

val random = java.util.Random(0)
val random = Random(0)
val exponent = BigInteger(256, random)
val modulus = BigInteger(256, random)
val table = VersionTableBuilder(exponent, modulus, indexCount)
Expand Down
1 change: 1 addition & 0 deletions cache/src/test/resources/huffman.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22, 22, 22, 22, 22, 22, 21, 22, 22, 20, 22, 22, 22, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 3, 8, 22, 16, 22, 16, 17, 7, 13, 13, 13, 16, 7, 10, 6, 16, 10, 11, 12, 12, 12, 12, 13, 13, 14, 14, 11, 14, 19, 15, 17, 8, 11, 9, 10, 10, 10, 10, 11, 10, 9, 7, 12, 11, 10, 10, 9, 10, 10, 12, 10, 9, 8, 12, 12, 9, 14, 8, 12, 17, 16, 17, 22, 13, 21, 4, 7, 6, 5, 3, 6, 6, 5, 4, 10, 7, 5, 6, 4, 4, 6, 10, 5, 4, 4, 5, 7, 6, 10, 6, 10, 22, 19, 22, 14, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 22, 21, 22, 22, 22, 21, 22, 22
1 change: 0 additions & 1 deletion engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ dependencies {
implementation("net.pearx.kasechange:kasechange:${findProperty("kaseChangeVersion")}")

implementation("io.insert-koin:koin-core:${findProperty("koinVersion")}")
implementation("org.mindrot:jbcrypt:${findProperty("jbcryptVersion")}")
implementation("org.rsmod:rsmod-pathfinder:${findProperty("pathfinderVersion")}")

implementation("io.insert-koin:koin-logger-slf4j:${findProperty("koinLogVersion")}")
Expand Down
22 changes: 15 additions & 7 deletions engine/src/main/kotlin/world/gregs/voidps/engine/EngineModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import org.koin.dsl.module
import org.rsmod.game.pathfinder.LineValidator
import org.rsmod.game.pathfinder.PathFinder
import org.rsmod.game.pathfinder.StepValidator
import world.gregs.voidps.engine.client.ConnectionQueue
import world.gregs.voidps.engine.client.LoginManager
import world.gregs.voidps.engine.client.PlayerAccountLoader
import world.gregs.voidps.engine.client.update.batch.ZoneBatchUpdates
import world.gregs.voidps.engine.data.PlayerAccounts
import world.gregs.voidps.engine.data.AccountManager
import world.gregs.voidps.engine.data.SafeStorage
import world.gregs.voidps.engine.data.SaveQueue
import world.gregs.voidps.engine.data.definition.*
import world.gregs.voidps.engine.data.json.FileStorage
import world.gregs.voidps.engine.data.sql.PostgresStorage
Expand All @@ -23,6 +24,7 @@ import world.gregs.voidps.engine.map.collision.CollisionStrategyProvider
import world.gregs.voidps.engine.map.collision.Collisions
import world.gregs.voidps.engine.map.collision.GameObjectCollision
import world.gregs.voidps.engine.map.zone.DynamicZones
import world.gregs.voidps.network.client.ConnectionQueue
import world.gregs.voidps.type.Tile
import world.gregs.yaml.Yaml
import world.gregs.yaml.read.YamlReaderConfiguration
Expand All @@ -37,9 +39,15 @@ val engineModule = module {
single { FloorItemTracking(get(), get(), get()) }
single { Hunting(get(), get(), get(), get(), get(), get()) }
single {
PlayerAccounts(get(), get(), get(), get(), get(), get(), Tile(
getIntProperty("homeX", 0), getIntProperty("homeY", 0), getIntProperty("homeLevel", 0)
), get())
SaveQueue(get(), SafeStorage(File(getProperty<String>("storageFailDirectory"))))
}
single {
val homeTile = Tile(
x = getIntProperty("homeX", 0),
y = getIntProperty("homeY", 0),
level = getIntProperty("homeLevel", 0)
)
AccountManager(get(), get(), get(), get(), get(), get(), homeTile, get(), get(), get(), get())
}
// IO
single { Yaml(YamlReaderConfiguration(2, 8, VERY_FAST_LOAD_FACTOR)) }
Expand All @@ -60,6 +68,7 @@ val engineModule = module {
}
FileStorage(get(), saves, get(), getProperty("experienceRate", "1.0").toDouble())
} }
single { PlayerAccountLoader(get(), get(), get(), get(), get(), Contexts.Game) }
// Map
single { ZoneBatchUpdates() }
single { DynamicZones(get(), get(), get()) }
Expand All @@ -68,7 +77,6 @@ val engineModule = module {
single {
ConnectionQueue(getIntProperty("connectionPerTickCap", 1))
}
single { LoginManager(get<Players>().indexer) }
single(createdAtStart = true) { GameObjectCollision(get()) }
// Collision
single { Collisions() }
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import world.gregs.voidps.engine.entity.character.Character
import world.gregs.voidps.engine.entity.character.player.Player
import world.gregs.voidps.engine.entity.character.player.chat.ChatType
import world.gregs.voidps.engine.get
import world.gregs.voidps.network.encode.*
import world.gregs.voidps.network.login.protocol.encode.*
import world.gregs.voidps.type.Tile

/**
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,52 @@ import com.github.michaelbull.logging.InlineLogger
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.withContext
import org.mindrot.jbcrypt.BCrypt
import world.gregs.voidps.engine.data.PlayerAccounts
import world.gregs.voidps.engine.data.AccountManager
import world.gregs.voidps.engine.data.AccountStorage
import world.gregs.voidps.engine.data.SaveQueue
import world.gregs.voidps.engine.data.definition.AccountDefinitions
import world.gregs.voidps.engine.entity.World
import world.gregs.voidps.engine.entity.character.player.Player
import world.gregs.voidps.network.AccountLoader
import world.gregs.voidps.network.Instruction
import world.gregs.voidps.network.NetworkQueue
import world.gregs.voidps.engine.entity.character.player.name
import world.gregs.voidps.engine.entity.character.player.rights
import world.gregs.voidps.network.Response
import world.gregs.voidps.network.client.Client
import world.gregs.voidps.network.client.ConnectionQueue
import world.gregs.voidps.network.client.Instruction
import world.gregs.voidps.network.login.AccountLoader
import world.gregs.voidps.network.login.protocol.encode.login

/**
* Checks password is valid for a player account before logging in
* Keeps track of the players online, prevents duplicate login attempts
*/
class PlayerAccountLoader(
private val queue: NetworkQueue,
private val accounts: PlayerAccounts,
private val queue: ConnectionQueue,
private val storage: AccountStorage,
private val accounts: AccountManager,
private val saveQueue: SaveQueue,
private val accountDefinitions: AccountDefinitions,
private val gameContext: CoroutineDispatcher
) : AccountLoader {
private val logger = InlineLogger()

override fun password(username: String): String? {
return accountDefinitions.get(username)?.passwordHash
}

/**
* @return flow of instructions for the player to be controlled with
*/
override suspend fun load(client: Client, username: String, password: String, index: Int, displayMode: Int): MutableSharedFlow<Instruction>? {
override suspend fun load(client: Client, username: String, passwordHash: String, displayMode: Int): MutableSharedFlow<Instruction>? {
try {
val saving = accounts.saving(username)
val saving = saveQueue.saving(username)
if (saving) {
client.disconnect(Response.ACCOUNT_ONLINE)
return null
}
val player = accounts.getOrElse(username, index) { accounts.create(username, password) }
if (validPassword(player, password)) {
client.disconnect(Response.INVALID_CREDENTIALS)
return null
}

val player = storage.load(username)?.toPlayer() ?: accounts.create(username, passwordHash)
logger.info { "Player $username loaded and queued for login." }
withContext(gameContext) {
queue.await()
logger.info { "Player logged in $username index $index." }
player.login(client, displayMode)
}
connect(player, client, displayMode)
return player.instructions
} catch (e: IllegalStateException) {
logger.trace(e) { "Error loading player account" }
Expand All @@ -53,5 +58,17 @@ class PlayerAccountLoader(
}
}

private fun validPassword(player: Player, password: String) = player.passwordHash.isBlank() || !BCrypt.checkpw(password, player.passwordHash)
suspend fun connect(player: Player, client: Client? = null, displayMode: Int = 0) {
if (!accounts.setup(player)) {
logger.warn { "Error setting up account" }
client?.disconnect(Response.WORLD_FULL)
return
}
withContext(gameContext) {
queue.await()
logger.info { "${if (client != null) "Player" else "Bot"} logged in ${player.accountName} index ${player.index}." }
client?.login(player.name, player.index, player.rights.ordinal, membersWorld = World.members)
accounts.spawn(player, client, displayMode)
}
}
}
Loading

0 comments on commit 4f9aaca

Please sign in to comment.