From 5af76bac7c37cbb6a6ef3393a2e57454d4923188 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Tue, 10 Aug 2021 17:43:44 +0200 Subject: [PATCH] Improved psoserv locking strategy. --- .gitignore | 2 +- .../kotlin/world/phantasmal/psoserv/Config.kt | 2 +- .../kotlin/world/phantasmal/psoserv/Main.kt | 2 +- .../world/phantasmal/psoserv/data/Account.kt | 60 ++++++++++++ .../phantasmal/psoserv/data/AccountStore.kt | 93 ++++--------------- .../phantasmal/psoserv/messages/BbMessages.kt | 9 +- .../phantasmal/psoserv/messages/Messages.kt | 2 + .../phantasmal/psoserv/messages/PcMessages.kt | 7 ++ .../psoserv/servers/AccountServer.kt | 38 +++----- .../phantasmal/psoserv/servers/AuthServer.kt | 11 --- .../phantasmal/psoserv/servers/BlockServer.kt | 41 +++----- .../phantasmal/psoserv/servers/GameServer.kt | 7 ++ .../phantasmal/psoserv/servers/PatchServer.kt | 11 --- .../phantasmal/psoserv/servers/ShipServer.kt | 11 --- 14 files changed, 132 insertions(+), 164 deletions(-) diff --git a/.gitignore b/.gitignore index d9e0f22d..fffa0e26 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ build karma.config.generated.js # Config -/psoserv/config.json +/psoserv/*.conf diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Config.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Config.kt index 6c94a297..eb76bffd 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Config.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Config.kt @@ -22,9 +22,9 @@ class Config( val patch: PatchServerConfig? = null, val auth: ServerConfig? = null, val account: ServerConfig? = null, - val proxy: ProxyConfig? = null, val ships: List = emptyList(), val blocks: List = emptyList(), + val proxy: ProxyConfig? = null, ) @Serializable diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt index c8a6960d..4176f961 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt @@ -103,7 +103,7 @@ private fun initialize(config: Config, accountStore: AccountStore): List for (blockCfg in config.blocks) { val block = BlockInfo( name = validateName("Block", blockCfg.name) ?: "block_$blockI", - uiName = blockCfg.uiName ?: "BLOCK${blockI.toString(2).padStart(2, '0')}", + uiName = blockCfg.uiName ?: "BLOCK${blockI.toString().padStart(2, '0')}", bindPair = Inet4Pair(address, blockCfg.port ?: blockPort++), ) blockI++ diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/Account.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/Account.kt index 5c8e3985..a7b1434c 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/Account.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/Account.kt @@ -1,5 +1,61 @@ package world.phantasmal.psoserv.data +import mu.KLogger + +class AccountData( + private val logger: KLogger, + account: Account, + playing: PlayingAccount?, + private val password: String, + private var loggedIn: Boolean, +) { + /** + * All access to this class' properties must synchronize on this lock. + */ + private val lock = Any() + private var _account = account + private var _playing = playing + + val account: Account get() = synchronized(lock) { _account } + val playing: PlayingAccount? get() = synchronized(lock) { _playing } + + init { + require(password.length <= 16) + } + + fun logIn(password: String): LogInResult = + synchronized(lock) { + if (password != this.password) { + LogInResult.BadPassword + } else if (loggedIn) { + LogInResult.AlreadyLoggedIn + } else { + loggedIn = true + LogInResult.Ok + } + } + + fun logOut() { + synchronized(lock) { + if (!loggedIn) { + logger.warn { + """Trying to log out account ${account.id} "${account.username}" while it wasn't logged in.""" + } + } + + _playing = null + loggedIn = false + } + } + + fun setPlaying(char: Character, blockId: Int) { + synchronized(lock) { + _playing = PlayingAccount(account, char, blockId) + loggedIn = true + } + } +} + class Account( val id: Long, val username: String, @@ -45,3 +101,7 @@ enum class SectionId { Yellowboze, Whitill, } + +enum class LogInResult { + Ok, BadPassword, AlreadyLoggedIn +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/AccountStore.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/AccountStore.kt index 6a7159f3..da0fac66 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/AccountStore.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/data/AccountStore.kt @@ -3,22 +3,23 @@ package world.phantasmal.psoserv.data import mu.KLogger class AccountStore(private val logger: KLogger) { + /** + * All access to this class' properties must synchronize on this lock. + */ + private val lock = Any() private var nextId: Long = 1L private var nextGuildCardNo: Int = 1 - /** - * Maps usernames to accounts. Accounts are created on the fly. - */ - private val accounts = mutableMapOf() + private val idToAccountData = mutableMapOf() + private val usernameToAccountData = mutableMapOf() - /** - * Logged in accounts must always be logged out with [logOut]. - */ - fun logIn(username: String, password: String): LogInResult = - synchronized(this) { - val data = accounts.getOrPut(username) { + fun getAccountData(username: String, password: String): AccountData = + synchronized(lock) { + // Simply create the account if it doesn't exist yet. + usernameToAccountData.getOrPut(username) { val accountId = nextId++ AccountData( + logger = logger, account = Account( id = accountId, username = username, @@ -38,74 +39,18 @@ class AccountStore(private val logger: KLogger) { playing = null, password = password, loggedIn = false, - ) - } - - if (password != data.password) { - LogInResult.BadPassword - } else if (data.loggedIn) { - LogInResult.AlreadyLoggedIn - } else { - data.loggedIn = true - LogInResult.Ok(data.account) - } - } - - fun logOut(accountId: Long) { - synchronized(this) { - val data = accounts.values.find { it.account.id == accountId } - - if (data == null) { - logger.warn { - "Trying to log out nonexistent account $accountId." + ).also { + // Ensure it can also be found by ID. + idToAccountData[accountId] = it } - } else { - if (!data.loggedIn) { - logger.warn { - """Trying to log out account ${data.account.id} "${data.account.username}" while it wasn't logged in.""" - } - } - - data.playing = null - data.loggedIn = false } } - } - fun getAccountById(accountId: Long): Account? = - synchronized(this) { - accounts.values.find { it.account.id == accountId }?.account - } - - fun setAccountPlaying(accountId: Long, char: Character, blockId: Int): Account { - synchronized(this) { - val data = accounts.values.first { it.account.id == accountId } - data.playing = PlayingAccount(data.account, char, blockId) - return data.account - } - } - - fun getAccountsByBlock(blockId: Int): List = - synchronized(this) { - accounts.values - .filter { it.loggedIn && it.playing?.blockId == blockId } + fun getPlayingAccountsForBlock(blockId: Int): List = + synchronized(lock) { + idToAccountData.values.asSequence() .mapNotNull { it.playing } + .filter { it.blockId == blockId } + .toList() } - - sealed class LogInResult { - class Ok(val account: Account) : LogInResult() - object BadPassword : LogInResult() - object AlreadyLoggedIn : LogInResult() - } - - private class AccountData( - var account: Account, - var playing: PlayingAccount?, - val password: String, - var loggedIn: Boolean, - ) { - init { - require(password.length <= 16) - } - } } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt index aaf21380..1037f54f 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt @@ -57,6 +57,13 @@ object BbMessageDescriptor : MessageDescriptor { 0x04EB -> BbMessage.GetFileList(buffer) else -> BbMessage.Unknown(buffer) } + + override fun createInitEncryption(serverKey: ByteArray, clientKey: ByteArray) = + BbMessage.InitEncryption( + "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", + serverKey, + clientKey, + ) } sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_SIZE) { @@ -601,7 +608,7 @@ class GuildCardEntry( val name: String, val description: String, val sectionId: Int, - val characterClass: Int, + val charClass: Int, ) class GuildCard( diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/Messages.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/Messages.kt index 3389f50f..25ad770c 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/Messages.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/Messages.kt @@ -39,6 +39,8 @@ interface MessageDescriptor { fun readHeader(buffer: Buffer): Header fun readMessage(buffer: Buffer): MessageType + + fun createInitEncryption(serverKey: ByteArray, clientKey: ByteArray): MessageType } interface InitEncryptionMessage : Message { diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt index 2115dcd7..6c0de9b8 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt @@ -37,6 +37,13 @@ object PcMessageDescriptor : MessageDescriptor { 0x14 -> PcMessage.Redirect(buffer) else -> PcMessage.Unknown(buffer) } + + override fun createInitEncryption(serverKey: ByteArray, clientKey: ByteArray) = + PcMessage.InitEncryption( + "Patch Server. Copyright SonicTeam, LTD. 2001", + serverKey, + clientKey, + ) } sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_SIZE) { diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt index f2926730..7e9d97d9 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt @@ -4,8 +4,9 @@ import world.phantasmal.core.math.clamp import world.phantasmal.psolib.Endianness import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psolib.cursor.cursor +import world.phantasmal.psoserv.data.AccountData import world.phantasmal.psoserv.data.AccountStore -import world.phantasmal.psoserv.data.AccountStore.LogInResult +import world.phantasmal.psoserv.data.LogInResult import world.phantasmal.psoserv.encryption.BbCipher import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.messages.* @@ -26,38 +27,23 @@ class AccountServer( serverCipher: Cipher, clientCipher: Cipher, ): ClientReceiver = object : ClientReceiver { - private var accountId: Long? = null + private var accountData: AccountData? = null private val guildCardBuffer = Buffer.withSize(54672) private var fileChunkNo = 0 private var charSlot: Int = 0 private var charSelected: Boolean = false - init { - ctx.send( - BbMessage.InitEncryption( - "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", - serverCipher.key, - clientCipher.key, - ), - encrypt = false, - ) - } - override fun process(message: BbMessage): Boolean = when (message) { is BbMessage.Authenticate -> { - when ( - val result = accountStore.logIn( - message.username, - message.password, - ) - ) { - is LogInResult.Ok -> { - val account = result.account - this.accountId = account.id + val accountData = accountStore.getAccountData(message.username, message.password) + this.accountData = accountData + when (accountData.logIn(message.password)) { + LogInResult.Ok -> { charSlot = message.charSlot charSelected = message.charSelected + val account = accountData.account ctx.send( BbMessage.AuthData( AuthStatus.Success, @@ -102,7 +88,7 @@ class AccountServer( } is BbMessage.GetAccount -> { - accountId?.let(accountStore::getAccountById)?.let { + accountData?.account?.let { ctx.send(BbMessage.Account(it.guildCardNo, it.teamId)) } @@ -110,7 +96,7 @@ class AccountServer( } is BbMessage.CharSelect -> { - val account = accountId?.let(accountStore::getAccountById) + val account = accountData?.account if (account != null && message.slot in account.characters.indices) { if (message.selected) { @@ -266,9 +252,9 @@ class AccountServer( private fun logOut() { try { - accountId?.let(accountStore::logOut) + accountData?.let(AccountData::logOut) } finally { - accountId = null + accountData = null } } } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt index 693aaf3f..b1681c15 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt @@ -22,17 +22,6 @@ class AuthServer( serverCipher: Cipher, clientCipher: Cipher, ): ClientReceiver = object : ClientReceiver { - init { - ctx.send( - BbMessage.InitEncryption( - "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", - serverCipher.key, - clientCipher.key, - ), - encrypt = false, - ) - } - override fun process(message: BbMessage): Boolean = when (message) { is BbMessage.Authenticate -> { // Don't actually authenticate, since we're simply redirecting the player to the diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BlockServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BlockServer.kt index 761ebe5c..3cc2821a 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BlockServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BlockServer.kt @@ -1,7 +1,8 @@ package world.phantasmal.psoserv.servers +import world.phantasmal.psoserv.data.AccountData import world.phantasmal.psoserv.data.AccountStore -import world.phantasmal.psoserv.data.AccountStore.LogInResult +import world.phantasmal.psoserv.data.LogInResult import world.phantasmal.psoserv.encryption.BbCipher import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.messages.* @@ -22,27 +23,16 @@ class BlockServer( serverCipher: Cipher, clientCipher: Cipher, ): ClientReceiver = object : ClientReceiver { - private var accountId: Long? = null - - init { - ctx.send( - BbMessage.InitEncryption( - "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", - serverCipher.key, - clientCipher.key, - ), - encrypt = false, - ) - } + private var accountData: AccountData? = null override fun process(message: BbMessage): Boolean = when (message) { is BbMessage.Authenticate -> { - when ( - val result = accountStore.logIn(message.username, message.password) - ) { - is LogInResult.Ok -> { - accountId = result.account.id - val char = result.account.characters.getOrNull(message.charSlot) + val accountData = accountStore.getAccountData(message.username, message.password) + this.accountData = accountData + + when (accountData.logIn(message.password)) { + LogInResult.Ok -> { + val char = accountData.account.characters.getOrNull(message.charSlot) if (char == null) { ctx.send( @@ -55,11 +45,8 @@ class BlockServer( ) ) } else { - val account = accountStore.setAccountPlaying( - result.account.id, - char, - blockId, - ) + accountData.setPlaying(char, blockId) + val account = accountData.account ctx.send( BbMessage.AuthData( AuthStatus.Success, @@ -122,7 +109,7 @@ class BlockServer( lobbyNo = 0u, blockNo = blockId.toUShort(), event = 0u, - players = accountStore.getAccountsByBlock(blockId).map { + players = accountStore.getPlayingAccountsForBlock(blockId).map { LobbyPlayer( playerTag = 0, guildCardNo = it.account.guildCardNo, @@ -151,9 +138,9 @@ class BlockServer( private fun logOut() { try { - accountId?.let(accountStore::logOut) + accountData?.let(AccountData::logOut) } finally { - accountId = null + accountData = null } } } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt index 2a275d9f..19b48bb2 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt @@ -65,6 +65,13 @@ abstract class GameServer( override val readEncryptCipher: Cipher? = null override val writeEncryptCipher: Cipher = serverCipher + init { + sendMessage( + messageDescriptor.createInitEncryption(serverCipher.key, clientCipher.key), + encrypt = false, + ) + } + override fun processMessage(message: MessageType): ProcessResult = if (receiver.process(message)) { ProcessResult.Ok diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt index e1b0566d..775823f5 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt @@ -19,17 +19,6 @@ class PatchServer( serverCipher: Cipher, clientCipher: Cipher, ): ClientReceiver = object : ClientReceiver { - init { - ctx.send( - PcMessage.InitEncryption( - "Patch Server. Copyright SonicTeam, LTD. 2001", - serverCipher.key, - clientCipher.key, - ), - encrypt = false, - ) - } - override fun process(message: PcMessage): Boolean = when (message) { is PcMessage.InitEncryption -> { ctx.send(PcMessage.Login()) diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ShipServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ShipServer.kt index b110b6b8..3f68462a 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ShipServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ShipServer.kt @@ -23,17 +23,6 @@ class ShipServer( serverCipher: Cipher, clientCipher: Cipher, ): ClientReceiver = object : ClientReceiver { - init { - ctx.send( - BbMessage.InitEncryption( - "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", - serverCipher.key, - clientCipher.key, - ), - encrypt = false, - ) - } - override fun process(message: BbMessage): Boolean = when (message) { is BbMessage.Authenticate -> { // Don't actually authenticate, since we're simply letting the player choose a block