commit 373dae59dac29620768c9c4664050129860dc3a7
parent f7629d9be5a3c7853127557ada2543fe251f387a
Author: Antoine A <>
Date: Mon, 14 Oct 2024 11:45:08 +0200
common: better subject parser
Diffstat:
2 files changed, 58 insertions(+), 36 deletions(-)
diff --git a/common/src/main/kotlin/TxMedatada.kt b/common/src/main/kotlin/TxMedatada.kt
@@ -19,31 +19,35 @@
package tech.libeufin.common
-private val BASE32_32B_UPPER_PATTERN = Regex("(KYC:)?([0-9A-Z]{52})")
+private val PART_PATTERN = Regex("[:a-z0-9A-Z]*")
private val BASE32_32B_PATTERN = Regex("(KYC:)?([a-z0-9A-Z]{52})")
-private val CLEAN_PATTERN = Regex(" ?[\\n\\-\\+] ?")
data class TalerIncomingMetadata(val type: TalerIncomingType, val key: EddsaPublicKey)
/**
* Extract the reserve public key from an incoming Taler transaction subject
*
- * We first try to match an uppercase key then a lowercase key. If none are
- * found we clean the subject and retry.
+ * We first try to find a whole key. If none are found we try to reconstruct
+ * one from parts.
**/
fun parseIncomingTxMetadata(subject: String): TalerIncomingMetadata {
- /**
- * Extract the reserve public key from [subject] using [pattern]
- *
- * Return null if found none and throw if find too many
- **/
- fun run(subject: String, pattern: Regex): TalerIncomingMetadata? {
- val matches = pattern.findAll(subject).iterator()
- if (!matches.hasNext()) return null
- val match = matches.next()
- if (matches.hasNext()) {
+ /** Check one or no reserve public key is found **/
+ fun check(matches: Sequence<MatchResult>): TalerIncomingMetadata? {
+ val iterator = matches.iterator()
+
+ // Check none
+ if (!iterator.hasNext()) {
+ return null
+ }
+
+ val match = iterator.next()
+
+ // Check many
+ if (iterator.hasNext()) {
throw Exception("Found multiple reserve public key")
}
+
+ // Check kind
val (prefix, key) = match.destructured
val type = if (prefix == "KYC:") TalerIncomingType.kyc else TalerIncomingType.reserve
return TalerIncomingMetadata(type, EddsaPublicKey(key))
@@ -61,17 +65,26 @@ fun parseIncomingTxMetadata(subject: String): TalerIncomingMetadata {
// Since any sequence of 52 upper and lowercase characters can be a
// valid encoded key, deleting spaces and separators can create false
- // positives, so we always start by searching for a key coded in
- // uppercase and then mixed once.
+ // positives, so we always start by searching for a valid whole key
+ // then we try to reconstruct a key from valid parts.
+
+ // Whole key match
+ val key = check(BASE32_32B_PATTERN.findAll(subject))
+ if (key != null) return key
- val matchWithoutCleaning = run(subject, BASE32_32B_UPPER_PATTERN)
- ?: run(subject, BASE32_32B_PATTERN)
- if (matchWithoutCleaning != null) return matchWithoutCleaning
+ // Key reconstruction from parts
+ val parts = PART_PATTERN.findAll(subject).map { it.value }.toList()
+ for (windowSize in 2..parts.size) {
+ val matches = parts.windowed(windowSize).asSequence().map { window ->
+ val joined = window.joinToString("")
+ BASE32_32B_PATTERN.matchEntire(joined)
+ }.filterNotNull()
+ val key = check(matches)
+ if (key != null) return key
+ }
- val cleaned = subject.replace(CLEAN_PATTERN, "")
- return run(cleaned, BASE32_32B_UPPER_PATTERN)
- ?: run(cleaned, BASE32_32B_PATTERN)
- ?: throw Exception("Missing reserve public key")
+ // No key where found
+ throw Exception("Missing reserve public key")
}
/** Extract the reserve public key from an incoming Taler transaction subject */
diff --git a/common/src/test/kotlin/TxMedataTest.kt b/common/src/test/kotlin/TxMedataTest.kt
@@ -18,9 +18,7 @@
*/
import tech.libeufin.common.*
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFails
+import kotlin.test.*
class TxMetadataTest {
fun assertFailsMsg(msg: String, lambda: () -> Unit) {
@@ -56,6 +54,7 @@ class TxMetadataTest {
for ((L, R) in sequenceOf(upperL to upperR, mixedL to mixedR)) {
for (case in sequenceOf(
"left $L$R right",
+ "left $L $R right",
"left $L-$R right",
"left $L+$R right",
"left $L\n$R right",
@@ -67,20 +66,14 @@ class TxMetadataTest {
)) {
assertEquals(key, parseIncomingTxMetadata(case))
}
- }
-
- // Check prefer upper
- for (case in sequenceOf(
- "$mixed $upper",
- "$mixedL-$mixedR $upperL-$upperR"
- )) {
- assertEquals(key, parseIncomingTxMetadata(case))
}
// Check failure when multiple keys match
for (case in sequenceOf(
- "$upper $upper", // Both upper
- "$mixed $mixed" // Both mixed
+ "$upper $upper",
+ "$mixed $mixed",
+ "$mixed $upper",
+ "$mixedL-$mixedR $upperL-$upperR"
)) {
assertFailsMsg("Found multiple reserve public key") {
parseIncomingTxMetadata(case)
@@ -97,4 +90,20 @@ class TxMetadataTest {
}
}
}
+
+ /** Test parsing logic using real use case */
+ @Test
+ fun real() {
+ // Good cases
+ for ((subject, key) in sequenceOf(
+ "Taler TEGY6d9mh9pgwvwpgs0z0095z854xegfy7j j202yd0esp8p0za60" to "TEGY6d9mh9pgwvwpgs0z0095z854xegfy7jj202yd0esp8p0za60",
+ "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N 0QT1RCBQ8FXJPZ6RG" to "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N0QT1RCBQ8FXJPZ6RG",
+ "Taler NDDCAM9XN4HJZFTBD8V6FNE2FJE8G Y734PJ5AGQMY06C8D4HB3Z0" to "NDDCAM9XN4HJZFTBD8V6FNE2FJE8GY734PJ5AGQMY06C8D4HB3Z0",
+ )) {
+ assertEquals(
+ TalerIncomingMetadata(TalerIncomingType.reserve, EddsaPublicKey(key)),
+ parseIncomingTxMetadata(subject)
+ )
+ }
+ }
}
\ No newline at end of file