diff --git a/.gitignore b/.gitignore index 79f4470..1d296f5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /logs /.vscode node-cmd-history.txt -**/dist/** +**/dist +**/data/* diff --git a/Cargo.lock b/Cargo.lock index 467b225..7985c9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.9.0" @@ -65,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -182,7 +197,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -254,9 +269,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -269,43 +284,55 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "any_spawner" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41058deaa38c9d9dd933d6d238d825227cffa668e2839b52879f6619c63eee3b" +dependencies = [ + "futures", + "thiserror 2.0.11", + "wasm-bindgen-futures", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "anymap2" @@ -314,44 +341,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] -name = "async-recursion" -version = "1.1.1" +name = "async-lock" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", + "event-listener", + "event-listener-strategy", + "pin-project-lite", ] [[package]] name = "attribute-derive" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1ee502851995027b06f99f5ffbeffa1406b38d0b318a1ebfa469332c6cbafd" +checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54" dependencies = [ "attribute-derive-macro", "derive-where", "manyhow", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "attribute-derive-macro" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3601467f634cfe36c4780ca9c75dea9a5b34529c1f2810676a337e7e0997f954" +checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b" dependencies = [ "collection_literals", "interpolator", "manyhow", - "proc-macro-utils 0.8.0", + "proc-macro-utils", "proc-macro2", "quote", "quote-use", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -398,9 +425,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "blind-rsa-signatures" @@ -442,9 +469,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -464,15 +491,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bytestring" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" dependencies = [ "bytes", ] @@ -485,9 +512,9 @@ checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" [[package]] name = "cc" -version = "1.1.30" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -502,15 +529,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -521,38 +548,11 @@ dependencies = [ "windows-targets", ] -[[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.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -560,9 +560,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -572,21 +572,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "client" @@ -597,12 +597,15 @@ dependencies = [ "codee", "console_error_panic_hook", "crypto", + "futures", + "gloo-timers 0.3.0", "leptos", "leptos-use", "protocol", + "reqwasm", "serde", "serde_json", - "thiserror 2.0.1", + "thiserror 2.0.11", "tracing", "tracing-subscriber", "tracing-subscriber-wasm", @@ -626,7 +629,7 @@ checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445" dependencies = [ "serde", "serde_json", - "thiserror 1.0.65", + "thiserror 1.0.69", ] [[package]] @@ -637,18 +640,26 @@ checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] [[package]] name = "config" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" dependencies = [ "convert_case 0.6.0", - "lazy_static", "nom", "pathdiff", "serde", @@ -673,24 +684,30 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "const_str_slice_concat" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" + [[package]] name = "convert_case" version = "0.4.0" @@ -725,9 +742,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -743,24 +760,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto" @@ -772,7 +783,7 @@ dependencies = [ "ring", "serde", "serde_with", - "thiserror 2.0.1", + "thiserror 2.0.11", "wasm-bindgen-test", ] @@ -807,7 +818,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -818,16 +829,17 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "dashmap" -version = "5.5.3" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", + "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", @@ -843,7 +855,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -886,7 +898,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -899,7 +911,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -917,6 +929,7 @@ dependencies = [ name = "digital-voting" version = "0.1.0" dependencies = [ + "actix-cors", "actix-web", "anyhow", "base64", @@ -927,17 +940,27 @@ dependencies = [ "process_io", "protocol", "ring", - "rustyline", "serde", + "serde_json", "serde_with", - "thiserror 2.0.1", + "thiserror 2.0.11", "tokio", "tracing", "tracing-actix-web", - "tracing-appender", "tracing-subscriber", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "drain_filter_polyfill" version = "0.1.3" @@ -950,11 +973,20 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "either_of" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2dc0006c5cf511f802ddcffc0a6df9dcc1912f5f0e448f6641b3b035f14f43d" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -973,12 +1005,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -987,6 +1019,27 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fd-lock" version = "4.0.2" @@ -1000,9 +1053,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1063,6 +1116,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -1079,7 +1133,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -1155,7 +1209,7 @@ dependencies = [ "gloo-net 0.3.1", "gloo-render", "gloo-storage", - "gloo-timers", + "gloo-timers 0.2.6", "gloo-utils 0.1.7", "gloo-worker", ] @@ -1214,10 +1268,30 @@ dependencies = [ "gloo-events", "gloo-utils 0.1.7", "serde", - "serde-wasm-bindgen 0.5.0", + "serde-wasm-bindgen", "serde_urlencoded", - "thiserror 1.0.65", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", ] @@ -1236,7 +1310,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror 1.0.65", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1252,12 +1326,12 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils 0.2.0", - "http 1.1.0", + "http 1.2.0", "js-sys", "pin-project", "serde", "serde_json", - "thiserror 1.0.65", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1283,7 +1357,7 @@ dependencies = [ "js-sys", "serde", "serde_json", - "thiserror 1.0.65", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", ] @@ -1298,6 +1372,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.1.7" @@ -1341,6 +1427,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "guardian" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493913a18c0d7bebb75127a26a432162c59edbe06f6cf712001e3e769345e8b5" + [[package]] name = "h2" version = "0.3.26" @@ -1353,23 +1445,13 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", "tracing", ] -[[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" @@ -1384,9 +1466,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -1408,29 +1490,29 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac-sha256" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" dependencies = [ "digest", ] [[package]] name = "hmac-sha512" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" +checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c" dependencies = [ "digest", ] [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1455,9 +1537,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -1476,6 +1558,20 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hydration_context" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35485b3dcbf7e044b8f28c73f04f13e7b509c2466fd10cb2a8a447e38f8a93a" +dependencies = [ + "futures", + "once_cell", + "or_poisoned", + "pin-project-lite", + "serde", + "throw_error", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1499,6 +1595,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1507,19 +1721,30 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "impl-more" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" @@ -1534,12 +1759,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "serde", ] @@ -1549,12 +1774,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" -[[package]] -name = "inventory" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1563,18 +1782,18 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -1587,10 +1806,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1611,17 +1831,34 @@ dependencies = [ [[package]] name = "leptos" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cbb3237c274dadf00dcc27db96c52601b40375117178fb24a991cda073624f0" +checksum = "21c31c9d022c77702c53e02830d08b28320aca9c0899a19c443096c114623fa5" dependencies = [ + "any_spawner", "cfg-if", + "either_of", + "futures", + "getrandom", + "hydration_context", "leptos_config", "leptos_dom", + "leptos_hot_reload", "leptos_macro", - "leptos_reactive", "leptos_server", + "oco_ref", + "or_poisoned", + "paste", + "reactive_graph", + "rustc-hash", + "send_wrapper", + "serde", + "serde_qs", "server_fn", + "slotmap", + "tachys", + "thiserror 2.0.11", + "throw_error", "tracing", "typed-builder", "typed-builder-macro", @@ -1631,18 +1868,20 @@ dependencies = [ [[package]] name = "leptos-use" -version = "0.13.8" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17a16cc63083779fe1081dd714b99a856e46d5ce427e7ce94300b0199a3ccb12" +checksum = "e63ce77018c615075541d944d678853b5bb40c79510e39edf695cadb9b128e13" dependencies = [ "cfg-if", + "chrono", "codee", "default-struct-builder", "js-sys", "lazy_static", "leptos", "paste", - "thiserror 1.0.65", + "send_wrapper", + "thiserror 2.0.11", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1650,70 +1889,56 @@ dependencies = [ [[package]] name = "leptos_config" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ed778611380ddea47568ac6ad6ec5158d39b5bd59e6c4dcd24efc15dc3dc0d" +checksum = "5d874993c7664d757677d056c8f46b5cb5365fe622005e1bf26050f4996e7e52" dependencies = [ "config", "regex", "serde", - "thiserror 1.0.65", + "thiserror 2.0.11", "typed-builder", ] [[package]] name = "leptos_dom" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8401c46c86c1f4c16dcb7881ed319fcdca9cda9b9e78a6088955cb423afcf119" +checksum = "a462aaeec85bc4ecfb26bf324437b92690bf3add1e30eb29b3acc08b20e8b4cb" dependencies = [ - "async-recursion", - "cfg-if", - "drain_filter_polyfill", - "futures", - "getrandom", - "html-escape", - "indexmap 2.6.0", - "itertools", "js-sys", - "leptos_reactive", - "once_cell", - "pad-adapter", - "paste", - "rustc-hash", - "serde", - "serde_json", - "server_fn", - "smallvec", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "tachys", "tracing", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] [[package]] name = "leptos_hot_reload" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb53d4794240b684a2f4be224b84bee9e62d2abc498cf2bcd643cd565e01d96" +checksum = "07eb295ad2f3b2af190da62af339b84fd01ce3c71702f09eb69a57310fcf0c6d" dependencies = [ "anyhow", "camino", - "indexmap 2.6.0", + "indexmap 2.7.1", "parking_lot", "proc-macro2", "quote", "rstml", "serde", - "syn 2.0.87", + "syn 2.0.96", "walkdir", ] [[package]] name = "leptos_macro" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b13bc3db70715cd8218c4535a5af3ae3c0e5fea6f018531fc339377b36bc0e0" +checksum = "90291b25ee576bc9c299d3371cc8f09bf60ea939a8de61fa8b744650aff76e24" dependencies = [ "attribute-derive", "cfg-if", @@ -1727,71 +1952,61 @@ dependencies = [ "quote", "rstml", "server_fn_macro", - "syn 2.0.87", + "syn 2.0.96", "tracing", "uuid", ] [[package]] -name = "leptos_reactive" -version = "0.6.15" +name = "leptos_server" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4161acbf80f59219d8d14182371f57302bc7ff81ee41aba8ba1ff7295727f23" +checksum = "18caffe32c245ddb35697edd898ccb3393efce67672a707a14eebd0db2e8249a" dependencies = [ + "any_spawner", "base64", - "cfg-if", + "codee", "futures", - "indexmap 2.6.0", - "js-sys", - "oco_ref", - "paste", - "pin-project", - "rustc-hash", - "self_cell", + "hydration_context", + "or_poisoned", + "reactive_graph", + "send_wrapper", "serde", - "serde-wasm-bindgen 0.6.5", "serde_json", - "slotmap", - "thiserror 1.0.65", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "leptos_server" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a97eb90a13f71500b831c7119ddd3bdd0d7ae0a6b0487cade4fddeed3b8c03f" -dependencies = [ - "inventory", - "lazy_static", - "leptos_macro", - "leptos_reactive", - "serde", "server_fn", - "thiserror 1.0.65", + "tachys", "tracing", ] [[package]] name = "libc" -version = "0.2.161" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linear-map" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "local-channel" @@ -1822,29 +2037,29 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "manyhow" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "manyhow-macros" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" dependencies = [ - "proc-macro-utils 0.8.0", + "proc-macro-utils", "proc-macro2", "quote", ] @@ -1863,9 +2078,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minicov" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", @@ -1879,20 +2094,19 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "log", "wasi", @@ -1905,6 +2119,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" +[[package]] +name = "next_tuple" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1916,9 +2136,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", @@ -2000,23 +2220,33 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[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", ] [[package]] name = "oco_ref" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" +checksum = "64b94982fe39a861561cf67ff17a7849f2cedadbbad960a797634032b7abb998" dependencies = [ "serde", - "thiserror 1.0.65", + "thiserror 1.0.69", ] [[package]] @@ -2025,6 +2255,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "or_poisoned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" + [[package]] name = "overload" version = "0.1.1" @@ -2032,10 +2268,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "pad-adapter" -version = "0.1.1" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -2068,9 +2304,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pem-rfc7468" @@ -2089,29 +2325,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.6" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.6" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2164,35 +2400,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" -dependencies = [ - "proc-macro2", - "syn 2.0.87", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "quote", - "version_check", + "syn 2.0.96", ] [[package]] @@ -2214,17 +2427,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", -] - -[[package]] -name = "proc-macro-utils" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" -dependencies = [ - "proc-macro2", - "quote", - "smallvec", + "syn 2.0.96", ] [[package]] @@ -2240,9 +2443,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -2255,7 +2458,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "version_check", "yansi", ] @@ -2266,7 +2469,10 @@ version = "0.1.0" dependencies = [ "rustyline", "shellwords", - "thiserror 2.0.1", + "thiserror 2.0.11", + "tracing", + "tracing-appender", + "tracing-subscriber", ] [[package]] @@ -2277,15 +2483,15 @@ dependencies = [ "chrono", "crypto", "serde", - "thiserror 2.0.1", + "thiserror 2.0.11", "wasm-bindgen-test", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2306,10 +2512,10 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" dependencies = [ - "proc-macro-utils 0.10.0", + "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2352,20 +2558,70 @@ dependencies = [ "getrandom", ] +[[package]] +name = "reactive_graph" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbf210c04505e128fb7f64acecc23c71f82f56c7d481b190e1010b7bada2cb9" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "hydration_context", + "or_poisoned", + "pin-project-lite", + "rustc-hash", + "send_wrapper", + "serde", + "slotmap", + "thiserror 2.0.11", + "tracing", + "web-sys", +] + +[[package]] +name = "reactive_stores" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb1913eeb71f74028213455ee971550c2b3cb91b6acd5efa8a0f8dc59f5039" +dependencies = [ + "guardian", + "itertools", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores_macro", + "rustc-hash", +] + +[[package]] +name = "reactive_stores_macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86e4f08f361b05d11422398cef4bc4cf356f2fdd2f06a96646b0e9cd902226" +dependencies = [ + "convert_case 0.6.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -2375,9 +2631,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2396,6 +2652,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwasm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb" +dependencies = [ + "gloo-net 0.1.0", +] + [[package]] name = "ring" version = "0.17.8" @@ -2434,16 +2699,17 @@ dependencies = [ [[package]] name = "rstml" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" dependencies = [ + "derive-where", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.87", + "syn 2.0.96", "syn_derive", - "thiserror 1.0.65", + "thiserror 2.0.11", ] [[package]] @@ -2454,9 +2720,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc_version" @@ -2469,22 +2735,28 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "rustyline" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ "bitflags", "cfg-if", @@ -2500,18 +2772,18 @@ dependencies = [ "unicode-segmentation", "unicode-width", "utf8parse", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustyline-derive" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5af959c8bf6af1aff6d2b463a57f71aae53d1332da58419e30ad8dc7011d951" +checksum = "327e9d075f6df7e25fbf594f1be7ef55cf0d567a6cb5112eeccbbd51ceb48e0d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2529,29 +2801,17 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "self_cell" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" - [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "send_wrapper" @@ -2564,9 +2824,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -2582,33 +2842,22 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", "memchr", @@ -2618,13 +2867,13 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" dependencies = [ "percent-encoding", "serde", - "thiserror 1.0.65", + "thiserror 1.0.69", ] [[package]] @@ -2650,15 +2899,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", @@ -2668,37 +2917,38 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "server_fn" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe" +checksum = "f5dd7fcccd3ef2081da086c1f8595b506627abbbbc9f64be0141d2251219570e" dependencies = [ "bytes", - "ciborium", "const_format", "dashmap", "futures", "gloo-net 0.6.0", - "http 1.1.0", + "http 1.2.0", "js-sys", "once_cell", + "pin-project-lite", "send_wrapper", "serde", "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 1.0.65", + "thiserror 2.0.11", + "throw_error", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -2709,26 +2959,26 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1" +checksum = "e0bbac4f01a714b0490247ac625bdb7055548210556c39e8f56a2dbbe3abc70b" dependencies = [ "const_format", "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "xxhash-rust", ] [[package]] name = "server_fn_macro_default" -version = "0.6.15" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" +checksum = "f07dfd1744a5f5612f00f69fe035b0bfafdf12bb46d76e785673078a9e56b170" dependencies = [ "server_fn_macro", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2801,7 +3051,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ - "serde", "version_check", ] @@ -2813,9 +3062,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2837,6 +3086,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -2862,9 +3117,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -2873,54 +3128,98 @@ dependencies = [ [[package]] name = "syn_derive" -version = "0.1.8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", +] + +[[package]] +name = "tachys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d777e4426a597296b020edcb5c3d8f25a3ccd8adfd22eb5154ac81da946aef9f" +dependencies = [ + "any_spawner", + "const_str_slice_concat", + "drain_filter_polyfill", + "either_of", + "futures", + "html-escape", + "indexmap 2.7.1", + "itertools", + "js-sys", + "linear-map", + "next_tuple", + "oco_ref", + "once_cell", + "or_poisoned", + "parking_lot", + "paste", + "reactive_graph", + "reactive_stores", + "rustc-hash", + "send_wrapper", + "slotmap", + "throw_error", + "tracing", + "wasm-bindgen", + "web-sys", ] [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.65", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.1" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c1e40dd48a282ae8edc36c732cbc219144b87fb6a4c7316d611c6b1f06ec0c" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.1", + "thiserror-impl 2.0.11", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "thiserror-impl" -version = "2.0.1" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874aa7e446f1da8d9c3a5c95b1c5eb41d800045252121dc7f8e0ba370cee55f5" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2933,11 +3232,20 @@ dependencies = [ "once_cell", ] +[[package]] +name = "throw_error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ef8bf264c6ae02a065a4a16553283f0656bd6266fc1fcb09fd2e6b5e91427b" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -2956,34 +3264,29 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -2999,20 +3302,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -3048,7 +3351,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -3057,9 +3360,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -3069,9 +3372,9 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b87073920bcce23e9f5cb0d2671e9f01d6803bb5229c159b2f5ce6806d73ffc" +checksum = "54a9f5c1aca50ebebf074ee665b9f99f2e84906dcf6b993a0d0090edb835166d" dependencies = [ "actix-web", "mutually_exclusive_features", @@ -3087,27 +3390,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror 1.0.65", + "thiserror 1.0.69", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -3126,9 +3429,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -3151,22 +3454,22 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.18.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" +checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" +checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -3175,26 +3478,11 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" @@ -3204,9 +3492,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" @@ -3222,21 +3510,33 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3245,18 +3545,18 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -3282,47 +3582,50 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", + "serde", + "serde_json", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3330,33 +3633,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -3364,20 +3668,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "wasm-streams" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -3388,9 +3692,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -3520,18 +3824,30 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "xxhash-rust" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yansi" @@ -3539,6 +3855,30 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3557,7 +3897,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", ] [[package]] @@ -3566,6 +3927,28 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 35cfaae..aa58609 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,15 @@ A system for both private and transparent electronic elections publish = false license = "Apache-2.0" repository = "https://github.com/ThrasherLT/digital-voting" -default-run = "digital-voting" +default-run = "node" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "node" +path = "src/main.rs" + +[[bin]] +name = "authority" +path = "src/bin/mock-authority/main.rs" [workspace] resolver = "2" @@ -19,29 +25,28 @@ members = [ ] [workspace.dependencies] -thiserror = "2.0.1" -chrono = { version = "0.4.38", features = ["serde"] } -serde = { version = "1.0.214", features = ["derive"] } -tracing = "0.1.40" +thiserror = "2.0.11" +chrono = { version = "0.4.39", features = ["serde"] } +serde = { version = "1.0.217", features = ["derive"] } +tracing = "0.1.41" ring = "0.17.8" base64 = "0.22.1" -serde_with = { version = "3.11.0", features = ["base64"] } -tracing-subscriber = "0.3.18" -wasm-bindgen-test = "0.3.45" +serde_with = { version = "3.12.0", features = ["base64"] } +tracing-subscriber = "0.3.19" +wasm-bindgen-test = "0.3.50" bincode = "1.3.3" +serde_json = "1.0.137" crypto = { version = "*", path = "./subcrates/crypto" } protocol = { version = "*", path = "./subcrates/protocol" } process_io = { version = "*", path = "./subcrates/process_io" } [dependencies] -anyhow = "1.0.93" +anyhow = "1.0.95" actix-web = "4.9.0" -tokio = { version = "1.41.1", features = ["full", "test-util"] } -tracing-appender = "0.2.3" -tracing-actix-web = "0.7.14" -clap = { version = "4.5.20", features = ["derive"] } -rustyline = { version = "14.0.0", features = ["derive"] } +tokio = { version = "1.43.0", features = ["full", "test-util"] } +tracing-actix-web = "0.7.15" +clap = { version = "4.5.27", features = ["derive"] } ring.workspace = true chrono.workspace = true @@ -52,10 +57,12 @@ serde_with.workspace = true base64.workspace = true tracing-subscriber.workspace = true bincode.workspace = true +serde_json.workspace = true crypto.workspace = true protocol.workspace = true process_io.workspace = true +actix-cors = "0.7.0" [profile.release] lto = true diff --git a/deny.toml b/deny.toml index e67a64a..98b0771 100644 --- a/deny.toml +++ b/deny.toml @@ -71,7 +71,6 @@ db-urls = ["https://github.com/rustsec/advisory-db"] # output a note when they are encountered. # TODO make sure these are figured out before any serious release: ignore = [ - { id = "RUSTSEC-2024-0370", reason = "`leptos` depends on `proc-macro-error`, so will have to wait for `leptos` to be updated" }, { id = "RUSTSEC-2023-0071", reason = "The Marvin Attack only affects deterministicly padded RSA, while this crate uses PSS under the hood" }, #"RUSTSEC-0000-0000", #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, @@ -95,13 +94,13 @@ allow = [ "MIT", "Apache-2.0", "ISC", - "Unicode-DFS-2016", "OpenSSL", "Apache-2.0 WITH LLVM-exception", "BSD-3-Clause", "BSD-2-Clause", "BSL-1.0", "Zlib", + "Unicode-3.0" ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the diff --git a/src/api/server_cli.rs b/src/api/cli.rs similarity index 56% rename from src/api/server_cli.rs rename to src/api/cli.rs index 49fc341..0a724d3 100644 --- a/src/api/server_cli.rs +++ b/src/api/cli.rs @@ -3,22 +3,40 @@ // TODO daemonize at least on Unix systems. +use std::path::PathBuf; + use clap::Parser; -use crypto::signature::blind_sign; + +// TODO make sure paths exist and won't cause an error /// Command line arguments for the node. /// All the stuff required to start the node. #[derive(Parser, Clone, Debug)] pub struct Args { /// The address the node will listen on. - #[clap(short = 'a', long = "address", default_value = "127.0.0.1:8080")] + #[clap( + long = "address", + default_value = "0.0.0.0:8080", + help = "The socket address on which the http server of the node will be hosted" + )] pub socket_addr: std::net::SocketAddr, - /// The public key of the election authority used to verify that the voters are eligible. - #[clap(short = 'p', long = "authority-public-key")] - pub authority_pk: blind_sign::PublicKey, + /// Path to where the log, config and etc files are stored. + #[clap( + long = "data-path", + default_value = "./data", + help = "Path to where log, config and similar files will be stored" + )] + pub data_path: PathBuf, + /// Don't start a CLI interface for this node. + #[clap( + long = "no-cli", + default_value_t = false, + help = "Only run HTTP server and do not start a CLI interface" + )] + pub no_cli: bool, /// The command to execute. See `Cmd` for more details. #[clap(subcommand)] - pub cmd: Cmd, + pub cmd: Option, } /// The command that the node should execute on startup. diff --git a/src/api/config.rs b/src/api/config.rs new file mode 100644 index 0000000..7baff94 --- /dev/null +++ b/src/api/config.rs @@ -0,0 +1,14 @@ +use std::path::Path; + +use anyhow::Result; +use protocol::config::BlockchainConfig; +use tokio::fs; + +pub async fn load_from_file(path: &Path) -> Result { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + let file_content = fs::read_to_string(path).await?; + + Ok(serde_json::from_str(&file_content)?) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 19a99c8..3662a7e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,2 +1,3 @@ +pub mod cli; +pub mod config; pub mod server; -pub mod server_cli; diff --git a/src/api/server.rs b/src/api/server.rs index 7fdc31f..8fe3ba6 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,46 +1,85 @@ use std::net::SocketAddr; -use actix_web::{post, routes, web, App, HttpServer, Responder}; +use actix_cors::Cors; +use actix_web::{get, post, routes, web, App, HttpResponse, HttpServer, Responder}; +use anyhow::{bail, Result}; +use tokio::{select, sync::oneshot, task::JoinHandle}; use tracing::info; use tracing_actix_web::TracingLogger; -use thiserror::Error; - use protocol::vote::Vote; -#[derive(Error, Debug)] -pub enum Error { - #[error("Actix error: {0}")] - ActixError(#[from] std::io::Error), -} -type Result = std::result::Result; - -pub async fn run(addr: SocketAddr) -> Result<()> { - println!("starting HTTP server at http://localhost:8080"); - - HttpServer::new(|| { - App::new() - // enable logger - .wrap(TracingLogger::default()) - .service(greet) - .service(vote) - }) - .bind(addr)? - .run() - .await?; - - Ok(()) +use crate::state::State; + +pub type Handle = (oneshot::Sender<()>, JoinHandle>); + +pub fn run(state: State, addr: SocketAddr) -> Result { + let (tx, rx) = oneshot::channel::<()>(); + let state = web::Data::new(state); + println!("starting HTTP server at {addr}"); + + let handle = tokio::spawn(async move { + let server = HttpServer::new(move || { + App::new() + // Actix web takes an app state factory here and uses an Arc internally. + // It will error in runtime, if state is passed inside an Arc. + // Also this closure is called once for every worker, meaning that, if you + // pass State::new(), it'll create a new instance for each worker. + // https://actix.rs/docs/application#shared-mutable-state + .app_data(state.clone()) + // Enable logger + .wrap(TracingLogger::default()) + .wrap( + Cors::default() + // TODO Probably should be more specific, even though it's a browser extension: + .allow_any_origin() + .allow_any_header() + .allowed_methods(vec!["GET", "POST"]) + .max_age(3600), + ) + .service(greet) + .service(vote) + .service(config) + .service(health) + }) + .bind(addr)?; + + select! { + _ = rx => { + Ok(()) + }, + _ = server.run() => { + bail!("Server stopped") + } + } + }); + + Ok((tx, handle)) } #[routes] #[get("/")] #[get("/index.html")] async fn greet() -> impl Responder { - "Hello! Please send a POST request to /vote with a JSON body, containing a public key, a vote, a timestamp, and a signature.\n" + HttpResponse::Ok().body("Welcome to the digital voting blockchain! (WIP).\n") } #[post("/vote")] pub async fn vote(vote: web::Json) -> impl Responder { info!("POST: /vote {vote:?}"); - vote + HttpResponse::Ok() +} + +#[get("/config")] +pub async fn config(state: web::Data) -> impl Responder { + if let Ok(json) = serde_json::to_string(state.get_blockchain_config()) { + HttpResponse::Ok().body(json) + } else { + HttpResponse::InternalServerError().body("Failed to get config") + } +} + +#[get("/health")] +pub async fn health() -> impl Responder { + HttpResponse::Ok() } diff --git a/src/batcher/mod.rs b/src/batcher/mod.rs index 0648038..9c0d59d 100644 --- a/src/batcher/mod.rs +++ b/src/batcher/mod.rs @@ -10,8 +10,6 @@ use tokio::sync::{ Notify, }; -use protocol::timestamp::Timestamp; - /// The Batcher instance itself holding the context for batching and the batched items. /// The intended way to add items to the batcher is through the Sender returned by the new function. /// This is done so that a single Batcher instance shouldn't be shared between multiple threads. @@ -24,7 +22,7 @@ pub struct Batcher { /// full batch had not yet been accumulated. batch_time_interval: Duration, /// Variable to track when the next batch should be returned. - next_batch_time: Timestamp, + next_batch_time: chrono::DateTime, /// Notify instance to notify the batcher that the batch is full and ready to be /// returned without waiting for the `batch_time_interval`. batch_ready_notify: Arc, diff --git a/src/bin/mock-authority/main.rs b/src/bin/mock-authority/main.rs new file mode 100644 index 0000000..3e70acd --- /dev/null +++ b/src/bin/mock-authority/main.rs @@ -0,0 +1,277 @@ +//! This is a mock of the election authorities servers which will be responsible for ensuring the +//! eligibility of the voters by signing their public keys. This is only used for testing purposes. + +use std::{ + io::Write, + path::{Path, PathBuf}, + sync::Arc, +}; + +use actix_web::{get, post, routes, web, App, HttpResponse, HttpServer, Responder}; +use anyhow::{anyhow, bail, Result}; +use clap::Parser; +use serde::{self, Deserialize, Serialize}; + +use crypto::signature::blind_sign; +use process_io::{cli::StdioReader, logging::start_logger}; +use tokio::{select, sync::oneshot, task::JoinHandle}; +use tracing::trace; + +#[derive(Parser, Clone, Debug)] +pub struct Args { + #[clap( + long = "address", + default_value = "0.0.0.0:8080", + help = "Specify the ip:port on which to host the mock election authority HTTP server" + )] + pub addr: std::net::SocketAddr, + #[clap( + long = "new-keys", + default_value_t = false, + help = "Generate new blind signer keys instead of loading them from FS" + )] + pub new_keys: bool, + #[clap( + long = "no-http", + default_value_t = false, + help = "Only run CLI and do not start an http server" + )] + pub no_http_server: bool, + #[clap( + long = "no-cli", + default_value_t = false, + help = "Only run HTTP server and do not start a CLI interface" + )] + pub no_cli: bool, + #[clap( + long = "data-path", + default_value = "./data", + help = "Path to where log, config and similar files will be stored" + )] + pub data_path: PathBuf, +} + +#[derive(Parser, Clone, Debug)] +pub enum Cmd { + #[clap(about = "Blind sign a blinded message")] + BlindSign { + blinded_msg: blind_sign::BlindedMessage, + }, + #[clap(about = "Get blinder public key")] + GetPubkey, + #[clap(about = "Shut down the mock authority")] + Quit, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct AuthorityConfig { + pk: blind_sign::PublicKey, + sk: blind_sign::SecretKey, +} + +impl AuthorityConfig { + fn load_from_fs(path: &Path) -> Result { + Ok(serde_json::from_slice(&std::fs::read(path)?)?) + } + + fn save_to_fs(&self, path: &Path) -> Result<()> { + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path)? + .write_all(&serde_json::to_vec(self)?)?; + + Ok(()) + } +} + +struct AppState { + blind_signer: Arc, +} + +fn new_blind_signer(path: &Path) -> Result { + let blind_signer = blind_sign::BlindSigner::new()?; + AuthorityConfig { + pk: blind_signer.get_public_key()?, + sk: blind_signer.get_secret_key()?, + } + .save_to_fs(path)?; + + Ok(blind_signer) +} + +fn setup_blind_signer( + new_keys: bool, + authority_config_path: &Path, +) -> Result { + if let Some(parent) = authority_config_path.parent() { + std::fs::create_dir_all(parent)?; + } + if new_keys { + if let Err(err) = std::fs::remove_file(authority_config_path) { + // It's not an error if the file actually doesn't exist, since we're deleting it anyway. + if err.kind() != std::io::ErrorKind::NotFound { + anyhow::bail!("Failed to delete old blind signer cfg file: {}", err); + } + } + } + + match load_blind_signer_from_fs(authority_config_path) { + Ok(blind_signer) => Ok(blind_signer), + Err(_) => Ok(new_blind_signer(authority_config_path)?), + } +} + +fn load_blind_signer_from_fs(path: &Path) -> Result { + let config = AuthorityConfig::load_from_fs(path)?; + Ok(blind_sign::BlindSigner::new_from_keys( + config.pk, config.sk, + )?) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Args::parse(); + let _tracing_worker_guard = start_logger(&args.data_path.join("authority.log"))?; + let blind_signer = Arc::new(setup_blind_signer( + args.new_keys, + &args.data_path.join("authority-config.json"), + )?); + + match (args.no_cli, args.no_http_server) { + (true, true) => bail!("Authority needs at least CLI interface or HTTP server to run"), + (true, false) => { + let (_stop_server, handle) = run_server(blind_signer, args.addr); + handle.await??; + } + (false, true) => run_cli( + &blind_signer, + args.data_path.join("authority-cmd-history.txt"), + )?, + (false, false) => { + let _server_shutdown = run_server(blind_signer.clone(), args.addr); + run_cli( + &blind_signer, + args.data_path.join("authority-cmd-history.txt"), + )?; + } + } + Ok(()) +} + +fn run_cli(blind_signer: &blind_sign::BlindSigner, cmd_history_path: PathBuf) -> Result<()> { + let mut stdio_reader = StdioReader::new(cmd_history_path)?; + + loop { + let line = match stdio_reader.read_stdio_blocking() { + Ok(line) => line, + Err(e) => { + println!("Quitting: {e:?}"); + break; + } + }; + let res = match Cmd::try_parse_from(line) { + Ok(Cmd::BlindSign { blinded_msg }) => blind_signer + .bling_sign(&blinded_msg) + .map_err(std::convert::Into::into) + .map(|blinded_signature| blinded_signature.to_string()), + Ok(Cmd::GetPubkey) => blind_signer + .get_public_key() + .map_err(std::convert::Into::into) + .map(|blinder_pk| blinder_pk.to_string()), + Ok(Cmd::Quit) => break, + Err(e) => Err(anyhow!("Unsupported command: {e}")), + }; + + match res { + Ok(res) => println!("{res}"), + Err(error) => println!("ERROR: {error}"), + } + } + + Ok(()) +} + +type Handle = (oneshot::Sender<()>, JoinHandle>); + +fn run_server(blind_signer: Arc, addr: std::net::SocketAddr) -> Handle { + let (tx, rx) = oneshot::channel::<()>(); + + let handle = tokio::spawn(async move { + let server = HttpServer::new(move || { + App::new() + .app_data(web::Data::new(AppState { + blind_signer: blind_signer.clone(), + })) + .wrap( + actix_cors::Cors::default() + // TODO Probably should be more specific: + .allow_any_origin() + .allow_any_header() + .allowed_methods(vec!["GET", "POST"]) + .max_age(3600), + ) + .service(greet) + .service(authenticate) + .service(get_pkey) + .service(health) + }) + .bind(addr)?; + trace!("Starting server"); + + select! { + _ = rx => { + Ok(()) + }, + _ = server.run() => { + bail!("Server stopped") + } + } + }); + + (tx, handle) +} + +#[routes] +#[get("/")] +#[get("/index.html")] +async fn greet() -> impl Responder { + trace!("GET root request"); + HttpResponse::Ok().body("Hello, mock authority!\n") +} + +#[post("/authenticate")] +pub async fn authenticate( + verification_request: web::Json, + data: web::Data, +) -> impl Responder { + trace!("POST /authenticate request"); + match data + .blind_signer + .bling_sign(&verification_request.blinded_pkey) + { + Ok(blind_signature) => HttpResponse::Ok().json(blind_signature), + Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")), + } +} + +#[get("/pkey")] +pub async fn get_pkey(data: web::Data) -> impl Responder { + trace!("POST /pkey request"); + match data.blind_signer.get_public_key() { + Ok(pkey) => HttpResponse::Ok().json(pkey.to_string()), + Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")), + } +} + +#[get("/health")] +pub async fn health() -> impl Responder { + trace!("GET /health request"); + HttpResponse::Ok() +} + +#[derive(Serialize, Deserialize, Debug)] +struct VerificationRequest { + blinded_pkey: blind_sign::BlindedMessage, +} diff --git a/src/bin/mock_authority.rs b/src/bin/mock_authority.rs deleted file mode 100644 index 047f95b..0000000 --- a/src/bin/mock_authority.rs +++ /dev/null @@ -1,208 +0,0 @@ -//! This is a mock of the election authorities servers which will be responsible for ensuring the -//! eligibility of the voters by signing their public keys. This is only used for testing purposes. - -use std::{io::Write, sync::Arc}; - -use actix_web::{get, post, routes, web, App, HttpResponse, HttpServer, Responder}; -use anyhow::{anyhow, Result}; -use clap::Parser; -use serde::{self, Deserialize, Serialize}; - -use crypto::signature::blind_sign; -use digital_voting::logging::start_logger; -use process_io::cli::StdioReader; - -#[derive(Parser, Clone, Debug)] -pub struct Args { - #[clap( - short = 'a', - long = "address", - default_value = "127.0.0.1:8081", - help = "Specify the ip:port on which to host the mock election authority HTTP server" - )] - pub addr: std::net::SocketAddr, - #[clap( - short = 'k', - long = "new-keys", - default_value_t = false, - help = "Generate new blind signer keys instead of loading them from FS" - )] - pub new_keys: bool, - #[clap( - short = 'n', - long = "no-http", - default_value_t = false, - help = "Only run CLI and do not start an http server" - )] - pub no_http_server: bool, -} - -#[derive(Parser, Clone, Debug)] -pub enum Cmd { - #[clap(about = "Blind sign a blinded message")] - BlindSign { - blinded_msg: blind_sign::BlindedMessage, - }, - #[clap(about = "Get blinder public key")] - GetPubkey, -} - -struct AppState { - blind_signer: Arc, -} - -fn new_blind_signer(path: &str) -> Result { - let blind_signer = blind_sign::BlindSigner::new()?; - let mut blind_signer_cfg_file = std::fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(path)?; - - writeln!(blind_signer_cfg_file, "{}", blind_signer.get_public_key()?)?; - writeln!(blind_signer_cfg_file, "{}", blind_signer.get_secret_key()?)?; - - Ok(blind_signer) -} - -fn setup_blind_signer(arg_new_keys: bool) -> Result { - let blind_signer_cfg_path = "authority-blind-signer-cfg"; - - if arg_new_keys { - if let Err(err) = std::fs::remove_file(blind_signer_cfg_path) { - // It's not an error if the file actually doesn't exist, since we're deleting it anyway. - if err.kind() != std::io::ErrorKind::NotFound { - anyhow::bail!("Failed to delete old blind signer cfg file: {}", err); - } - } - } - - match load_blind_signer_from_fs(blind_signer_cfg_path) { - Ok(blind_signer) => Ok(blind_signer), - Err(_) => Ok(new_blind_signer(blind_signer_cfg_path)?), - } -} - -fn load_blind_signer_from_fs(path: &str) -> Result { - if std::path::Path::new(path).exists() { - let blind_signer_cfg = std::fs::read_to_string(path)?; - let mut blind_signer_cfg = blind_signer_cfg.lines().take(2); - let (pk, sk) = ( - blind_signer_cfg - .next() - .ok_or(anyhow!("Failed to parse blind signer public key"))? - .parse()?, - blind_signer_cfg - .next() - .ok_or(anyhow!("Failed to parse blind signer secret key"))? - .parse()?, - ); - Ok(blind_sign::BlindSigner::new_from_keys(pk, sk)?) - } else { - Err(anyhow!("Blind signer config not found")) - } -} - -#[tokio::main] -async fn main() -> Result<()> { - let _tracing_worker_guard = start_logger("mock_authority.log")?; - let args = Args::parse(); - let blind_signer = Arc::new(setup_blind_signer(args.new_keys)?); - - println!("Starting mock authority server on: {}...", args.addr); - println!("With authority PK:\n{}", blind_signer.get_public_key()?); - if args.no_http_server { - run_cli(&blind_signer)?; - } else { - let blind_signer_clone = blind_signer.clone(); - tokio::task::spawn_blocking(move || run_cli(&blind_signer_clone)); - - run_server(blind_signer, args).await?; - } - - Ok(()) -} - -fn run_cli(blind_signer: &blind_sign::BlindSigner) -> Result<()> { - let mut stdio_reader = StdioReader::new()?; - - loop { - let line = match stdio_reader.read_stdio_blocking() { - Ok(line) => line, - Err(e) => { - // TODO - println!("Quitting: {e:?}, send interrupt again to kill the server (WIP)"); - break; - } - }; - let res = match Cmd::try_parse_from(line) { - Ok(Cmd::BlindSign { blinded_msg }) => blind_signer - .bling_sign(&blinded_msg) - .map_err(std::convert::Into::into) - .map(|blinded_signature| blinded_signature.to_string()), - Ok(Cmd::GetPubkey) => blind_signer - .get_public_key() - .map_err(std::convert::Into::into) - .map(|blinder_pk| blinder_pk.to_string()), - Err(e) => Err(anyhow!("Unsupported command: {e}")), - }; - - match res { - Ok(res) => println!("{res}"), - Err(error) => println!("ERROR: {error}"), - } - } - - Ok(()) -} - -async fn run_server(blind_signer: Arc, args: Args) -> Result<()> { - HttpServer::new(move || { - App::new() - .app_data(web::Data::new(AppState { - blind_signer: blind_signer.clone(), - })) - .service(greet) - .service(authenticate) - .service(get_pkey) - }) - .bind(args.addr)? - .run() - .await?; - - Ok(()) -} - -#[routes] -#[get("/")] -#[get("/index.html")] -async fn greet() -> impl Responder { - "Hello! Please send a POST request to /authenticate with a JSON body, containing a public key, a vote, some mock authentication data, and a signature.\n" -} - -#[post("/authenticate")] -pub async fn authenticate( - verification_request: web::Json, - data: web::Data, -) -> impl Responder { - match data - .blind_signer - .bling_sign(&verification_request.blinded_pkey) - { - Ok(blind_signature) => HttpResponse::Ok().json(blind_signature), - Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")), - } -} - -#[get("/pkey")] -pub async fn get_pkey(data: web::Data) -> impl Responder { - match data.blind_signer.get_public_key() { - Ok(pkey) => HttpResponse::Ok().json(pkey.to_string()), - Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")), - } -} - -#[derive(Serialize, Deserialize, Debug)] -struct VerificationRequest { - blinded_pkey: blind_sign::BlindedMessage, -} diff --git a/src/lib.rs b/src/lib.rs index 468278d..99f2957 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,13 @@ use std::{collections::HashMap, fmt::Display}; use thiserror::Error; pub mod api; +pub mod state; pub mod batcher; -pub mod logging; mod blockchain; use blockchain::{BlockValue, Blockchain, Error as BlockchainError}; -use protocol::{candidate_id::CandidateId, vote::Vote}; +use protocol::{config::CandidateId, vote::Vote}; #[derive(Error, Debug)] pub enum Error { @@ -57,7 +57,7 @@ impl VotingSystem { self.blockchain.iter().for_each(|values| { for vote in values { - let count = tally.entry(vote.get_candidate().clone()).or_insert(0); + let count = tally.entry(*vote.get_candidate()).or_insert(0); *count += 1; } }); diff --git a/src/main.rs b/src/main.rs index b8aa47a..d72f982 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,46 @@ -use anyhow::Result; +use std::{path::PathBuf, str::FromStr}; + +use anyhow::{anyhow, Result}; use clap::Parser; -use digital_voting::{api::server_cli::Args, logging::start_logger}; -use process_io::cli::StdioReader; +use digital_voting::{ + api::{cli::Args, config}, + state::State, +}; +use process_io::{cli::StdioReader, logging::start_logger}; +use tokio::task::JoinHandle; +use tracing::trace; #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); - println!("Args: {args:?}"); - let _tracing_worker_guard = start_logger("digital_voting.log")?; - - tokio::task::spawn_blocking(|| { - let mut stdio_reader = StdioReader::new().unwrap(); - loop { - let line = match stdio_reader.read_stdio_blocking() { - Ok(line) => line, - Err(e) => { - // TODO - println!("Quitting: {e:?}, send interrupt again to kill the server (WIP)"); - break; - } - }; - println!("Read line: {line:?}"); - } - }); + let _tracing_worker_guard = start_logger(&args.data_path.join("node.log"))?; + let blockchain_config = config::load_from_file(&args.data_path.join("blockchain-config.json")) + .await + .map_err(|e| anyhow!("Failed to load blockchain config: {e}"))?; + let state = State::new(blockchain_config); + trace!("Config loaded"); - digital_voting::api::server::run(args.socket_addr).await?; + let (stop_server, server_handle) = digital_voting::api::server::run(state, args.socket_addr)?; + if !args.no_cli { + let _cli_handle: JoinHandle> = tokio::task::spawn_blocking(|| { + let mut stdio_reader = StdioReader::new(PathBuf::from_str("node-cmd-history.txt")?)?; + loop { + let line = match stdio_reader.read_stdio_blocking() { + Ok(line) => line, + Err(e) => { + println!("Quitting: {e:?}"); + if let Err(()) = stop_server.send(()) { + println!("Server already down"); + } + break Ok(()); + } + }; + println!("Read line: {line:?}"); + } + }); + } + server_handle.await??; Ok(()) } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..046bc8e --- /dev/null +++ b/src/state.rs @@ -0,0 +1,17 @@ +use protocol::config::BlockchainConfig; + +pub struct State { + blockchain_config: BlockchainConfig, +} + +impl State { + #[must_use] + pub fn new(blockchain_config: BlockchainConfig) -> Self { + Self { blockchain_config } + } + + #[must_use] + pub fn get_blockchain_config(&self) -> &BlockchainConfig { + &self.blockchain_config + } +} diff --git a/subcrates/client/Cargo.toml b/subcrates/client/Cargo.toml index fcb0f7a..591331d 100644 --- a/subcrates/client/Cargo.toml +++ b/subcrates/client/Cargo.toml @@ -10,20 +10,22 @@ license = "Apache-2.0" repository = "https://github.com/ThrasherLT/digital-voting" [dependencies] -leptos = { version = "0.6.15", features = ["csr", "tracing"] } +leptos = { version = "0.7.4", features = ["csr", "tracing"] } console_error_panic_hook = "0.1.7" -leptos-use = { version = "0.13.8", default-features = false, features = ["use_clipboard", "storage"] } +leptos-use = { version = "0.15.5", default-features = false, features = ["use_clipboard", "storage"] } tracing-subscriber-wasm = "0.1.0" -serde_json = "1.0.132" codee = { version = "0.2.0", features = ["json_serde"] } anyhow = "1.0.91" -# reqwasm = "0.5.0" +reqwasm = "0.5.0" +futures = "0.3.31" +gloo-timers = { version = "0.3.0", features = ["futures"] } thiserror.workspace = true serde.workspace = true tracing.workspace = true tracing-subscriber.workspace = true chrono.workspace = true +serde_json.workspace = true crypto.workspace = true protocol.workspace = true diff --git a/subcrates/client/src/authentication.rs b/subcrates/client/src/authentication.rs index a6f3929..359b743 100644 --- a/subcrates/client/src/authentication.rs +++ b/subcrates/client/src/authentication.rs @@ -3,35 +3,38 @@ // TODO Docummentation // TODO local storage only works on pages that have the same origin -use crate::state::State; +use crate::states::user::User; use leptos::{ - component, create_node_ref, create_signal, event_target_checked, expect_context, view, - IntoView, NodeRef, Show, SignalGet, SignalSet, + component, + prelude::{ + event_target_checked, signal, ClassAttribute, ElementChild, Get, NodeRef, NodeRefAttribute, + OnAttribute, Read, RwSignal, Set, Show, + }, + view, IntoView, }; #[component] -pub fn User() -> impl IntoView { +pub fn Authentication(user_state: RwSignal>) -> impl IntoView { view! { - - + + } } #[component] -fn Login() -> impl IntoView { - let mut state = expect_context::(); - let (get_error, set_error) = create_signal(None); +fn Login(user_state: RwSignal>) -> impl IntoView { + let (get_error, set_error) = signal(None); - let (password_visible, set_password_visible) = create_signal(false); + let (password_visible, set_password_visible) = signal(false); let get_password_visibility = move || { - if password_visible.get() { + if *password_visible.read() { "text" } else { "password" } }; - let username_ref: NodeRef = create_node_ref(); - let password_ref: NodeRef = create_node_ref(); + let username_ref: NodeRef = NodeRef::new(); + let password_ref: NodeRef = NodeRef::new(); let on_submit = move |ev: leptos::ev::SubmitEvent| { ev.prevent_default(); let username = username_ref @@ -42,9 +45,9 @@ fn Login() -> impl IntoView { .get() .expect("Password should be mounted") .value(); - if let Err(e) = state.login_user(&username, &password) { - // TODO Better error reporting: - set_error.set(Some(format!("Error occured: {e}"))); + match User::login(username, &password) { + Ok(user) => user_state.set(Some(user)), + Err(e) => set_error.set(Some(format!("Error occured: {e}"))), } }; @@ -78,27 +81,26 @@ fn Login() -> impl IntoView { - +

{get_error.get().expect("Error to be some")}

} } #[component] -fn Register() -> impl IntoView { - let (get_error, set_error) = create_signal(None); - let (password_visible, set_password_visible) = create_signal(false); +fn Register(user_state: RwSignal>) -> impl IntoView { + let (get_error, set_error) = signal(None); + let (password_visible, set_password_visible) = signal(false); let get_password_visibility = move || { - if password_visible.get() { + if *password_visible.read() { "text" } else { "password" } }; - let username_ref: NodeRef = create_node_ref(); - let password_ref: NodeRef = create_node_ref(); - let repeat_password_ref: NodeRef = create_node_ref(); - let mut state = expect_context::(); + let username_ref: NodeRef = NodeRef::new(); + let password_ref: NodeRef = NodeRef::new(); + let repeat_password_ref: NodeRef = NodeRef::new(); let on_submit = move |ev: leptos::ev::SubmitEvent| { ev.prevent_default(); let username = username_ref @@ -121,9 +123,9 @@ fn Register() -> impl IntoView { set_error.set(Some("Username and password cannot be empty".to_owned())); return; } - if let Err(e) = state.register_user(&username, &password) { - // TODO Better error reporting: - set_error.set(Some(format!("Error occured: {e}"))); + match User::register(username, &password) { + Ok(user) => user_state.set(Some(user)), + Err(e) => set_error.set(Some(format!("Error occured: {e}"))), } }; @@ -163,7 +165,7 @@ fn Register() -> impl IntoView { - +

{get_error.get().expect("Error to be some")}

} diff --git a/subcrates/client/src/blockchain_selection.rs b/subcrates/client/src/blockchain_selection.rs new file mode 100644 index 0000000..28a8344 --- /dev/null +++ b/subcrates/client/src/blockchain_selection.rs @@ -0,0 +1,148 @@ +use std::time::Duration; + +use anyhow::{anyhow, bail, Result}; +use leptos::{ + component, + prelude::{ + signal, ClassAttribute, ElementChild, For, Get, NodeRef, NodeRefAttribute, OnAttribute, + Read, RwSignal, Set, Show, Signal, Update, WriteSignal, + }, + task::spawn_local, + view, IntoView, +}; +use protocol::config::BlockchainConfig; + +use crate::{fetch, states::user::User}; + +fn new_blockchain( + new_blockchain_addr: String, + set_user: RwSignal>, + blockchain_config: BlockchainConfig, +) -> Result<()> { + let mut res = Ok(()); + + set_user.update(|user| { + if let Some(user) = user { + if let Err(e) = user.add_blockchain(new_blockchain_addr.clone(), blockchain_config) { + res = Err(anyhow!(format!("Error fetching blockchain configs: {e}"))); + } + } else { + res = Err(anyhow!("Internal user error")); + } + }); + + res +} + +fn delete_blockchain(blockchain_addr: &str, set_user: &mut Option) -> Result<()> { + let Some(user) = set_user else { + bail!("Internal user error"); + }; + user.remove_blockchain(blockchain_addr) +} + +#[component] +pub fn SelectBlockchain( + user: RwSignal>, + set_blockchain: WriteSignal, +) -> impl IntoView { + view! { +

"Blockchain Node Selection"

+ + + } +} + +#[component] +fn NewBlockchain(user: RwSignal>) -> impl IntoView { + let (get_error, set_error) = signal(Option::::None); + let new_blockchain_addr_ref: NodeRef = NodeRef::new(); + + let on_submit = move |ev: leptos::ev::SubmitEvent| { + ev.prevent_default(); + let new_blockchain_addr = new_blockchain_addr_ref + .get() + .expect("New blockchain input should be mounted") + .value(); + + spawn_local(async move { + match fetch::blockchain_config(new_blockchain_addr.clone(), Duration::from_secs(5)) + .await + { + Ok(blockchain_config) => { + if let Err(e) = new_blockchain(new_blockchain_addr, user, blockchain_config) { + set_error.set(Some(format!("Error setting up new blockchain: {e}"))) + } else { + set_error.set(None); + } + } + Err(e) => set_error.set(Some(format!("Error fetching blockchain configs: {e}"))), + }; + }) + }; + + view! { +
+ + +
+ + +

{get_error.get().expect("Error to be some")}

+
+ } +} + +#[component] +fn BlockchainList( + user: RwSignal>, + set_blockchain: WriteSignal, +) -> impl IntoView { + let (get_error, set_error) = signal(None); + + let blockchain_list = Signal::derive(move || { + let blockchain_list: Vec> = user + .read() + .as_ref() + .map(|user| user.blockchains.clone()) + .unwrap_or_default() + .iter() + .map(|blockchain| RwSignal::new(blockchain.clone())) + .collect(); + blockchain_list + }); + + view! { +
    + +
  • + // TODO create separate component for buttons with confirmation prompt. + + +
  • +
    +
+ + +

{get_error.get().expect("Error to be some")}

+
+ } +} diff --git a/subcrates/client/src/candidate_selection.rs b/subcrates/client/src/candidate_selection.rs new file mode 100644 index 0000000..cdcc990 --- /dev/null +++ b/subcrates/client/src/candidate_selection.rs @@ -0,0 +1,125 @@ +use std::{str::FromStr, time::Duration}; + +use anyhow::{bail, Result}; +use leptos::{ + component, + prelude::{ + event_target_value, signal, ClassAttribute, CollectView, ElementChild, Get, OnAttribute, + Read, ReadSignal, Set, Show, Signal, WriteSignal, + }, + task::spawn_local, + view, IntoView, +}; +use protocol::{config::CandidateId, vote::Vote}; + +use crate::{ + fetch, + states::{ + access_tokens::AccessTokens, candidate::Candidate, config::Config, signature::Signature, + user::User, + }, +}; + +fn send_vote( + user: Signal, + blockchain: ReadSignal, + candidate_id: ReadSignal>, + access_tokens: ReadSignal, + set_candidate: WriteSignal>, + set_error: WriteSignal>, +) -> Result<()> { + let Some(candidate_id) = candidate_id.get() else { + bail!("Candidate ID is not selected"); + }; + + let vote = Vote::new( + &Signature::load(&user.read(), &blockchain.read())?.signer, + candidate_id, + chrono::Utc::now(), + access_tokens.read().prepare()?, + )?; + + // For some reason `leptos` throws a "time not implemented on this platform" error, + // if we read from any signal within the `spawn_local` future. + let addr = blockchain.get().to_string(); + let user = user.read(); + + spawn_local(async move { + if let Err(e) = fetch::submit_vote(addr.clone(), Duration::from_secs(5), vote).await { + set_error.set(Some(format!("Failed to submit vote: {e}"))); + } else { + match Candidate::choose(candidate_id, &user, &addr) { + Ok(candidate) => set_candidate.set(Some(candidate)), + Err(e) => set_error.set(Some(format!("Failed to save candidate: {e}"))), + } + } + }); + + Ok(()) +} + +#[component] +pub fn CandidateSelection( + user: Signal, + blockchain: ReadSignal, + set_candidate: WriteSignal>, + access_tokens: ReadSignal, +) -> impl IntoView { + let (get_error, set_error) = signal(None); + let (selected, set_selected) = signal(None); + + let config = + Config::load(&user.read(), &blockchain.read()).expect("Config to exist at this point"); + let candidates = config + .blockchain_config + .candidates + .into_iter() + .map(|candidate| { + view! { + + } + }) + .collect_view(); + + view! { +

"Election Candidate Selection"

+ + + + + +

{move || get_error.get().expect("Error to be some")}

+
+ } +} diff --git a/subcrates/client/src/fetch.rs b/subcrates/client/src/fetch.rs new file mode 100644 index 0000000..b9a69f8 --- /dev/null +++ b/subcrates/client/src/fetch.rs @@ -0,0 +1,68 @@ +use std::{pin::pin, time::Duration}; + +use anyhow::{anyhow, bail, Result}; +use futures::future::{select, Either}; +use leptos::logging::log; +use protocol::{config::BlockchainConfig, vote::Vote}; +use reqwasm::http::Response; + +pub async fn blockchain_config(addr: String, timeout: Duration) -> Result { + let addr = format!("{addr}/config"); + let response = get(&addr, timeout).await?; + if response.status() != 200 { + bail!("Error code {}", response.status()); + } + let config: BlockchainConfig = response.json().await?; + + Ok(config) +} + +pub async fn submit_vote(addr: String, timeout: Duration, vote: Vote) -> Result<()> { + let addr = format!("{addr}/vote"); + let vote = serde_json::to_string(&vote)?; + log!("{}", vote); + + let response = post(vote, &addr, timeout).await?; + if response.status() != 200 { + bail!("Error code {}", response.status()); + } + + Ok(()) +} + +async fn get(addr: &str, timeout: Duration) -> Result { + let fetch_future = pin!(async { + match reqwasm::http::Request::get(&addr).send().await { + Ok(response) => Ok(response), + Err(_) => Err(anyhow!("Request failed".to_string())), + } + }); + + let timeout_future = gloo_timers::future::TimeoutFuture::new(timeout.as_millis().try_into()?); + + match select(fetch_future, timeout_future).await { + Either::Left((result, _)) => result, + Either::Right(_) => Err(anyhow!("Request timed out".to_string())), + } +} + +async fn post(payload: String, addr: &str, timeout: Duration) -> Result { + let fetch_future = pin!(async { + match reqwasm::http::Request::post(&addr) + .header("Content-Type", "application/json") + .body(payload) + .send() + .await + { + Ok(response) => Ok(response), + Err(_) => Err(anyhow!("Request failed".to_string())), + } + }); + + let timeout_future = gloo_timers::future::TimeoutFuture::new(timeout.as_millis().try_into()?); + + match select(fetch_future, timeout_future).await { + Either::Left((result, _)) => result, + Either::Right(_) => Err(anyhow!("Request timed out".to_string())), + } +} diff --git a/subcrates/client/src/main.rs b/subcrates/client/src/main.rs index 1f47dcd..5b1b387 100644 --- a/subcrates/client/src/main.rs +++ b/subcrates/client/src/main.rs @@ -1,23 +1,23 @@ -//! This is the main entry point of the client for the whole blockchain. -//! The client is implemented as a browser extension, but may be conveniently -//! build into a website for quick development and testing with hot reloading. -//! The browser extension had been set up to work with manifest version 3. - use leptos::{ - component, expect_context, mount_to_body, provide_context, view, IntoView, Show, Signal, - SignalWith, + component, + mount::mount_to_body, + prelude::{ElementChild, Read, RwSignal, Show, Signal}, + view, IntoView, }; +use states::user::User; mod authentication; +mod blockchain_selection; +mod candidate_selection; +mod fetch; mod settings; -mod state; +mod states; mod storage; mod utils; mod validation; +mod verification; mod vote; -use state::{State, Status}; - // Configuration for wasm-bindgen-test to run tests in browser. #[cfg(test)] mod tests { @@ -40,23 +40,36 @@ fn main() { #[must_use] #[component] pub fn App() -> impl IntoView { - provide_context(State::new()); - let state = expect_context::(); - - let status = Signal::derive(move || state.get_status()); + let blockchain = RwSignal::new(String::new()); + let user_state = RwSignal::new(Option::::None); + let get_user = Signal::derive(move || { + user_state + .read_only() + .read() + .as_ref() + .expect("User to have existed by now") + .to_owned() + }); view! { - - +

"Untitled Voting System"

+ + - Status::LoggedOut }) fallback=|| ()> - + + - - + + - - + + } } diff --git a/subcrates/client/src/settings.rs b/subcrates/client/src/settings.rs index cd343f6..74865ef 100644 --- a/subcrates/client/src/settings.rs +++ b/subcrates/client/src/settings.rs @@ -1,13 +1,15 @@ //! File containing code which handles the user settings for the browser extension. -use crate::state::State; +use crate::states::user::User; use leptos::{ - component, create_signal, expect_context, view, IntoView, Show, SignalGet, SignalSet, + component, + prelude::{signal, ElementChild, Get, OnAttribute, RwSignal, Set, Show, Update}, + view, IntoView, }; #[component] -pub fn SettingsPanel() -> impl IntoView { - let (show_settings, set_show_settings) = create_signal(false); +pub fn SettingsPanel(user: RwSignal>) -> impl IntoView { + let (show_settings, set_show_settings) = signal(false); view! { impl IntoView { - + + + } } #[component] -pub fn User() -> impl IntoView { - let (double_check, set_double_check) = create_signal(false); +fn User(user: RwSignal>) -> impl IntoView { + let (double_check, set_double_check) = signal(false); view! { impl IntoView { }>"Delete User"

"Are you sure? This cannot be undone!"

- - - -

{get_error.get().expect("Error to be some")}

-
- } -} +use crate::{ + states::{ + access_tokens::AccessTokens, config::Config, signature::Signature, user::User, + validators::Validators, + }, + utils, +}; -#[must_use] -#[component] -pub fn BlindedPkDisplay() -> impl IntoView { - let state = expect_context::(); - // TODO Check if nothing is overwriting this value, saw some flicker on the screen. - let display_value = - Signal::derive(move || state.get_blinded_pk().get().map(|val| val.to_string())); +// TODO Figure out a clean way to handle errors and excepts. +// TODO Blockchain validation must apply to all nodes of the same blockchain. - view! { } -} - -#[must_use] #[component] -pub fn UnblindAccessToken() -> impl IntoView { - let mut state = expect_context::(); - let (get_error, set_error) = create_signal(None); - - let input_ref: NodeRef = create_node_ref(); - let on_submit = move |ev: leptos::ev::SubmitEvent| { - // stop the page from reloading! - ev.prevent_default(); - - // here, we'll extract the value from the input - let blind_signature = input_ref.get().expect("Input to be mounted").value(); - - match blind_sign::BlindSignature::from_str(&blind_signature) { - Ok(blind_signature) => { - if let Err(e) = state.unblind(blind_signature) { - // TODO better error reporting: - set_error.set(Some(format!("Error had occured: {e}"))); +pub fn Validation( + user: Signal, + blockchain: ReadSignal, + access_tokens: RwSignal, +) -> impl IntoView { + let config = Config::load(&user.read(), &blockchain.read()).expect("Config to be loaded"); + let (signature, _) = + signal(Signature::load(&user.read(), &blockchain.read()).expect("Signature to be loaded")); + + let (validators, _) = signal( + Validators::load(&config.blockchain_config, &user.read(), &blockchain.read()) + .expect("Validators to be loaded"), + ); + + let entries = config + .get_authorities() + .into_iter() + .zip(validators.read().get_blinded_pks()) + .enumerate() + .map(|(i, (authority, blinded_pk))| { + let (blinded_pk, _) = signal(Some(format!("{}", blinded_pk))); + let (get_error, set_error) = signal(None); + let blind_signature_ref: NodeRef = NodeRef::new(); + + let on_submit = move |ev: leptos::ev::SubmitEvent| { + // Stop the page from reloading: + ev.prevent_default(); + let blind_signature = blind_signature_ref + .get() + .expect("Input to be mounted") + .value(); + + match blind_sign::BlindSignature::from_str(&blind_signature) { + Ok(blind_signature) => { + match validators + .read() + .validate(&signature.read(), i, blind_signature) + { + Ok(access_token) => access_tokens.update(|access_tokens| { + if let Err(e) = access_tokens.set( + &user.read(), + &blockchain.read(), + i, + Some(access_token), + ) { + set_error.set(Some(format!("Failed to save access token: {e}"))) + } + }), + Err(e) => set_error.set(Some(format!( + "Invalid valid blind_signature in position {i}: {e}" + ))), + } + } + Err(e) => set_error.set(Some(format!( + "Not a valid blind_signature in position {i}: {e}" + ))), } + }; + let link = authority.clone(); + view! { + + {authority} + + "Access token acquired!"

} + > + +
+ +
+ +

{move || get_error.get().expect("Error to be some")}

+
+
} - Err(e) => { - set_error.set(Some(format!("Invalid blinded authority signature: {e}"))); - } - } - }; + }) + .collect_view(); - view! { -
- -
- -

{get_error.get().expect("Error to be some")}

-
- } + view! {
    {entries}
} } diff --git a/subcrates/client/src/verification.rs b/subcrates/client/src/verification.rs new file mode 100644 index 0000000..59142b0 --- /dev/null +++ b/subcrates/client/src/verification.rs @@ -0,0 +1,17 @@ +use leptos::{ + component, + html::ElementChild, + prelude::{Get, ReadSignal}, + view, IntoView, +}; + +use crate::states::candidate::Candidate; + +#[component] +pub fn Verification(candidate: ReadSignal>) -> impl IntoView { + view! { +

"Election Verification"

+

{format!("You have successfully voted for {:?}", candidate.get())}

+

"WIP"

+ } +} diff --git a/subcrates/client/src/vote.rs b/subcrates/client/src/vote.rs index feaa85e..9cfa5c6 100644 --- a/subcrates/client/src/vote.rs +++ b/subcrates/client/src/vote.rs @@ -1,81 +1,56 @@ -//! This file contains the logic for casting an actual vote. - use leptos::{ - component, create_node_ref, create_signal, event_target_value, expect_context, html, view, - IntoView, NodeRef, Show, SignalGet, SignalSet, + component, + prelude::{signal, ElementChild, Get, OnAttribute, Read, RwSignal, Set, Show, Signal}, + view, IntoView, }; -use crate::state::State; +use crate::{ + candidate_selection, + states::{access_tokens::AccessTokens, candidate::Candidate, user::User}, + validation, verification, +}; +#[must_use] #[component] -pub fn Cast() -> impl IntoView { - let mut state = expect_context::(); - let (get_error, set_error) = create_signal(None); - // Temporarily holding the selected candidate in this signal before commiting it to the state. - let (candidate, set_candidate) = create_signal(Option::::None); - - let blockchain_addr_ref: NodeRef = create_node_ref(); - - let on_submit = move |ev: leptos::ev::SubmitEvent| { - // stop the page from reloading: - ev.prevent_default(); - let blockchain_addr = blockchain_addr_ref - .get() - .expect("Blockchain URL input should be mounted") - .value(); - - if blockchain_addr.is_empty() { - set_error.set(Some("Blockchain URL cannot be empty".to_owned())); - return; - } - let selected_candidate = candidate.get(); - match selected_candidate { - Some(selected_candidate) => { - if let Err(e) = state.vote(&selected_candidate, &blockchain_addr) { - set_error.set(Some(format!("Failed to vote: {e}"))); - } - } - None => set_error.set(Some("Candidate not selected".to_owned())), - } - }; +pub fn Vote(user: Signal, blockchain: RwSignal) -> impl IntoView { + let access_tokens = RwSignal::new( + AccessTokens::load(&user.read(), &blockchain.read()).expect("Access tokens to be loaded"), + ); + let (candidate, set_candidate) = + signal(Candidate::load(&user.read(), &blockchain.read()).expect("Candidate to load")); view! { - - -

{get_error.get().expect("Error to be some")}

+ + + + + + + + + + + + - } -} - -#[component] -fn Config() -> impl IntoView { - // TODO For now candidate config is hardcoded: - view! { - - - } } diff --git a/subcrates/crypto/src/encryption/symmetric.rs b/subcrates/crypto/src/encryption/symmetric.rs index a3d0aa3..dff7d72 100644 --- a/subcrates/crypto/src/encryption/symmetric.rs +++ b/subcrates/crypto/src/encryption/symmetric.rs @@ -36,6 +36,7 @@ type Result = std::result::Result; const SALT_LEN: usize = 32; /// Newtype for unique SALT generated for each user and used for deriving salt for encryption key. +#[derive(Clone)] struct Salt([u8; SALT_LEN]); impl Salt { @@ -165,6 +166,7 @@ impl AsRef<[u8]> for MetaData { } /// Encryption state structure. +#[derive(Clone)] pub struct Encryption { /// The AEAD key used to encrypt and decrypt messages. key: aead::LessSafeKey, diff --git a/subcrates/process_io/Cargo.toml b/subcrates/process_io/Cargo.toml index e7bd527..21cdfa0 100644 --- a/subcrates/process_io/Cargo.toml +++ b/subcrates/process_io/Cargo.toml @@ -10,7 +10,10 @@ license = "Apache-2.0" repository = "https://github.com/ThrasherLT/digital-voting" [dependencies] -rustyline = { version = "14.0.0", features = ["derive"] } +rustyline = { version = "15.0.0", features = ["derive"] } shellwords = "1.1.0" +tracing-appender = "0.2.3" +tracing-subscriber.workspace = true +tracing = { workspace = true, features = ["max_level_trace", "release_max_level_trace"] } thiserror.workspace = true diff --git a/subcrates/process_io/src/cli.rs b/subcrates/process_io/src/cli.rs index f99a84c..e79bdd3 100644 --- a/subcrates/process_io/src/cli.rs +++ b/subcrates/process_io/src/cli.rs @@ -3,13 +3,16 @@ use rustyline::{ completion::FilenameCompleter, error::ReadlineError, - highlight::{Highlighter, MatchingBracketHighlighter}, + highlight::{CmdKind, Highlighter, MatchingBracketHighlighter}, hint::HistoryHinter, history::DefaultHistory, validate::MatchingBracketValidator, Completer, CompletionType, Config, EditMode, Editor, Helper, Hinter, Validator, }; -use std::borrow::Cow::{self, Borrowed, Owned}; +use std::{ + borrow::Cow::{self, Borrowed, Owned}, + path::PathBuf, +}; use thiserror::Error; // Error type of the CLI module. @@ -24,6 +27,9 @@ pub enum Error { /// There was an error reading while reading the name of the current executable. #[error(transparent)] MismatchedQuotes(#[from] shellwords::MismatchedQuotes), + /// The provided path was invalid. + #[error("Invalid path")] + Path, } type Result = std::result::Result; @@ -36,7 +42,9 @@ pub struct StdioReader { /// The name of the executable. /// Used for adding to the input command, so that `clap` can parse it. /// Storing this here to avoid the extra operations needed to retrieve it. - exec_name: String, + exec_path: String, + /// Path to where the command history file will be stored. + cmd_history_path: PathBuf, } impl StdioReader { @@ -49,7 +57,7 @@ impl StdioReader { /// # Errors /// /// If there was an error creating the Editor for Rustyline. - pub fn new() -> Result { + pub fn new(cmd_history_path: PathBuf) -> Result { let config = Config::builder() .completion_type(CompletionType::List) .auto_add_history(true) @@ -62,13 +70,20 @@ impl StdioReader { colored_prompt: String::new(), validator: MatchingBracketValidator::new(), }; - let exec_name = std::env::current_exe()?; - let exec_name = exec_name.to_string_lossy().to_string(); + let exec_path = std::env::current_exe()?; + let exec_path = exec_path.to_string_lossy().to_string(); let mut rl = Editor::with_config(config)?; rl.set_helper(Some(h)); - let _ = rl.load_history("node-cmd-history.txt"); + if let Some(parent) = cmd_history_path.parent() { + std::fs::create_dir_all(parent)?; + } + let _ = rl.load_history(&cmd_history_path); - Ok(Self { rl, exec_name }) + Ok(Self { + rl, + exec_path, + cmd_history_path, + }) } /// Read a line from stdio. This function blocks until a line is read. @@ -91,7 +106,7 @@ impl StdioReader { let line = self.rl.readline(&prompt)?; let mut line = shellwords::split(&line)?; - line.insert(0, self.exec_name.clone()); + line.insert(0, self.exec_path.clone()); Ok(line) } @@ -101,7 +116,7 @@ impl Drop for StdioReader { /// The command history is saved to a file when the `StdioReader` is dropped. /// So `StdioReader` should only really be dropped when the program is exiting. fn drop(&mut self) { - let _ = self.rl.save_history("node-cmd-history.txt"); + let _ = self.rl.save_history(&self.cmd_history_path); } } @@ -139,7 +154,7 @@ impl Highlighter for MyHelper { self.highlighter.highlight(line, pos) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { - self.highlighter.highlight_char(line, pos, forced) + fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool { + self.highlighter.highlight_char(line, pos, kind) } } diff --git a/subcrates/process_io/src/lib.rs b/subcrates/process_io/src/lib.rs index 4f77372..2b8e049 100644 --- a/subcrates/process_io/src/lib.rs +++ b/subcrates/process_io/src/lib.rs @@ -1 +1,2 @@ pub mod cli; +pub mod logging; diff --git a/src/logging.rs b/subcrates/process_io/src/logging.rs similarity index 71% rename from src/logging.rs rename to subcrates/process_io/src/logging.rs index edd65ad..9f5afdc 100644 --- a/src/logging.rs +++ b/subcrates/process_io/src/logging.rs @@ -1,11 +1,21 @@ +use std::path::Path; + use tracing::level_filters::LevelFilter; use tracing_appender::non_blocking::WorkerGuard as TracingWorkerGuard; /// Start a logger that writes traces to a file without blocking. -pub fn start_logger(log_filename: &str) -> std::io::Result { +/// +/// # Errors +/// +/// If the path to the log file is invalid. +/// If initializing the `tracing_subscriber` fails. +pub fn start_logger(log_path: &Path) -> std::io::Result { + if let Some(parent) = log_path.parent() { + std::fs::create_dir_all(parent)?; + } + + let log_file = std::fs::File::create(log_path)?; // Set up a rolling file appender - std::fs::create_dir_all("logs")?; - let log_file = std::fs::File::create(format!("logs/{log_filename}"))?; // Do not let _tracing_worker_guard go out of scope, or the logging thread will be terminated. let (non_blocking_tracing_writer, tracing_worker_guard) = tracing_appender::non_blocking(log_file); diff --git a/subcrates/protocol/src/config.rs b/subcrates/protocol/src/config.rs new file mode 100644 index 0000000..e64bacc --- /dev/null +++ b/subcrates/protocol/src/config.rs @@ -0,0 +1,23 @@ +use crypto::signature::blind_sign; + +/// This configurably defines what underlying primitive type will be used for the candidate ID. +pub type CandidateId = u8; + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct Authority { + pub addr: String, + pub authority_key: blind_sign::PublicKey, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct Candidate { + pub name: String, + pub id: CandidateId, +} + +#[allow(clippy::module_name_repetitions)] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct BlockchainConfig { + pub authorities: Vec, + pub candidates: Vec, +} diff --git a/subcrates/protocol/src/lib.rs b/subcrates/protocol/src/lib.rs index 16b39b6..eb4bd06 100644 --- a/subcrates/protocol/src/lib.rs +++ b/subcrates/protocol/src/lib.rs @@ -1,6 +1,6 @@ //! Crate which describes the protocol and fundamental operation of the blockchain. -pub mod candidate_id; +pub mod config; pub mod timestamp; pub mod vote; diff --git a/subcrates/protocol/src/vote.rs b/subcrates/protocol/src/vote.rs index bbd8189..582c70b 100644 --- a/subcrates/protocol/src/vote.rs +++ b/subcrates/protocol/src/vote.rs @@ -8,8 +8,10 @@ use crypto::{ use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::candidate_id::CandidateId; -use crate::timestamp::{Limits as TimestampLimits, Timestamp}; +use crate::{ + config::CandidateId, + timestamp::{Limits as TimestampLimits, Timestamp}, +}; /// Errors that can occur when working with election votes. #[derive(Error, Debug)] @@ -41,7 +43,7 @@ pub struct Vote { /// The public key of this signature is the public key of the election authority. /// Each access token on the blockchain must be unique. timestamp: Timestamp, - access_token: blind_sign::Signature, + access_tokens: Vec, /// Digital signature corresponding to the `public_key`. /// It signs all previous fields. signature: digital_sign::Signature, @@ -67,16 +69,16 @@ impl Vote { signer: &digital_sign::Signer, candidate: CandidateId, timestamp: Timestamp, - access_token: &blind_sign::Signature, + access_tokens: Vec, ) -> Result { let public_key = signer.get_public_key(); - let to_sign = Self::signed_bytes(&public_key, &candidate, ×tamp, access_token)?; + let to_sign = Self::signed_bytes(&public_key, candidate, timestamp, &access_tokens)?; Ok(Self { public_key, candidate, timestamp, - access_token: access_token.clone(), + access_tokens, signature: signer.sign(&to_sign), }) } @@ -99,15 +101,22 @@ impl Vote { /// A new Vote instance. fn signed_bytes( public_key: &digital_sign::PublicKey, - candidate: &CandidateId, - timestamp: &Timestamp, - access_token: &blind_sign::Signature, + candidate: CandidateId, + timestamp: Timestamp, + access_tokens: &Vec, ) -> Result> { - let mut to_sign = - Vec::with_capacity(public_key.len() + candidate.as_ref().len() + access_token.len()); + let mut access_tokens_total_len = 0; + for access_token in access_tokens { + access_tokens_total_len += access_token.len(); + } + let mut to_sign = Vec::with_capacity( + public_key.len() + std::mem::size_of::() + access_tokens_total_len, + ); to_sign.extend_from_slice(public_key.as_ref()); - to_sign.extend_from_slice(candidate.as_ref()); - to_sign.extend_from_slice(access_token.as_ref()); + to_sign.extend_from_slice(&candidate.to_le_bytes()); + for access_token in access_tokens { + to_sign.extend_from_slice(access_token.as_ref()); + } to_sign.append(&mut bincode::serialize(×tamp)?); Ok(to_sign) @@ -117,25 +126,28 @@ impl Vote { /// /// # Arguments /// - /// - `access_token_verifyer` - Verifyer of the blind signature of the election authority. + /// - `access_token_verifiers` - A list of verifiers of the access tokens. + /// - `timestamp_limits` - The limits of an acceptable timestamp. /// /// # Errors /// /// If the vote is invalid or corrupted. pub fn verify( &self, - access_token_verifyer: &blind_sign::Verifier, + access_token_verifiers: &[blind_sign::Verifier], timestamp_limits: &TimestampLimits, ) -> Result<()> { if !timestamp_limits.verify(self.timestamp) { return Err(Error::InvalidTimestmap(self.timestamp)); } - access_token_verifyer.verify_signature(self.access_token.clone(), &self.public_key)?; + for (i, access_token) in self.access_tokens.iter().enumerate() { + access_token_verifiers[i].verify_signature(access_token.clone(), &self.public_key)?; + } let signed_bytes = Self::signed_bytes( &self.public_key, - &self.candidate, - &self.timestamp, - &self.access_token, + self.candidate, + self.timestamp, + &self.access_tokens, )?; Ok(digital_sign::verify( &signed_bytes, @@ -150,9 +162,7 @@ impl std::fmt::Display for Vote { write!( f, "{} voted for candidate {} on {}", - self.public_key, - self.candidate, - self.timestamp.format("%Y-%m-%d %H:%M:%S") + self.public_key, self.candidate, self.timestamp, ) } } @@ -166,35 +176,47 @@ mod tests { fn generate_vote_for_testing( timestamp: Timestamp, candidate: CandidateId, - ) -> (Vote, blind_sign::PublicKey) { - let blind_signer = blind_sign::BlindSigner::new().unwrap(); - let authority_pubkey = blind_signer.get_public_key().unwrap(); + authority_count: usize, + ) -> (Vote, Vec) { let digital_signer = digital_sign::Signer::new().unwrap(); - let msg = digital_signer.get_public_key(); - let blinder = blind_sign::Blinder::new(blind_signer.get_public_key().unwrap()).unwrap(); - let (blind_msg, unblinder) = blinder.blind(&msg).unwrap(); - let blind_signature = blind_signer.bling_sign(&blind_msg).unwrap(); - let access_token = unblinder - .unblind_signature(blind_signature.clone(), &msg) - .unwrap(); - let vote = Vote::new(&digital_signer, candidate, timestamp, &access_token).unwrap(); - - (vote, authority_pubkey) + let mut access_tokens = Vec::new(); + let mut authority_pubkeys = Vec::new(); + + for _ in 0..authority_count - 1 { + let blind_signer = blind_sign::BlindSigner::new().unwrap(); + authority_pubkeys.push(blind_signer.get_public_key().unwrap()); + let msg = digital_signer.get_public_key(); + let blinder = blind_sign::Blinder::new(blind_signer.get_public_key().unwrap()).unwrap(); + let (blind_msg, unblinder) = blinder.blind(&msg).unwrap(); + let blind_signature = blind_signer.bling_sign(&blind_msg).unwrap(); + + let access_token = unblinder + .unblind_signature(blind_signature.clone(), &msg) + .unwrap(); + access_tokens.push(access_token); + } + let vote = Vote::new(&digital_signer, candidate, timestamp, access_tokens).unwrap(); + + (vote, authority_pubkeys) } // TODO Not sure if it's a good idea to couple this test to crypto subcrate. #[wasm_bindgen_test] #[test] fn test_vote() { + let authority_count = 3; let timestamp = chrono::Utc::now(); - let (vote, access_token) = generate_vote_for_testing(timestamp, CandidateId::new(1)); - - let verifier = blind_sign::Verifier::new(access_token).unwrap(); + let (vote, authority_pubkeys) = + generate_vote_for_testing(timestamp, 2.into(), authority_count); let timestamp_limits = TimestampLimits::new( timestamp - std::time::Duration::from_secs(1), timestamp + std::time::Duration::from_secs(1), ) .unwrap(); - vote.verify(&verifier, ×tamp_limits).unwrap(); + let authorities: Vec = authority_pubkeys + .iter() + .map(|pk| blind_sign::Verifier::new(pk.clone()).unwrap()) + .collect(); + vote.verify(&authorities, ×tamp_limits).unwrap(); } } diff --git a/testing/.env b/testing/.env new file mode 100644 index 0000000..a4fe9d0 --- /dev/null +++ b/testing/.env @@ -0,0 +1,3 @@ +export APP_PORT=8080 +# Using this deterministic port range for more convenient debugging for now: +export HOST_PORT=32912-32920 diff --git a/testing/Dockerfile b/testing/Dockerfile new file mode 100644 index 0000000..0ef38b4 --- /dev/null +++ b/testing/Dockerfile @@ -0,0 +1,4 @@ +FROM ubuntu:latest + +# You can specify the internal dependencies for the containers here: +RUN apt-get update && apt-get install -y curl jq diff --git a/testing/aggregate-blockchain-config.sh b/testing/aggregate-blockchain-config.sh new file mode 100755 index 0000000..5a4d464 --- /dev/null +++ b/testing/aggregate-blockchain-config.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# This used to generate the blockchain config file from the authority config files. + +authorities=$(jq -n '[]') + +for dir in $(find ./data/ -type d -iname "*authority*"); do + PUBKEY=$(jq -r ".pk" "$dir/authority-config.json") + PORT=$(cat "$dir/port") + + new_authority=$(jq -n \ + --arg addr "http://${HOSTNAME}:${PORT}" \ + --arg authority_key "$PUBKEY" \ + '{addr: $addr, authority_key: $authority_key}') + authorities=$(jq --argjson new_authority "$new_authority" '. += [$new_authority]' <<< "$authorities") +done + +candidates='[{"name": "Ricky", "id": 0}, {"name": "Randy", "id": 1}]' + +blockchain_config=$(jq -n \ + --argjson candidates "$candidates" \ + --argjson authorities "$authorities" \ + '{candidates: $candidates, authorities: $authorities}') + +echo "$blockchain_config" > data/blockchain-config.json diff --git a/testing/clean.sh b/testing/clean.sh new file mode 100755 index 0000000..1dace9b --- /dev/null +++ b/testing/clean.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Use this script to clean all files from the data directory. +# This is useful if you want to generate a new blockchain configuration. + +sudo rm -rf data/* diff --git a/testing/docker-compose.yml b/testing/docker-compose.yml new file mode 100644 index 0000000..27ebb17 --- /dev/null +++ b/testing/docker-compose.yml @@ -0,0 +1,39 @@ +version: '3.8' + +x-common: &common + build: + context: . + ports: + - "${HOST_PORT}:${APP_PORT}" + volumes: + - ./data:/data:rw + - ./../target/:/exec:ro + - ./startup.sh:/startup.sh + - /var/run/docker.sock:/var/run/docker.sock + entrypoint: ["/startup.sh"] + stdin_open: true + tty: true + healthcheck: + test: ["CMD", "curl", "http://localhost:${APP_PORT}/health"] + interval: 10s + retries: 5 + start_period: 4s + timeout: 4s + + +services: + authority: + deploy: + replicas: 3 + environment: + - APP=authority + - APP_PORT=${APP_PORT} + <<: *common + + node: + deploy: + replicas: 5 + environment: + - APP=node + - APP_PORT=${APP_PORT} + <<: *common diff --git a/testing/readme.md b/testing/readme.md new file mode 100644 index 0000000..31cb7a8 --- /dev/null +++ b/testing/readme.md @@ -0,0 +1,36 @@ +# Testing Environment + +This is a simple testing environment based on docker compose. + +## Platform Support + +Currently this testing environment is only supported on linux + +## Setup + +In order to run the testing environment, follow these steps: + +- Run `./requirements.sh` to install the host dependencies (note that this currently is only supported on Ubuntu, but it's easy to install the dependencies manually by referencing the script). +- Build the project using `cargo build`. The containers will mount the `target/debug/` dir and run the executables from there. +- Run `./run.sh` to run the testing environment (note that this could take up to half a minute if the configs are being generated). + +## Usage + +You can check the `./data` directory for port numbers and logs of all the containers. +You can also edit the `docker-compose.yml` file to adjust the number of nodes and authorities. + +## Shutdown + +If you want to stop the testing environment, follow these steps: + +- Run `./stop.sh` to stop the containers (note that this could take up to a minute). +- If you want to clear configs (so new configs could be generated on the next run), you can run `sudo ./clean.sh`. +- The HTTP endpoints of the authority containers can be found in the `data/blockchain-config.json` file. + +## TODO + +The following features will/should/might be implemented: + +- Multiplatform `./requirements.sh` support. +- Faster `./stop.sh` operation. +- Configurable candidates. diff --git a/testing/requirements.sh b/testing/requirements.sh new file mode 100644 index 0000000..242413e --- /dev/null +++ b/testing/requirements.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Use this file to install the necessary dependencies for running the test environment. + +sudo apt-get update +sudo apt-get install -y docker-compose diff --git a/testing/run.sh b/testing/run.sh new file mode 100755 index 0000000..f34221a --- /dev/null +++ b/testing/run.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Use this file to startup the testing environment. + +DATA_DIR="data/" + +# If docker-compose creates the data dir, it's owner will be root and the services won't +# be able to write anything to it. +if [ ! -d "$DATA_DIR" ]; then + echo "Error: Directory '$DATA_DIR' must exist and be owned by the current user." + exit 1 +fi + +dir_owner=$(stat -c '%U' $DATA_DIR) +if [ "$dir_owner" != $(whoami) ]; then + echo "Error: directory '$DATA_DIR' exists, but must be owned by the current user" + exit 1 +fi + +check_health() { + for container in $(docker-compose ps -q authority); do + health_status=$(docker inspect --format '{{.State.Health.Status}}' $container) + if [ "$health_status" != "healthy" ]; then + return 1 + fi + done + return 0 +} + +docker-compose up -d authority + +# We need to wait for containers to be healthy, because `docker-compose up` exits when the +# containers have started, but the internal services might not have started yet, which means +# that the `authority-config.json` files might not have been created yet. +echo "Waiting for all authority containers to be healthy..." +until check_health; do + echo "..." + sleep 2 +done + +./aggregate-blockchain-config.sh + +docker-compose up -d node diff --git a/testing/startup.sh b/testing/startup.sh new file mode 100755 index 0000000..bac0c8d --- /dev/null +++ b/testing/startup.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# This is the script which is used to initialize the containers. + +# Docker does not expose the service name as an environment variable. +# So we have to retrieve it through the docker.socket API. +export NAME=\ +$(curl -X GET --unix-socket /var/run/docker.sock -s "http://v1.43/containers/$HOSTNAME/json" \ +| jq -r '.Name' \ +| sed "s/['\"/\\/]//g") # Sanitize output from extra symbols like '/' or '\"' + +# Same for the host port to which the container's `APP_PORT` port is bound to. +export PORT=\ +$(curl -X GET --unix-socket /var/run/docker.sock -s "http://v1.43/containers/$HOSTNAME/json" \ +| jq -r --arg app_port "$APP_PORT" '.NetworkSettings.Ports[$app_port + "/tcp"][0].HostPort' \ +| sed "s/['\"/\\/]//g") + +mkdir -p /data/$NAME +cd /data/$NAME +echo "$PORT" > port +if [ "$APP" == "node" ]; then + cp ../blockchain-config.json . +fi + +/exec/debug/${APP} --data-path . diff --git a/testing/stop.sh b/testing/stop.sh new file mode 100755 index 0000000..be3103e --- /dev/null +++ b/testing/stop.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# This stops all the runnning containers. +# Keep in mind, this may take up to a minute. + +docker-compose down