From 98aeb3948eb280e84856dfe7a8b621e93f2663e0 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Feb 2025 15:44:21 +0800 Subject: [PATCH 1/4] chore: improve activation code parsing robustness --- .../im/angry/openeuicc/util/ActivationCode.kt | 22 ++++-- app-unpriv/build.gradle.kts | 1 + .../openeuicc/util/ActivationCodeTest.kt | 68 +++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt 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-unpriv/build.gradle.kts b/app-unpriv/build.gradle.kts index 66a60b4..8f06830 100644 --- a/app-unpriv/build.gradle.kts +++ b/app-unpriv/build.gradle.kts @@ -43,4 +43,5 @@ android { dependencies { implementation(project(":app-common")) + testImplementation("junit:junit:4.13.2") } \ No newline at end of file diff --git a/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt b/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt new file mode 100644 index 0000000..b6c3335 --- /dev/null +++ b/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt @@ -0,0 +1,68 @@ +package im.angry.openeuicc.util + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +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 fixtures = buildMap { + // if SM-DP+ OID and Confirmation Code Required Flag are not present + put( + "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 + put( + "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 + put( + "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 + put( + "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 + put( + "1\$SMDP.GSMA.COM\$\$1.3.6.1.4.1.31746", + ActivationCode("SMDP.GSMA.COM", null, "1.3.6.1.4.1.31746", false) + ) + } + + @Test + fun testParsing() { + for ((input, expected) in fixtures) { + 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()) + } + } + + @Test + fun testUnexpected() { + @Suppress("SpellCheckingInspection") + val fixtures = listOf( + "", "LPA:", + "1", "LPA:1", + "1$", "LPA:1$", + "1$$", "LPA:1$$", + "2\$SMDP.GSMA.COM", "LPA:2\$SMsDP.GSMA.COM", + ) + for (fixture in fixtures) { + assertThrows(IllegalArgumentException::class.java) { + ActivationCode.fromString(fixture) + } + } + } +} \ No newline at end of file -- 2.45.3 From a3b07e2965f994e95a0d1ca5447217d71fb12ddd Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Feb 2025 15:52:15 +0800 Subject: [PATCH 2/4] fix: typo --- .../src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt b/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt index b6c3335..210f897 100644 --- a/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt +++ b/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt @@ -57,7 +57,7 @@ class ActivationCodeTest { "1", "LPA:1", "1$", "LPA:1$", "1$$", "LPA:1$$", - "2\$SMDP.GSMA.COM", "LPA:2\$SMsDP.GSMA.COM", + "2\$SMDP.GSMA.COM", "LPA:2\$SMDP.GSMA.COM", ) for (fixture in fixtures) { assertThrows(IllegalArgumentException::class.java) { -- 2.45.3 From 195cab008d483253b4f1a102770b8798870741e7 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Feb 2025 16:02:25 +0800 Subject: [PATCH 3/4] fix: path issue --- .../src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt | 0 app-unpriv/build.gradle.kts | 1 - 2 files changed, 1 deletion(-) rename {app-unpriv => app-common}/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt (100%) diff --git a/app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt b/app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt similarity index 100% rename from app-unpriv/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt rename to app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt diff --git a/app-unpriv/build.gradle.kts b/app-unpriv/build.gradle.kts index 8f06830..66a60b4 100644 --- a/app-unpriv/build.gradle.kts +++ b/app-unpriv/build.gradle.kts @@ -43,5 +43,4 @@ android { dependencies { implementation(project(":app-common")) - testImplementation("junit:junit:4.13.2") } \ No newline at end of file -- 2.45.3 From add4bad2223d67c05c387dce565f9897541ca7d9 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Feb 2025 16:08:55 +0800 Subject: [PATCH 4/4] chore: improve testing --- .../openeuicc/util/ActivationCodeTest.kt | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) 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 index 210f897..aadaab3 100644 --- a/app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt +++ b/app-common/src/test/java/im/angry/openeuicc/util/ActivationCodeTest.kt @@ -2,6 +2,7 @@ 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 { @@ -9,57 +10,59 @@ 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 fixtures = buildMap { + private val expectedFixtures = mapOf( // if SM-DP+ OID and Confirmation Code Required Flag are not present - put( + 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 - put( + 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 - put( + 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 - put( + 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 - put( + 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 fixtures) { + 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() { - @Suppress("SpellCheckingInspection") - val fixtures = listOf( - "", "LPA:", - "1", "LPA:1", - "1$", "LPA:1$", - "1$$", "LPA:1$$", - "2\$SMDP.GSMA.COM", "LPA:2\$SMDP.GSMA.COM", - ) - for (fixture in fixtures) { + for (fixture in unexpectedFixtures) { assertThrows(IllegalArgumentException::class.java) { ActivationCode.fromString(fixture) } -- 2.45.3