Added logic to check Player- and Game-IDs according to new layout. (#2108)
Backwards compatible to old format.
This commit is contained in:
parent
701ddcb76b
commit
e308f1fe0c
6 changed files with 205 additions and 9 deletions
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.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.GameInfo
|
||||
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.ui.pickerscreens.PickerScreen
|
||||
import com.unciv.ui.utils.*
|
||||
|
@ -115,7 +116,7 @@ class MultiplayerScreen() : PickerScreen() {
|
|||
fun addMultiplayerGame(gameId: String?, gameName: String = ""){
|
||||
try {
|
||||
//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) {
|
||||
val errorPopup = Popup(this)
|
||||
errorPopup.addGoodSizedLabel("Invalid game ID!".tr())
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.unciv.UncivGame
|
|||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.GameStarter
|
||||
import com.unciv.logic.IdChecker
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.translations.tr
|
||||
|
@ -49,7 +50,7 @@ class NewGameScreen: PickerScreen(){
|
|||
if (newGameParameters.isOnlineMultiplayer) {
|
||||
for (player in newGameParameters.players.filter { it.playerType == PlayerType.Human }) {
|
||||
try {
|
||||
UUID.fromString(player.playerId)
|
||||
UUID.fromString(IdChecker.checkAndReturnPlayerUuid(player.playerId))
|
||||
} catch (ex: Exception) {
|
||||
val invalidPlayerIdPopup = Popup(this)
|
||||
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.utils.Align
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.IdChecker
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.metadata.Player
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.ui.utils.Popup
|
||||
import java.util.*
|
||||
|
||||
class PlayerPickerTable(val newGameScreen: NewGameScreen, val newGameParameters: GameParameters): Table() {
|
||||
|
@ -72,8 +72,8 @@ class PlayerPickerTable(val newGameScreen: NewGameScreen, val newGameParameters:
|
|||
|
||||
fun onPlayerIdTextUpdated(){
|
||||
try {
|
||||
val uuid = UUID.fromString(playerIdTextfield.text)
|
||||
player.playerId = playerIdTextfield.text
|
||||
UUID.fromString(IdChecker.checkAndReturnPlayerUuid(playerIdTextfield.text))
|
||||
player.playerId = playerIdTextfield.text.trim()
|
||||
errorLabel.apply { setText("✔");setFontColor(Color.GREEN) }
|
||||
} catch (ex: Exception) {
|
||||
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.graphics.Color
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.IdChecker
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.CivilopediaScreen
|
||||
import com.unciv.ui.victoryscreen.VictoryScreen
|
||||
import com.unciv.ui.MultiplayerScreen
|
||||
import com.unciv.ui.mapeditor.LoadMapScreen
|
||||
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.SaveGameScreen
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.ui.victoryscreen.VictoryScreen
|
||||
import com.unciv.ui.worldscreen.WorldScreen
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
@ -105,7 +106,7 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen) {
|
|||
multiplayerPopup.addButton("Join Game") {
|
||||
val gameId = Gdx.app.clipboard.contents
|
||||
try {
|
||||
UUID.fromString(gameId.trim())
|
||||
UUID.fromString(IdChecker.checkAndReturnGameUuid(gameId))
|
||||
} catch (ex: Exception) {
|
||||
badGameIdLabel.setText("Invalid game ID!")
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue