Added logic to check Player- and Game-IDs according to new layout. (#2108)

Backwards compatible to old format.
This commit is contained in:
wrov 2020-03-18 21:55:57 +01:00 committed by GitHub
parent 701ddcb76b
commit e308f1fe0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 205 additions and 9 deletions

View 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
}
}
}

View file

@ -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())

View file

@ -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()

View file

@ -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) }

View file

@ -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

View 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
}
}