commit 746650e94729c23fe3765a3f561525cfb35d59c4
parent 3a5d4099dfa0d9782503dd03447d84de12853de1
Author: Antoine A <>
Date: Tue, 24 Dec 2024 01:03:04 +0100
taler-common: incoming subject parser
Diffstat:
5 files changed, 696 insertions(+), 42 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -60,10 +60,22 @@ dependencies = [
]
[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
name = "anyhow"
-version = "1.0.94"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
+checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "assert-json-diff"
@@ -268,10 +280,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
name = "cc"
-version = "1.2.4"
+version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
+checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
dependencies = [
"shlex",
]
@@ -296,6 +314,58 @@ dependencies = [
]
[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -351,6 +421,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "is-terminal",
+ "itertools",
+ "num-traits",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -366,6 +491,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -376,6 +507,32 @@ dependencies = [
]
[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -481,6 +638,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "sha2",
+ "subtle",
+]
+
+[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -534,6 +712,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -659,6 +843,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
+name = "half"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -696,6 +890,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1015,35 +1215,36 @@ dependencies = [
]
[[package]]
-name = "itoa"
-version = "1.0.14"
+name = "is-terminal"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.52.0",
+]
[[package]]
-name = "jiff"
-version = "0.1.15"
+name = "itertools"
+version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
- "jiff-tzdb-platform",
- "windows-sys 0.59.0",
+ "either",
]
[[package]]
-name = "jiff-tzdb"
-version = "0.1.1"
+name = "itoa"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
-name = "jiff-tzdb-platform"
-version = "0.1.1"
+name = "jiff"
+version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329"
-dependencies = [
- "jiff-tzdb",
-]
+checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9"
[[package]]
name = "js-sys"
@@ -1066,24 +1267,24 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.168"
+version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libdeflate-sys"
-version = "1.22.0"
+version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f4ae7b48098016dc3bc64a35605668f0af4425ec1a4a175ce2d0c1129067932"
+checksum = "413b667c8a795fcbe6287a75a8ce92b1dae928172c716fe95044cb2ec7877941"
dependencies = [
"cc",
]
[[package]]
name = "libdeflater"
-version = "1.22.0"
+version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567ff5eb948d34d3f93d8da568e72db0f5a12c89efb6c3913e4d6b142cc7ec34"
+checksum = "d78376c917eec0550b9c56c858de50e1b7ebf303116487562e624e63ce51453a"
dependencies = [
"libdeflate-sys",
]
@@ -1292,9 +1493,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.36.5"
+version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
@@ -1306,6 +1507,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
+name = "oorandom"
+version = "11.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
+
+[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1401,6 +1608,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1474,6 +1709,26 @@ dependencies = [
]
[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1594,6 +1849,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
name = "rustix"
version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1659,12 +1923,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "semver"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
+
+[[package]]
name = "serde"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1686,9 +1965,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.133"
+version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
+checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"itoa",
"memchr",
@@ -2085,9 +2364,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "2.0.90"
+version = "2.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
+checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
dependencies = [
"proc-macro2",
"quote",
@@ -2137,15 +2416,18 @@ name = "taler-common"
version = "0.1.0"
dependencies = [
"base32",
+ "criterion",
+ "ed25519-dalek",
"fastrand",
"jiff",
"rand",
+ "regex",
"serde",
"serde_json",
"serde_urlencoded",
"serde_with",
"sqlx",
- "thiserror 2.0.8",
+ "thiserror 2.0.9",
"url",
]
@@ -2185,11 +2467,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.8"
+version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a"
+checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [
- "thiserror-impl 2.0.8",
+ "thiserror-impl 2.0.9",
]
[[package]]
@@ -2205,9 +2487,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
-version = "2.0.8"
+version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943"
+checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [
"proc-macro2",
"quote",
@@ -2266,10 +2548,20 @@ dependencies = [
]
[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
name = "tinyvec"
-version = "1.8.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
@@ -2536,6 +2828,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2611,6 +2913,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
+name = "web-sys"
+version = "0.3.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "webpki-roots"
version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2646,6 +2958,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/taler-common/Cargo.toml b/taler-common/Cargo.toml
@@ -9,9 +9,18 @@ serde_with = "3.11.0"
rand = "0.8"
fastrand = "2.2.0"
serde_urlencoded = "0.7"
-jiff = "0.1"
+jiff = { version = "0.1", default-features = false, features = ["std"] }
+regex = { version = "1.11", default-features = false, features = ["perf"] }
+ed25519-dalek = { version = "2.1.1", default-features = false }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
url.workspace = true
thiserror.workspace = true
sqlx = { workspace = true, features = ["macros"] }
+
+[dev-dependencies]
+criterion = { version = "0.5" }
+
+[[bench]]
+name = "subject"
+harness = false
diff --git a/taler-common/benches/subject.rs b/taler-common/benches/subject.rs
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use taler_common::subject::SubjectParse;
+
+fn parser(c: &mut Criterion) {
+ const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789::: \t\t\t\t\n\n\n\n----++++";
+ let mut rng = fastrand::Rng::with_seed(42);
+ let real = [
+ "Taler TEGY6d9mh9pgwvwpgs0z0095z854xegfy7j j202yd0esp8p0za60",
+ "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N 0QT1RCBQ8FXJPZ6RG",
+ "Taler NDDCAM9XN4HJZFTBD8V6FNE2FJE8G Y734PJ5AGQMY06C8D4HB3Z0",
+ ];
+ let randoms: Vec<String> = (0..30)
+ .map(|_| {
+ (0..256)
+ .map(|_| *rng.choice(CHARS).unwrap() as char)
+ .collect()
+ })
+ .collect();
+ let chunks: Vec<String> = [
+ "TEGY6d9mh9pgwvwpgs0z0095z854xegfy7jj202yd0esp8p0za60",
+ "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N0QT1RCBQ8FXJPZ6RG",
+ "NDDCAM9XN4HJZFTBD8V6FNE2FJE8GY734PJ5AGQMY06C8D4HB3Z0",
+ "4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0",
+ ]
+ .iter()
+ .flat_map(|key| {
+ (1..key.len()).map(|chunk_size| {
+ key.as_bytes()
+ .chunks(chunk_size)
+ .flat_map(|c| [std::str::from_utf8(c).unwrap(), " "])
+ .collect()
+ })
+ })
+ .collect();
+
+ let parser = SubjectParse::new();
+
+ c.bench_function("real", |b| {
+ b.iter(|| {
+ for case in real {
+ parser.parse_incoming_unstructured(black_box(case));
+ }
+ })
+ });
+
+ c.bench_function("rng", |b| {
+ b.iter(|| {
+ for case in &randoms {
+ parser.parse_incoming_unstructured(black_box(case));
+ }
+ })
+ });
+
+ c.bench_function("chunks", |b| {
+ b.iter(|| {
+ for case in &chunks {
+ parser.parse_incoming_unstructured(black_box(case));
+ }
+ })
+ });
+}
+
+criterion_group!(benches, parser);
+criterion_main!(benches);
diff --git a/taler-common/src/lib.rs b/taler-common/src/lib.rs
@@ -19,6 +19,7 @@ pub mod api_params;
pub mod api_wire;
pub mod error_code;
pub mod types;
+pub mod subject;
pub mod config {
// TODO
}
diff --git a/taler-common/src/subject.rs b/taler-common/src/subject.rs
@@ -0,0 +1,243 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+use std::{fmt::Debug, str::FromStr};
+
+use regex::Regex;
+
+use crate::api_common::EddsaPublicKey;
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum IncomingType {
+ Reserve,
+ Kyc,
+ Wad,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct IncomingSubject(IncomingType, EddsaPublicKey);
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum IncomingSubjectResult {
+ Success(IncomingSubject),
+ Ambiguous,
+}
+
+pub struct SubjectParse {
+ part_regex: Regex,
+ key_regex: Regex,
+ whole_regex: Regex,
+}
+
+impl SubjectParse {
+ pub fn new() -> Self {
+ Self {
+ part_regex: Regex::new("[:a-z0-9A-Z]*").expect("part_pattern regex"),
+ key_regex: Regex::new("(KYC:)?([a-z0-9A-Z]{52})").expect("key_regex regex"),
+ whole_regex: Regex::new("^(KYC:)?([a-z0-9A-Z]{52})$").expect("whole_pattern regex"),
+ }
+ }
+
+ /**
+ * Extract the public key from an unstructured incoming transfer subject
+ *
+ * We first try to find a whole key. If none are found we try to reconstruct
+ * one from parts.
+ **/
+ pub fn parse_incoming_unstructured(&self, subject: &str) -> Option<IncomingSubjectResult> {
+ /** Parse an incoming subject */
+ fn parse_single(str: &str) -> Option<IncomingSubject> {
+ // Check key type
+ let (kind, key) = if let Some(key) = str.strip_prefix("KYC:") {
+ (IncomingType::Kyc, key)
+ } else {
+ (IncomingType::Reserve, str)
+ };
+
+ // Check key validity
+ let key = EddsaPublicKey::from_str(key).ok()?;
+ if ed25519_dalek::VerifyingKey::from_bytes(&key).is_err() {
+ return None;
+ }
+
+ Some(IncomingSubject(kind, key))
+ }
+
+ /** Parse a unique incoming subject */
+ fn parse_unique<A: AsRef<str> + Debug>(
+ candidates: impl Iterator<Item = A>,
+ ) -> Option<IncomingSubjectResult> {
+ let mut keys = candidates.filter_map(|s| parse_single(s.as_ref()));
+ // Check none
+ let first = keys.next()?;
+
+ // Check many
+ if keys.next().is_some() {
+ return Some(IncomingSubjectResult::Ambiguous);
+ }
+ Some(IncomingSubjectResult::Success(first))
+ }
+
+ // Check whole key
+ if let Some(res) = parse_unique(self.key_regex.find_iter(subject).map(|it| it.as_str())) {
+ return Some(res);
+ }
+
+ // Check from parts
+ let parts: Vec<_> = self
+ .part_regex
+ .find_iter(subject)
+ .map(|it| it.as_str())
+ .collect();
+ // We start by assembling big chunks first as we need to detect keys with prefix before other ones
+ for window_size in (2..=parts.len()).rev() {
+ if let Some(res) = parse_unique(parts.windows(window_size).filter_map(|parts| {
+ // Fast skipping path
+ let total_len: usize = parts.iter().map(|it| it.len()).sum();
+ if total_len != 52 && total_len != 56 {
+ // 52 for key and 56 for 'KIC:' prefix + key
+ return None;
+ }
+
+ // TODO lending iterator to reduce allocations
+ let concatenated: String = parts.iter().copied().collect();
+ if self.whole_regex.is_match(&concatenated) {
+ Some(concatenated)
+ } else {
+ None
+ }
+ })) {
+ return Some(res);
+ }
+ }
+
+ None
+ }
+}
+
+#[test]
+/** Test parsing logic */
+fn parse() {
+ let parser = SubjectParse::new();
+ for (ty, prefix) in [(IncomingType::Reserve, ""), (IncomingType::Kyc, "KYC:")] {
+ let key = IncomingSubject(
+ ty,
+ EddsaPublicKey::from_str("4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0")
+ .unwrap(),
+ );
+ let result = Some(IncomingSubjectResult::Success(key));
+ let upper = &format!("{prefix}4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0");
+ let (upper_l, upper_r) = upper.split_at(upper.len() / 2);
+ let mixed = &format!("{prefix}4mzt6RS3rvb3b0e2rdmyw0yra3y0vphyv0cyde6xbb0ympfxceg0");
+ let (mixed_l, mixed_r) = mixed.split_at(mixed.len() / 2);
+
+ // Check succeed if upper or mixed
+ for case in [upper, mixed] {
+ for test in [
+ format!("noise {case} noise"),
+ format!("{case} noise to the right"),
+ format!("noise to the left {case}"),
+ format!(" {case} "),
+ format!("noise\n{case}\nnoise"),
+ format!("Test+{case}"),
+ ] {
+ assert_eq!(parser.parse_incoming_unstructured(&test), result);
+ }
+ }
+
+ // Check succeed if upper or mixed and split
+ for (l, r) in [(upper_l, upper_r), (mixed_l, mixed_r)] {
+ for case in [
+ format!("left {l}{r} right"),
+ format!("left {l} {r} right"),
+ format!("left {l}-{r} right"),
+ format!("left {l}+{r} right"),
+ format!("left {l}\n{r} right"),
+ format!("left {l}-+\n{r} right"),
+ format!("left {l} - {r} right"),
+ format!("left {l} + {r} right"),
+ format!("left {l} \n {r} right"),
+ format!("left {l} - + \n {r} right"),
+ ] {
+ assert_eq!(parser.parse_incoming_unstructured(&case), result);
+ }
+ }
+
+ // Check concat parts
+ for chunk_size in 1..upper.len() {
+ let chunked: String = upper
+ .as_bytes()
+ .chunks(chunk_size)
+ .flat_map(|c| [std::str::from_utf8(c).unwrap(), " "])
+ .collect();
+ for case in &[chunked.clone(), format!("left {chunked} right")] {
+ assert_eq!(parser.parse_incoming_unstructured(&case), result);
+ }
+ }
+
+ // Check failure when multiple keys match
+ for case in [
+ format!("{upper} {upper}"),
+ format!("{mixed} {mixed}"),
+ format!("{mixed} {upper}"),
+ format!("{mixed_l}-{mixed_r} {upper_l}-{upper_r}"),
+ ] {
+ assert_eq!(
+ parser.parse_incoming_unstructured(&case),
+ Some(IncomingSubjectResult::Ambiguous)
+ );
+ }
+
+ // Check failure if malformed or missing
+ for case in [
+ "does not contain any reserve", // Check fail if none
+ &upper[0..upper.len() - 1], // Check fail if missing char
+ "2MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0", // Check fail if not a valid key
+ ] {
+ assert_eq!(parser.parse_incoming_unstructured(&case), None);
+ }
+ }
+}
+
+#[test]
+/** Test parsing logic using real cases */
+fn real() {
+ let parser = SubjectParse::new();
+
+ // Good case
+ for (subject, key) in [
+ (
+ "Taler TEGY6d9mh9pgwvwpgs0z0095z854xegfy7j j202yd0esp8p0za60",
+ "TEGY6d9mh9pgwvwpgs0z0095z854xegfy7jj202yd0esp8p0za60",
+ ),
+ (
+ "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N 0QT1RCBQ8FXJPZ6RG",
+ "00Q979QSMJ29S7BJT3DDAVC5A0DR5Z05B7N0QT1RCBQ8FXJPZ6RG",
+ ),
+ (
+ "Taler NDDCAM9XN4HJZFTBD8V6FNE2FJE8G Y734PJ5AGQMY06C8D4HB3Z0",
+ "NDDCAM9XN4HJZFTBD8V6FNE2FJE8GY734PJ5AGQMY06C8D4HB3Z0",
+ ),
+ ] {
+ assert_eq!(
+ Some(IncomingSubjectResult::Success(IncomingSubject(
+ IncomingType::Reserve,
+ EddsaPublicKey::from_str(key).unwrap(),
+ ))),
+ parser.parse_incoming_unstructured(subject)
+ )
+ }
+}