taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit 746650e94729c23fe3765a3f561525cfb35d59c4
parent 3a5d4099dfa0d9782503dd03447d84de12853de1
Author: Antoine A <>
Date:   Tue, 24 Dec 2024 01:03:04 +0100

taler-common: incoming subject parser

Diffstat:
MCargo.lock | 403+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtaler-common/Cargo.toml | 11++++++++++-
Ataler-common/benches/subject.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtaler-common/src/lib.rs | 1+
Ataler-common/src/subject.rs | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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) + ) + } +}