mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-20 20:59:18 +07:00
Added logic to check Player- and Game-IDs according to new layout. (#2108)
Backwards compatible to old format.
This commit is contained in:
117
core/src/com/unciv/logic/IdHelper.kt
Normal file
117
core/src/com/unciv/logic/IdHelper.kt
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package com.unciv.logic
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class checks whether a Game- or Player-ID matches the old or new format.
|
||||||
|
* If old format is used, checks are skipped and input is returned.
|
||||||
|
* If new format is detected, prefix and checkDigit are checked and UUID returned.
|
||||||
|
*
|
||||||
|
* All input is returned trimmed.
|
||||||
|
*
|
||||||
|
* New format:
|
||||||
|
* G-UUID-CheckDigit for Game IDs
|
||||||
|
* P-UUID-CheckDigit for Player IDs
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* 2ddb3a34-0699-4126-b7a5-38603e665928
|
||||||
|
* Same ID in proposed new Player-ID format:
|
||||||
|
* P-2ddb3a34-0699-4126-b7a5-38603e665928-5
|
||||||
|
* Same ID in proposed new Game-ID format:
|
||||||
|
* G-2ddb3a34-0699-4126-b7a5-38603e665928-5
|
||||||
|
*/
|
||||||
|
class IdChecker {
|
||||||
|
companion object {
|
||||||
|
fun checkAndReturnPlayerUuid(playerId: String): String {
|
||||||
|
return checkAndReturnUuiId(playerId, "P")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkAndReturnGameUuid(gameId: String): String {
|
||||||
|
return checkAndReturnUuiId(gameId, "G")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkAndReturnUuiId(id: String, prefix: String): String {
|
||||||
|
val trimmedPlayerId = id.trim()
|
||||||
|
if (trimmedPlayerId.length == 40) { // length of a UUID (36) with pre- and postfix
|
||||||
|
if (!trimmedPlayerId.startsWith(prefix, true)) {
|
||||||
|
throw IllegalArgumentException("Not a valid ID. Does not start with prefix " + prefix)
|
||||||
|
}
|
||||||
|
val checkDigit = trimmedPlayerId.substring(trimmedPlayerId.lastIndex, trimmedPlayerId.lastIndex +1)
|
||||||
|
// remember, the format is: P-9e37e983-a676-4ecc-800e-ef8ec721a9b9-5
|
||||||
|
val shortenedPlayerId = trimmedPlayerId.substring(2, 38)
|
||||||
|
val calculatedCheckDigit = getCheckDigit(shortenedPlayerId).toString()
|
||||||
|
if (!calculatedCheckDigit.equals(checkDigit)) {
|
||||||
|
throw IllegalArgumentException("Not a valid ID. Checkdigit invalid.")
|
||||||
|
}
|
||||||
|
return shortenedPlayerId
|
||||||
|
} else if (trimmedPlayerId.length == 36) {
|
||||||
|
return trimmedPlayerId
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Not a valid ID. Wrong length.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from https://wiki.openmrs.org/display/docs/Check+Digit+Algorithm
|
||||||
|
*/
|
||||||
|
fun getCheckDigit(uuid: String): Int {
|
||||||
|
// allowable characters within identifier
|
||||||
|
val validChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVYWXZ-"
|
||||||
|
var idWithoutCheckdigit = uuid
|
||||||
|
// remove leading or trailing whitespace, convert to uppercase
|
||||||
|
idWithoutCheckdigit = idWithoutCheckdigit.trim().toUpperCase(Locale.ENGLISH)
|
||||||
|
|
||||||
|
// this will be a running total
|
||||||
|
var sum = 0
|
||||||
|
|
||||||
|
// loop through digits from right to left
|
||||||
|
for (i in idWithoutCheckdigit.indices) {
|
||||||
|
|
||||||
|
//set ch to "current" character to be processed
|
||||||
|
val ch = idWithoutCheckdigit.get(idWithoutCheckdigit.length - i - 1)
|
||||||
|
|
||||||
|
// throw exception for invalid characters
|
||||||
|
if (validChars.indexOf(ch) == -1)
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
ch + " is an invalid character")
|
||||||
|
|
||||||
|
// our "digit" is calculated using ASCII value - 48
|
||||||
|
val digit = ch.toInt() - 48
|
||||||
|
|
||||||
|
// weight will be the current digit's contribution to
|
||||||
|
// the running total
|
||||||
|
var weight: Int
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
|
||||||
|
// for alternating digits starting with the rightmost, we
|
||||||
|
// use our formula this is the same as multiplying x 2 and
|
||||||
|
// adding digits together for values 0 to 9. Using the
|
||||||
|
// following formula allows us to gracefully calculate a
|
||||||
|
// weight for non-numeric "digits" as well (from their
|
||||||
|
// ASCII value - 48).
|
||||||
|
weight = (2 * digit) - (digit / 5) * 9
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// even-positioned digits just contribute their ascii
|
||||||
|
// value minus 48
|
||||||
|
weight = digit
|
||||||
|
|
||||||
|
}
|
||||||
|
// keep a running total of weights
|
||||||
|
sum += weight
|
||||||
|
|
||||||
|
}
|
||||||
|
// avoid sum less than 10 (if characters below "0" allowed,
|
||||||
|
// this could happen)
|
||||||
|
sum = Math.abs(sum) + 10
|
||||||
|
|
||||||
|
// check digit is amount needed to reach next number
|
||||||
|
// divisible by ten
|
||||||
|
val returnValue= (10 - (sum % 10)) % 10
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -3,9 +3,10 @@ package com.unciv.ui
|
|||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.*
|
import com.badlogic.gdx.scenes.scene2d.ui.*
|
||||||
import com.unciv.logic.GameSaver
|
|
||||||
import com.unciv.logic.GameInfo
|
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.GameInfo
|
||||||
|
import com.unciv.logic.GameSaver
|
||||||
|
import com.unciv.logic.IdChecker
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.pickerscreens.PickerScreen
|
import com.unciv.ui.pickerscreens.PickerScreen
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
@ -115,7 +116,7 @@ class MultiplayerScreen() : PickerScreen() {
|
|||||||
fun addMultiplayerGame(gameId: String?, gameName: String = ""){
|
fun addMultiplayerGame(gameId: String?, gameName: String = ""){
|
||||||
try {
|
try {
|
||||||
//since the gameId is a String it can contain anything and has to be checked
|
//since the gameId is a String it can contain anything and has to be checked
|
||||||
UUID.fromString(gameId!!.trim())
|
UUID.fromString(IdChecker.checkAndReturnGameUuid(gameId!!))
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
val errorPopup = Popup(this)
|
val errorPopup = Popup(this)
|
||||||
errorPopup.addGoodSizedLabel("Invalid game ID!".tr())
|
errorPopup.addGoodSizedLabel("Invalid game ID!".tr())
|
||||||
|
@ -9,6 +9,7 @@ import com.unciv.UncivGame
|
|||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
import com.unciv.logic.GameSaver
|
import com.unciv.logic.GameSaver
|
||||||
import com.unciv.logic.GameStarter
|
import com.unciv.logic.GameStarter
|
||||||
|
import com.unciv.logic.IdChecker
|
||||||
import com.unciv.logic.civilization.PlayerType
|
import com.unciv.logic.civilization.PlayerType
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
@ -49,7 +50,7 @@ class NewGameScreen: PickerScreen(){
|
|||||||
if (newGameParameters.isOnlineMultiplayer) {
|
if (newGameParameters.isOnlineMultiplayer) {
|
||||||
for (player in newGameParameters.players.filter { it.playerType == PlayerType.Human }) {
|
for (player in newGameParameters.players.filter { it.playerType == PlayerType.Human }) {
|
||||||
try {
|
try {
|
||||||
UUID.fromString(player.playerId)
|
UUID.fromString(IdChecker.checkAndReturnPlayerUuid(player.playerId))
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
val invalidPlayerIdPopup = Popup(this)
|
val invalidPlayerIdPopup = Popup(this)
|
||||||
invalidPlayerIdPopup.addGoodSizedLabel("Invalid player ID!".tr()).row()
|
invalidPlayerIdPopup.addGoodSizedLabel("Invalid player ID!".tr()).row()
|
||||||
|
@ -9,13 +9,13 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
|||||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.IdChecker
|
||||||
import com.unciv.logic.civilization.PlayerType
|
import com.unciv.logic.civilization.PlayerType
|
||||||
import com.unciv.models.metadata.GameParameters
|
import com.unciv.models.metadata.GameParameters
|
||||||
import com.unciv.models.metadata.Player
|
import com.unciv.models.metadata.Player
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
import com.unciv.ui.utils.Popup
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PlayerPickerTable(val newGameScreen: NewGameScreen, val newGameParameters: GameParameters): Table() {
|
class PlayerPickerTable(val newGameScreen: NewGameScreen, val newGameParameters: GameParameters): Table() {
|
||||||
@ -72,8 +72,8 @@ class PlayerPickerTable(val newGameScreen: NewGameScreen, val newGameParameters:
|
|||||||
|
|
||||||
fun onPlayerIdTextUpdated(){
|
fun onPlayerIdTextUpdated(){
|
||||||
try {
|
try {
|
||||||
val uuid = UUID.fromString(playerIdTextfield.text)
|
UUID.fromString(IdChecker.checkAndReturnPlayerUuid(playerIdTextfield.text))
|
||||||
player.playerId = playerIdTextfield.text
|
player.playerId = playerIdTextfield.text.trim()
|
||||||
errorLabel.apply { setText("✔");setFontColor(Color.GREEN) }
|
errorLabel.apply { setText("✔");setFontColor(Color.GREEN) }
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
errorLabel.apply { setText("✘");setFontColor(Color.RED) }
|
errorLabel.apply { setText("✘");setFontColor(Color.RED) }
|
||||||
|
@ -3,9 +3,9 @@ package com.unciv.ui.worldscreen.mainmenu
|
|||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.IdChecker
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.CivilopediaScreen
|
import com.unciv.ui.CivilopediaScreen
|
||||||
import com.unciv.ui.victoryscreen.VictoryScreen
|
|
||||||
import com.unciv.ui.MultiplayerScreen
|
import com.unciv.ui.MultiplayerScreen
|
||||||
import com.unciv.ui.mapeditor.LoadMapScreen
|
import com.unciv.ui.mapeditor.LoadMapScreen
|
||||||
import com.unciv.ui.mapeditor.NewMapScreen
|
import com.unciv.ui.mapeditor.NewMapScreen
|
||||||
@ -13,6 +13,7 @@ import com.unciv.ui.newgamescreen.NewGameScreen
|
|||||||
import com.unciv.ui.saves.LoadGameScreen
|
import com.unciv.ui.saves.LoadGameScreen
|
||||||
import com.unciv.ui.saves.SaveGameScreen
|
import com.unciv.ui.saves.SaveGameScreen
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
|
import com.unciv.ui.victoryscreen.VictoryScreen
|
||||||
import com.unciv.ui.worldscreen.WorldScreen
|
import com.unciv.ui.worldscreen.WorldScreen
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -105,7 +106,7 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen) {
|
|||||||
multiplayerPopup.addButton("Join Game") {
|
multiplayerPopup.addButton("Join Game") {
|
||||||
val gameId = Gdx.app.clipboard.contents
|
val gameId = Gdx.app.clipboard.contents
|
||||||
try {
|
try {
|
||||||
UUID.fromString(gameId.trim())
|
UUID.fromString(IdChecker.checkAndReturnGameUuid(gameId))
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
badGameIdLabel.setText("Invalid game ID!")
|
badGameIdLabel.setText("Invalid game ID!")
|
||||||
badGameIdLabel.isVisible = true
|
badGameIdLabel.isVisible = true
|
||||||
|
76
tests/src/com/unciv/testing/IdHelperTests.kt
Normal file
76
tests/src/com/unciv/testing/IdHelperTests.kt
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package com.unciv.testing
|
||||||
|
|
||||||
|
import com.unciv.logic.IdChecker
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(GdxTestRunner::class)
|
||||||
|
class IdHelperTests {
|
||||||
|
@Test
|
||||||
|
fun testCheckDigits() {
|
||||||
|
val correctString = "2ddb3a34-0699-4126-b7a5-38603e665928"
|
||||||
|
val inCorrectString1 = "2ddb3a34-0699-4126-b7a5-38603e665929"
|
||||||
|
val inCorrectString2 = "2ddb3a34-0969-4126-b7a5-38603e665928"
|
||||||
|
val inCorrectString3 = "2ddb3a34-0699-4126-b7a"
|
||||||
|
val inCorrectString4 = "0699-4126-b7a5-38603e665928-2ddb3a34"
|
||||||
|
|
||||||
|
val correctLuhn = IdChecker.getCheckDigit(correctString)
|
||||||
|
val correctLuhn2 = IdChecker.getCheckDigit(correctString)
|
||||||
|
val inCorrectLuhn1 = IdChecker.getCheckDigit(inCorrectString1)
|
||||||
|
val inCorrectLuhn2 = IdChecker.getCheckDigit(inCorrectString2)
|
||||||
|
val inCorrectLuhn3 = IdChecker.getCheckDigit(inCorrectString3)
|
||||||
|
val inCorrectLuhn4 = IdChecker.getCheckDigit(inCorrectString4)
|
||||||
|
|
||||||
|
Assert.assertEquals(correctLuhn, correctLuhn2)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn1, correctLuhn)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn2, correctLuhn)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn3, correctLuhn)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn4, correctLuhn)
|
||||||
|
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn1, inCorrectLuhn2)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn1, inCorrectLuhn3)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn1, inCorrectLuhn4)
|
||||||
|
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn2, inCorrectLuhn3)
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn2, inCorrectLuhn4)
|
||||||
|
|
||||||
|
Assert.assertNotEquals(inCorrectLuhn3, inCorrectLuhn4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIdsSuccess() {
|
||||||
|
val correctString = "2ddb3a34-0699-4126-b7a5-38603e665928"
|
||||||
|
|
||||||
|
Assert.assertEquals(correctString, IdChecker.checkAndReturnPlayerUuid(correctString))
|
||||||
|
Assert.assertEquals("c872b8e0-f274-47d4-b761-ce684c5d224c", IdChecker.checkAndReturnGameUuid("c872b8e0-f274-47d4-b761-ce684c5d224c"))
|
||||||
|
|
||||||
|
Assert.assertEquals(correctString, IdChecker.checkAndReturnGameUuid("G-" + correctString + "-2"))
|
||||||
|
Assert.assertEquals(correctString, IdChecker.checkAndReturnPlayerUuid("P-" + correctString + "-2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun testIdFailure1() {
|
||||||
|
IdChecker.checkAndReturnGameUuid("2ddb3a34-0699-4126-b7a5-38603e66592") // too short
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun testIdFailure2() {
|
||||||
|
IdChecker.checkAndReturnGameUuid("P-2ddb3a34-0699-4126-b7a5-38603e665928-2") // wrong prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun testIdFailure3() {
|
||||||
|
IdChecker.checkAndReturnPlayerUuid("G-2ddb3a34-0699-4126-b7a5-38603e665928-2") // wrong prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun testIdFailure4() {
|
||||||
|
IdChecker.checkAndReturnGameUuid("G-2ddb3a34-0699-4126-b7a5-38603e665928-3") // changed checkDigit
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun testIdFailure5() {
|
||||||
|
IdChecker.checkAndReturnGameUuid("G-2ddb3a34-0699-4126-b7a5-48603e665928-2") // changed uuid without changing checkdigit
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user