diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index 3aca0d6..c34da4c 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -9,15 +9,27 @@ data class ActivationCode( companion object { fun fromString(input: String): ActivationCode { val components = input.removePrefix("LPA:").split('$') - if (components.size < 2 || components[0] != "1") { + .map { it.trim().ifEmpty { null } } + if (components.size < 2 || components[0] != "1" || components[1].isNullOrEmpty()) { throw IllegalArgumentException("Invalid activation code format") } return ActivationCode( - address = components[1].trim(), - matchingId = components.getOrNull(2)?.trim()?.ifBlank { null }, - oid = components.getOrNull(3)?.trim()?.ifBlank { null }, - confirmationCodeRequired = components.getOrNull(4)?.trim() == "1" + components[1]!!, + components.getOrNull(2), + components.getOrNull(3), + components.getOrNull(4) == "1" ) } } + + override fun toString(): String { + val parts = buildList { + add("1") + add(address) + add(matchingId ?: "") + add(oid ?: "") + add(if (confirmationCodeRequired) "1" else "") + } + return parts.joinToString("$").trimEnd('$') + } } \ No newline at end of file diff --git a/app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt b/app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt new file mode 100644 index 0000000..aadaab3 --- /dev/null +++ b/app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt @@ -0,0 +1,71 @@ +package im.angry.openeuicc.util + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +class ActivationCodeTest { + /** + * @see {https://www.gsma.com/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf#page=112} + */ + @Suppress("SpellCheckingInspection") + private val expectedFixtures = mapOf( + // if SM-DP+ OID and Confirmation Code Required Flag are not present + Pair( + "1\$SMDP.GSMA.COM\$04386-AGYFT-A74Y8-3F815", + ActivationCode("SMDP.GSMA.COM", "04386-AGYFT-A74Y8-3F815", null, false) + ), + // if SM-DP+ OID is not present and Confirmation Code Required Flag is present + Pair( + "1\$SMDP.GSMA.COM\$04386-AGYFT-A74Y8-3F815\$\$1", + ActivationCode("SMDP.GSMA.COM", "04386-AGYFT-A74Y8-3F815", null, true) + ), + // if SM-DP+ OID and Confirmation Code Required flag are present + Pair( + "1\$SMDP.GSMA.COM\$04386-AGYFT-A74Y8-3F815\$1.3.6.1.4.1.31746\$1", + ActivationCode("SMDP.GSMA.COM", "04386-AGYFT-A74Y8-3F815", "1.3.6.1.4.1.31746", true) + ), + // if SM-DP+ OID is present and Confirmation Code Required Flag is not present + Pair( + "1\$SMDP.GSMA.COM\$04386-AGYFT-A74Y8-3F815\$1.3.6.1.4.1.31746", + ActivationCode("SMDP.GSMA.COM", "04386-AGYFT-A74Y8-3F815", "1.3.6.1.4.1.31746", false) + ), + // if SM-DP+ OID is present, Activation token is left blank and Confirmation Code Required Flag is not present + Pair( + "1\$SMDP.GSMA.COM\$\$1.3.6.1.4.1.31746", + ActivationCode("SMDP.GSMA.COM", null, "1.3.6.1.4.1.31746", false) + ), + ) + + @Suppress("SpellCheckingInspection") + private val unexpectedFixtures = listOf( + "", "LPA:", + "1", "LPA:1", + "1$", "LPA:1$", + "1$$", "LPA:1$$", + "2\$SMDP.GSMA.COM", "LPA:2\$SMDP.GSMA.COM", + ) + + @Test + fun testParsing() { + for ((input, expected) in expectedFixtures) { + val actual = ActivationCode.fromString(input) + assertEquals(expected.address, actual.address) + assertEquals(expected.matchingId, actual.matchingId) + assertEquals(expected.oid, actual.oid) + assertEquals(expected.confirmationCodeRequired, actual.confirmationCodeRequired) + assertEquals(input, expected.toString()) + assertTrue(expected == actual) + } + } + + @Test + fun testUnexpected() { + for (fixture in unexpectedFixtures) { + assertThrows(IllegalArgumentException::class.java) { + ActivationCode.fromString(fixture) + } + } + } +} \ No newline at end of file