diff options
| author | Andreas Grois <andi@grois.info> | 2022-10-10 21:30:02 +0200 |
|---|---|---|
| committer | Andreas Grois <andi@grois.info> | 2022-10-10 21:37:15 +0200 |
| commit | e4ad766315879e1ff05bb111229f073f8f0ed68e (patch) | |
| tree | 4b043ff47c78b2c00c80c94ebda622c32c8b6d3d /rust | |
PassFish: Initial Commit
Well, that's a lie. But nobody needs to see all the iterations I decided
to sweep under the rug.
That said, I think the repo is, while not clean, clean enough now, to
not be embarrassed by uploading it to github.
Diffstat (limited to 'rust')
| -rw-r--r-- | rust/Cargo.lock | 452 | ||||
| -rw-r--r-- | rust/Cargo.toml | 23 | ||||
| -rw-r--r-- | rust/clippy.toml | 1 | ||||
| -rw-r--r-- | rust/src/implementation/mod.rs | 70 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/emittingsender.rs | 17 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/helperthread/hashers/mod.rs | 31 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/helperthread/hashers/qt_hash.rs | 131 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/helperthread/message_parsing.rs | 102 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/helperthread/mod.rs | 49 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/helperthread/profile_to_domain.rs | 46 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/mod.rs | 305 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/settings.rs | 159 | ||||
| -rw-r--r-- | rust/src/implementation/passwordmaker/thread_messages.rs | 41 | ||||
| -rw-r--r-- | rust/src/implementation/profiles.rs | 1127 | ||||
| -rw-r--r-- | rust/src/implementation/pwm_macros.rs | 27 | ||||
| -rw-r--r-- | rust/src/interface.rs | 1062 | ||||
| -rw-r--r-- | rust/src/lib.rs | 12 |
17 files changed, 3655 insertions, 0 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..8d8ed52 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,452 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fragile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mockall" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4d70639a72f972725db16350db56da68266ca368b2a1fe26724a903ad3d6b8" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79ef208208a0dea3f72221e26e904cdc6db2e481d9ade89081ddd494f1dbaa6b" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mockall_double" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dffc15b97456ecc84d2bde8c1df79145e154f45225828c4361f676e1b82acd6" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "passfish" +version = "1.0.0" +dependencies = [ + "dirs", + "libc", + "mockall", + "mockall_double", + "passwordmaker-rs", + "passwordmaker_macros", + "ripemd", + "rust_testhelper", + "serde", + "toml", + "unicode-segmentation", +] + +[[package]] +name = "passwordmaker-rs" +version = "0.1.0" +source = "git+https://github.com/soulsource/passwordmaker-rs.git#fd68c7ad50b78f84443e826fbe29bce24c417dd7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "passwordmaker_macros" +version = "1.0.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rust_testhelper" +version = "0.1.0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..85c38d7 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "passfish" +version = "1.0.0" +edition = "2018" + +[dependencies] +libc = "0.2" +serde = { version = "1.0", features = ["derive", "rc"] } +toml = "0.5" +dirs = "4.0" +unicode-segmentation = "1.10.0" +mockall_double = "0.2" +ripemd = {version = "0.1.3", features = ["std"] } +passwordmaker_macros = { path = "../rust_macro" } +passwordmaker-rs = { git = "https://github.com/soulsource/passwordmaker-rs.git" } + +[dev-dependencies] +mockall = "0.11" +rust_testhelper = { path = "../rust_testhelper" } + +[lib] +name = "passfish" +crate-type = ["cdylib"] diff --git a/rust/clippy.toml b/rust/clippy.toml new file mode 100644 index 0000000..935336a --- /dev/null +++ b/rust/clippy.toml @@ -0,0 +1 @@ +msrv = "1.52.0" diff --git a/rust/src/implementation/mod.rs b/rust/src/implementation/mod.rs new file mode 100644 index 0000000..889e743 --- /dev/null +++ b/rust/src/implementation/mod.rs @@ -0,0 +1,70 @@ +mod profiles; +mod passwordmaker; +mod pwm_macros; + +pub use self::profiles::Profiles; +pub use self::passwordmaker::PasswordMaker; +pub use self::passwordmaker::Settings; + +//------------------------------------------------------------------------------ +// helper types that are used in multiple modules. + +fn get_config_folder() -> Option<std::path::PathBuf> { + dirs::config_dir() + .map(|p| p.join("info.grois/harbour-passfish/")) +} + +#[derive(Debug)] +enum LoadError { + Xdg, + Loading(std::io::Error), + Parsing(toml::de::Error), +} +impl std::fmt::Display for LoadError { + fn fmt(&self, f : &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + LoadError::Xdg => { write!(f, "XDG config path not found") } + LoadError::Loading(e) => { e.fmt(f) } + LoadError::Parsing(e) => { e.fmt(f) } + } + } +} +impl std::error::Error for LoadError {} +impl From<std::io::Error> for LoadError { + fn from(e : std::io::Error) -> Self { + LoadError::Loading(e) + } +} +impl From<toml::de::Error> for LoadError { + fn from(e : toml::de::Error) -> Self { + LoadError::Parsing(e) + } +} + + +#[derive(Debug)] +enum StoreError { + Xdg, + Writing(std::io::Error), + Serialization(toml::ser::Error) +} +impl std::fmt::Display for StoreError { + fn fmt(&self, f : &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + StoreError::Xdg => { write!(f, "XDC config path not found") } + StoreError::Writing(e) => { e.fmt(f) } + StoreError::Serialization(e) => { e.fmt(f) } + } + } +} +impl std::error::Error for StoreError {} +impl From<std::io::Error> for StoreError { + fn from(e: std::io::Error) -> Self { + StoreError::Writing(e) + } +} +impl From<toml::ser::Error> for StoreError { + fn from(e: toml::ser::Error) -> Self { + StoreError::Serialization(e) + } +} diff --git a/rust/src/implementation/passwordmaker/emittingsender.rs b/rust/src/implementation/passwordmaker/emittingsender.rs new file mode 100644 index 0000000..1cf58fc --- /dev/null +++ b/rust/src/implementation/passwordmaker/emittingsender.rs @@ -0,0 +1,17 @@ +use std::sync::mpsc::{channel,Sender,Receiver,SendError}; +#[derive(Clone)] +pub(crate) struct EmittingSender<MT, E : Fn()> { + sender : Sender<MT>, + emit : E, +} + +impl <MT, E : Fn()> EmittingSender<MT, E>{ + pub(crate) fn emitting_channel(emit : E) -> (Self, Receiver<MT>) { + let (sender,r) = channel(); + (EmittingSender { sender, emit }, r) + } + pub(crate) fn send(&self, t: MT) -> Result<(), SendError<MT>> { + //first send, then notify. + self.sender.send(t).map(|_| (self.emit)()) + } +} diff --git a/rust/src/implementation/passwordmaker/helperthread/hashers/mod.rs b/rust/src/implementation/passwordmaker/helperthread/hashers/mod.rs new file mode 100644 index 0000000..85fbc39 --- /dev/null +++ b/rust/src/implementation/passwordmaker/helperthread/hashers/mod.rs @@ -0,0 +1,31 @@ +mod qt_hash; +use passwordmaker_rs::{Hasher, HasherList}; +use ripemd::Digest; + + +pub(super) use qt_hash::{QtMd4, QtMd5, QtSha1, QtSha256}; +pub(super) struct Ripemd160; + +impl Hasher for Ripemd160{ + type Output = [u8;20]; + + fn hash(input : &[u8]) -> Self::Output { + let hash = ripemd::Ripemd160::digest(input); + hash.into() + } +} + +pub(super) struct PassFishHashers; +impl HasherList for PassFishHashers { + type MD4 = QtMd4; + type MD5 = QtMd5; + type SHA1 = QtSha1; + type SHA256 = QtSha256; + type RIPEMD160 = Ripemd160; +} + +impl passwordmaker_rs::Md4 for QtMd4 {} +impl passwordmaker_rs::Md5 for QtMd5 {} +impl passwordmaker_rs::Sha1 for QtSha1 {} +impl passwordmaker_rs::Sha256 for QtSha256 {} +impl passwordmaker_rs::Ripemd160 for Ripemd160 {} diff --git a/rust/src/implementation/passwordmaker/helperthread/hashers/qt_hash.rs b/rust/src/implementation/passwordmaker/helperthread/hashers/qt_hash.rs new file mode 100644 index 0000000..64d4e80 --- /dev/null +++ b/rust/src/implementation/passwordmaker/helperthread/hashers/qt_hash.rs @@ -0,0 +1,131 @@ +use std::borrow::{BorrowMut, Borrow}; +use passwordmaker_rs::Hasher; +use libc::size_t; + +pub(crate) struct Md4; +pub(crate) struct Md5; +pub(crate) struct Sha1; +pub(crate) struct Sha256; + +pub(crate) struct QHasher<T>(T); + +pub(crate) type QtMd4 = QHasher<Md4>; +pub(crate) type QtMd5 = QHasher<Md5>; +pub(crate) type QtSha1 = QHasher<Sha1>; +pub(crate) type QtSha256 = QHasher<Sha256>; + +impl<T> Hasher for QHasher<T> where T:QtHasher { + type Output = T::Output; + + fn hash(input : &[u8]) -> Self::Output { + let mut result = T::Output::default(); + let required_bytes = result.borrow().len(); + let computed_bytes = unsafe{ + pwm_qhash( + T::QT_ALGO_NUMBER, + input.as_ptr(), + input.len() as size_t, + result.borrow_mut().as_mut_ptr(), + required_bytes) + }; + assert_eq!(computed_bytes, required_bytes); //no point to forward this to caller. It's a code bug, plain and simple. + result + } +} + +pub(crate) trait QtHasher{ + type Output : Default + BorrowMut<[u8]>; + const QT_ALGO_NUMBER : size_t; +} + +impl QtHasher for Md4 { + type Output = [u8;16]; + const QT_ALGO_NUMBER : size_t = 0; +} + +impl QtHasher for Md5 { + type Output = [u8;16]; + const QT_ALGO_NUMBER : size_t = 1; +} + +impl QtHasher for Sha1 { + type Output = [u8;20]; + const QT_ALGO_NUMBER : size_t = 2; +} + +impl QtHasher for Sha256 { + type Output = [u8;32]; + const QT_ALGO_NUMBER : size_t = 4; +} + +#[cfg(test)] +use rust_testhelper::pwm_qhash; +#[cfg(not(test))] +extern "C"{ + fn pwm_qhash(algorithm : size_t, input : *const u8, input_length : size_t, output : *mut u8, output_capacity : size_t) -> size_t; +} + +/// Those tests are testing the integration of Qt's QCryptographicHash function. They are functional tests, NOT unit tests. +#[cfg(test)] +mod qt_hash_tests{ + use super::*; + fn get_simple_string_as_bytes() -> &'static [u8] { + "I am a simple string and I like simple things. I like dancing in the rain, I like eating hamburgers, and I like you.".as_bytes() + } + fn get_complex_string_as_bytes() -> &'static [u8] { + "I am a complex string and do complex stuff. I like 🕺 in the 🌧️. I like eating 🍔, and I ❤️ you.".as_bytes() + } + #[test] + fn md4_simple_string_test(){ + let hash = QtMd4::hash(get_simple_string_as_bytes()); + let expected = vec![0x14, 0x1f, 0x1f, 0x1d, 0xef, 0x4a, 0x0d, 0x15, 0x1d, 0xb5, 0x5f, 0x7c, 0xb8, 0x96, 0xca, 0x99]; + assert_eq!(hash.borrow(), expected); + } + #[test] + fn md4_complex_string_test(){ + let hash = QtMd4::hash(get_complex_string_as_bytes()); + let expected = vec![0xc1, 0xfb, 0xe3, 0x4d, 0x72, 0x75, 0xb3, 0xa5, 0x3a, 0xc3, 0x45, 0xcf, 0x90, 0x34, 0x81, 0xf7]; + assert_eq!(hash.borrow(), expected); + } + + #[test] + fn md5_simple_string_test(){ + let hash = QtMd5::hash(get_simple_string_as_bytes()); + let expected = vec![0x4b, 0x2e, 0x18, 0x22, 0x45, 0x43, 0xf3, 0x96, 0xee, 0x79, 0x53, 0x18, 0x90, 0x1b, 0xb9, 0x7f]; + assert_eq!(hash.borrow(), expected); + } + #[test] + fn md5_complex_string_test(){ + let hash = QtMd5::hash(get_complex_string_as_bytes()); + let expected = vec![0x70, 0x31, 0x35, 0xd9, 0x38, 0x55, 0x1d, 0x2a, 0xae, 0xfa, 0xd9, 0x38, 0x07, 0x91, 0x11, 0xfe ]; + assert_eq!(hash.borrow(), expected); + } + + #[test] + fn sha1_simple_string_test(){ + let hash = QtSha1::hash(get_simple_string_as_bytes()); + let expected = vec![0xa1, 0x0a, 0x15, 0x18, 0x99, 0x29, 0x9d, 0xc7, 0xa6, 0x48, 0x36, 0x11, 0x44, 0xb3, 0x94, 0x09, 0x87, 0x3a, 0x39, 0xf3]; + assert_eq!(hash.borrow(), expected); + } + #[test] + fn sha1_complex_string_test(){ + let hash = QtSha1::hash(get_complex_string_as_bytes()); + let expected = vec![0x30, 0x79, 0xcd, 0xbc, 0x66, 0x09, 0xad, 0x24, 0x99, 0x44, 0xe5, 0x52, 0x25, 0xdf, 0xb4, 0x68, 0xfd, 0x5f, 0xb9, 0x8f ]; + assert_eq!(hash.borrow(), expected); + } + + #[test] + fn sha256_simple_string_test(){ + let hash = QtSha256::hash(get_simple_string_as_bytes()); + let expected = vec![0xd4, 0xaf, 0x13, 0x6a, 0x87, 0x62, 0x12, 0xf1, 0x93, 0x7d, 0xd1, 0x71, 0xab, 0xa1, 0xfa, 0x3e, 0x3b, 0x8e, 0xc5, 0x68, + 0xed, 0x42, 0x46, 0x9d, 0xf0, 0x9b, 0xd0, 0xd8, 0xd8, 0x39, 0x09, 0x93]; + assert_eq!(hash.borrow(), expected); + } + #[test] + fn sha256_complex_string_test(){ + let hash = QtSha256::hash(get_complex_string_as_bytes()); + let expected = vec![0x01, 0x3d, 0x93, 0x17, 0x45, 0x18, 0x29, 0x41, 0x6a, 0x09, 0xb5, 0x65, 0x1b, 0x81, 0x32, 0x88, 0xce, 0x83, 0xad, 0x92, + 0x04, 0x0f, 0x24, 0x13, 0x57, 0x8d, 0xd1, 0xa5, 0xe8, 0x3a, 0x73, 0xaa ]; + assert_eq!(hash.borrow(), expected); + } +}
\ No newline at end of file diff --git a/rust/src/implementation/passwordmaker/helperthread/message_parsing.rs b/rust/src/implementation/passwordmaker/helperthread/message_parsing.rs new file mode 100644 index 0000000..4cda572 --- /dev/null +++ b/rust/src/implementation/passwordmaker/helperthread/message_parsing.rs @@ -0,0 +1,102 @@ +use std::sync::mpsc::{Receiver, RecvError}; +use super::super::thread_messages::UiToHelper; + +pub(super) fn receive_and_get_newest_or_important_command(receiver : &Receiver<UiToHelper>) -> Result<NewestMostImportantCommand, RecvError> { + let first_message = receive_and_log_error(receiver)?; + Ok(get_newest_or_important_command(first_message, receiver.try_iter())) +} + +pub(super) struct NewestMostImportantCommand(pub UiToHelper); + +trait GetNewerOrMoreImportantCommand { + type Output; + fn get_newer_or_more_important_command(old: Self::Output, new: Self) -> Self::Output; + fn convert_first_command(self) -> Self::Output; +} + +impl GetNewerOrMoreImportantCommand for UiToHelper { + type Output = NewestMostImportantCommand; + + fn convert_first_command(self) -> Self::Output{ + NewestMostImportantCommand(self) + } + fn get_newer_or_more_important_command(old: Self::Output, new: Self) -> Self::Output { + match old.0 { + UiToHelper::Shutdown => old, + UiToHelper::GeneratePassword(_) => NewestMostImportantCommand(new), + } + } +} + + +fn get_newest_or_important_command<I, It >(first_message: I, other_messages: It) -> I::Output + where I: GetNewerOrMoreImportantCommand, + It: Iterator<Item=I> +{ + other_messages.fold(first_message.convert_first_command(), I::get_newer_or_more_important_command) +} + +fn receive_and_log_error(receiver : &Receiver<UiToHelper>) -> Result<UiToHelper, RecvError> { + match receiver.recv() { + Ok(x) => Ok(x), + e => { + eprintln!("Connection to UI Thread closed unexpectedly."); + e + } + } +} + +#[cfg(test)] +mod message_parsing_tests { + use super::*; + + #[derive(PartialEq, Debug)] + enum TestPrioritizedEnum { + Lowest(usize), + Highest(usize), + Medium(usize) + } + impl TestPrioritizedEnum{ + fn get_prio(&self) -> usize{ + match self { + TestPrioritizedEnum::Lowest(_) => 0, + TestPrioritizedEnum::Highest(_) => 2, + TestPrioritizedEnum::Medium(_) => 1, + } + } + } + + struct TestPrioritizedEnumPrioResult(TestPrioritizedEnum); + impl GetNewerOrMoreImportantCommand for TestPrioritizedEnum { + type Output = TestPrioritizedEnumPrioResult; + + fn get_newer_or_more_important_command(old: Self::Output, new: Self) -> Self::Output { + if old.0.get_prio() > new.get_prio() { old } else { TestPrioritizedEnumPrioResult(new) } + } + + fn convert_first_command(self) -> Self::Output { + TestPrioritizedEnumPrioResult(self) + } + } + + #[test] + fn test_get_newest_or_important_command_test(){ + let input1 = vec![TestPrioritizedEnum::Lowest(0), TestPrioritizedEnum::Lowest(1), TestPrioritizedEnum::Lowest(2)]; + let expected1 = TestPrioritizedEnum::Lowest(2); + let input2 = vec![TestPrioritizedEnum::Medium(0), TestPrioritizedEnum::Lowest(1), TestPrioritizedEnum::Lowest(2)]; + let expected2 = TestPrioritizedEnum::Medium(0); + let input3 = vec![TestPrioritizedEnum::Lowest(0), TestPrioritizedEnum::Medium(1), TestPrioritizedEnum::Lowest(2)]; + let expected3 = TestPrioritizedEnum::Medium(1); + let input4 = vec![TestPrioritizedEnum::Medium(0), TestPrioritizedEnum::Lowest(1), TestPrioritizedEnum::Medium(2)]; + let expected4 = TestPrioritizedEnum::Medium(2); + let input5 = vec![TestPrioritizedEnum::Lowest(0), TestPrioritizedEnum::Lowest(1), TestPrioritizedEnum::Highest(2), TestPrioritizedEnum::Medium(3)]; + let expected5 = TestPrioritizedEnum::Highest(2); + let data = vec![(input1, expected1), (input2, expected2), (input3, expected3), (input4, expected4), (input5, expected5)]; + for (input, expected) in data { + let mut it = input.into_iter(); + let first = it.next().unwrap(); + let result = get_newest_or_important_command(first,it); + assert_eq!(result.0, expected); + } + } +}
\ No newline at end of file diff --git a/rust/src/implementation/passwordmaker/helperthread/mod.rs b/rust/src/implementation/passwordmaker/helperthread/mod.rs new file mode 100644 index 0000000..7e3b587 --- /dev/null +++ b/rust/src/implementation/passwordmaker/helperthread/mod.rs @@ -0,0 +1,49 @@ +mod message_parsing; +mod hashers; +mod profile_to_domain; +use std::sync::mpsc::{Receiver, SendError}; +use passwordmaker_rs::PasswordMaker; +use profile_to_domain::{convert_hash_algorithm, convert_leet}; +use super::emittingsender::EmittingSender; +use super::thread_messages::{HelperToUi, UiToHelper}; +use message_parsing::receive_and_get_newest_or_important_command; +use hashers::PassFishHashers; + +pub(super) fn run<T: Fn()>(to_ui : &EmittingSender<HelperToUi, T>, from_ui : &Receiver<UiToHelper>) -> Result<(), SendError<HelperToUi>>{ + println!("Helper Thread starting up."); + + while let Ok(m) = receive_and_get_newest_or_important_command(from_ui) + { + //m is a product type, because we gather the latest command of each type. Easiest to deal with those would be using a group of ifs. + //for now the number of alternatives is rather small though, so let's match instead. + match m.0 { + UiToHelper::Shutdown => break, + UiToHelper::GeneratePassword(task) => { + + to_ui.send(HelperToUi::GenerationStarted)?; + let hash_algorithm = convert_hash_algorithm(&task.generation_settings.hash_algorithm); + let use_leet = convert_leet(&task.generation_settings.leet); + let characters = &task.generation_settings.characters; + let username = &task.generation_settings.username; + let modifier = &task.generation_settings.modifier; + let password_length = task.generation_settings.password_length as usize; + let suffix = &task.generation_settings.suffix; + let prefix = &task.generation_settings.prefix; + type Pwm<'a> = PasswordMaker<'a, PassFishHashers>; + let pwm = Pwm::new(hash_algorithm, use_leet, characters, username, modifier, password_length, prefix, suffix); + let input = task.input; + let master_password = task.master_password; + let pwm = pwm.map_err(Into::into); + let password = + pwm.and_then(|pwm| pwm.generate(input, master_password).map_err(Into::into)); + + match password { + Ok(password) => to_ui.send(HelperToUi::Generated{password})?, + Err(error) => to_ui.send(HelperToUi::GenerationFailed{ error })?, + } + } + } + } + println!("Helper Thread shutting down."); + Ok(()) +} diff --git a/rust/src/implementation/passwordmaker/helperthread/profile_to_domain.rs b/rust/src/implementation/passwordmaker/helperthread/profile_to_domain.rs new file mode 100644 index 0000000..152ba8f --- /dev/null +++ b/rust/src/implementation/passwordmaker/helperthread/profile_to_domain.rs @@ -0,0 +1,46 @@ +/// This whole module may look dumb, but that might change, once the public interface of passwordmaker_rs starts to deviate from the saved data. + +use passwordmaker_rs::{HashAlgorithm,UseLeetWhenGenerating,LeetLevel}; + +pub(super) fn convert_hash_algorithm(stored_algo : &crate::implementation::profiles::HashAlgorithm) -> HashAlgorithm { + use crate::implementation::profiles::HashAlgorithm as PHashAlgorithm; + match stored_algo { + PHashAlgorithm::Md4 => HashAlgorithm::Md4, + PHashAlgorithm::HmacMd4 => HashAlgorithm::HmacMd4, + PHashAlgorithm::Md5 => HashAlgorithm::Md5, + PHashAlgorithm::Md5Version06 => HashAlgorithm::Md5Version06, + PHashAlgorithm::HmacMd5 => HashAlgorithm::HmacMd5, + PHashAlgorithm::HmacMd5Version06 => HashAlgorithm::HmacMd5Version06, + PHashAlgorithm::Sha1 => HashAlgorithm::Sha1, + PHashAlgorithm::HmacSha1 => HashAlgorithm::HmacSha1, + PHashAlgorithm::Sha256 => HashAlgorithm::Sha256, + PHashAlgorithm::HmacSha256 => HashAlgorithm::HmacSha256, + PHashAlgorithm::Ripemd160 => HashAlgorithm::Ripemd160, + PHashAlgorithm::HmacRipemd160 => HashAlgorithm::HmacRipemd160, + } +} + +pub(super) fn convert_leet(stored_leet : &crate::implementation::profiles::UseLeetWhenGenerating) -> UseLeetWhenGenerating { + use crate::implementation::profiles::UseLeetWhenGenerating as PUseLeetWhenGenerating; + match stored_leet { + PUseLeetWhenGenerating::NotAtAll => UseLeetWhenGenerating::NotAtAll, + PUseLeetWhenGenerating::Before { level } => UseLeetWhenGenerating::Before { level: convert_leet_level(level) }, + PUseLeetWhenGenerating::After { level } => UseLeetWhenGenerating::After { level: convert_leet_level(level) }, + PUseLeetWhenGenerating::BeforeAndAfter { level } => UseLeetWhenGenerating::BeforeAndAfter { level: convert_leet_level(level) }, + } +} + +fn convert_leet_level(stored_level : &crate::implementation::profiles::LeetLevel) -> LeetLevel { + use crate::implementation::profiles::LeetLevel as PLeetLevel; + match stored_level { + PLeetLevel::One => LeetLevel::One, + PLeetLevel::Two => LeetLevel::Two, + PLeetLevel::Three => LeetLevel::Three, + PLeetLevel::Four => LeetLevel::Four, + PLeetLevel::Five => LeetLevel::Five, + PLeetLevel::Six => LeetLevel::Six, + PLeetLevel::Seven => LeetLevel::Seven, + PLeetLevel::Eight => LeetLevel::Eight, + PLeetLevel::Nine => LeetLevel::Nine, + } +}
\ No newline at end of file diff --git a/rust/src/implementation/passwordmaker/mod.rs b/rust/src/implementation/passwordmaker/mod.rs new file mode 100644 index 0000000..af0516c --- /dev/null +++ b/rust/src/implementation/passwordmaker/mod.rs @@ -0,0 +1,305 @@ +mod emittingsender; +mod thread_messages; +mod settings; +mod helperthread; + +pub use self::settings::Settings; +use passwordmaker_rs::{UrlParsing, ProtocolUsageMode, SettingsError, GenerationError}; +use crate::interface::ProfilesTrait; + +use std::cell::RefCell; +use std::convert::TryFrom; +use std::error::Error; +use std::fmt::Display; +use std::sync::mpsc::{Receiver, Sender, channel}; +use crate::implementation::pwm_macros::EnumVariantCount; + +use mockall_double::double; + +#[double] +use crate::interface::PasswordMakerEmitter; +use crate::interface::PasswordMakerTrait; +use super::Profiles; +use self::emittingsender::EmittingSender; +use self::thread_messages::{GeneratePasswordTask, HelperToUi, UiToHelper, GenerationIssue}; + +#[derive(EnumVariantCount, Clone, Copy)] +enum GeneratorState{ + MissingTextToUse, + MissingMasterPassword, + CharsetError, + GenerationCompleted, + Busy +} + +pub struct PasswordMaker { + emit : PasswordMakerEmitter, + profiles : Profiles, + settings : Settings, + url : RefCell<String>, + used_text : RefCell<String>, + master_password : RefCell<String>, + generated_password : RefCell<String>, + generator_state : RefCell<GeneratorState>, + from_helper : Receiver<HelperToUi>, + to_helper : Sender<UiToHelper>, + helper_thread : Option<std::thread::JoinHandle<()>> +} + +#[derive(Debug)] +struct GeneratorStateFromIntError; +impl Display for GeneratorStateFromIntError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f,"Invalid value for GeneratorStateFromIntError.") + } +} +impl Error for GeneratorStateFromIntError{} + +impl TryFrom<u8> for GeneratorState { + type Error = GeneratorStateFromIntError; + fn try_from(value: u8) -> Result<Self, Self::Error> { + match value { + 0 => Ok(GeneratorState::GenerationCompleted), + 1 => Ok(GeneratorState::Busy), + 2 => Ok(GeneratorState::MissingTextToUse), + 3 => Ok(GeneratorState::MissingMasterPassword), + 4 => Ok(GeneratorState::CharsetError), + 5..=255 => Err(GeneratorStateFromIntError) + } + } +} +impl From<GeneratorState> for u8 { + fn from(s : GeneratorState) -> Self { + match s { + GeneratorState::MissingTextToUse => 2, + GeneratorState::GenerationCompleted => 0, + GeneratorState::Busy => 1, + GeneratorState::MissingMasterPassword => 3, + GeneratorState::CharsetError => 4, + } + } +} + +impl PasswordMakerTrait for PasswordMaker { + fn new(emit : PasswordMakerEmitter, profiles : Profiles, settings : Settings) -> Self { + let stored_settings_data = settings.load(); + if let Ok(x) = &stored_settings_data { + #[allow(clippy::cast_possible_truncation)] + profiles.set_current_profile(x.current_profile_index as u32); + } + let settings = if let Ok(x) = stored_settings_data { x.settings } else { settings }; + + let emit_clone = emit.clone(); + let (to_ui,from_helper) = EmittingSender::emitting_channel( + move || {emit_clone.i_say_sexy_things_to_myself_while_im_dancing_changed();} + ); + let (to_helper, from_ui) = channel(); + let helper_thread = Some(std::thread::spawn(move || helperthread::run(&to_ui, &from_ui).expect("UI Thread hung up.") )); + PasswordMaker{emit, profiles, settings, from_helper, to_helper, helper_thread, + url: RefCell::new(String::new()), used_text: RefCell::new(String::new()), master_password: RefCell::new(String::new()), generated_password: RefCell::new(String::new()), generator_state: RefCell::new(GeneratorState::MissingTextToUse) } + } + fn emit(&self) -> &PasswordMakerEmitter { + &self.emit + } + fn profiles(&self) -> &Profiles { + &self.profiles + } + fn settings(&self) -> &Settings { + &self.settings + } + fn store_settings(&self) -> bool { + self.settings.store(self.profiles.current_profile() as usize).is_ok() + } + fn i_say_sexy_things_to_myself_while_im_dancing(&self) -> bool { + unreachable!() + } + fn set_i_say_sexy_things_to_myself_while_im_dancing(&self, _ : bool) { + if let Ok(m) = self.from_helper.try_recv() { + self.handle_message_from_helper(m); + } else { + println!("Spurious wakeup from helper thread. No message queued. Please investigate."); + } + } + + fn generated_password<F>(&self, setter: F) where F: FnOnce(&str) { + setter(&*self.generated_password.borrow()); + } + + fn master_password<F>(&self, setter: F) where F: FnOnce(&str) { + setter(&*self.master_password.borrow()); + } + + fn set_master_password(&self, value: String) { + let different = { + let mut b = self.master_password.borrow_mut(); + let different = *b != value; + if different {*b = value;} + different + }; + if different { + self.update_generated_password(); + self.emit().master_password_changed(); + } + } + + fn url<F>(&self, setter: F) where F: FnOnce(&str) { + setter(&*self.url.borrow()); + } + + fn set_url(&self, value: String) { + let different = { + let mut b = self.url.borrow_mut(); + let different = *b != value; + if different { *b = value; } + different + }; + if different { + self.update_used_text_from_url(); + self.emit().url_changed(); + } + } + + fn used_text<F>(&self, setter: F) where F: FnOnce(&str) { + setter(&*self.used_text.borrow()); + } + + fn set_used_text(&self, value: String) { + let different = { + let mut b = self.used_text.borrow_mut(); + let different = *b != value; + if different { *b = value; } + different + }; + if different { + self.update_generated_password(); + self.emit().used_text_changed(); + } + } + + fn generator_state(&self) -> u8 { + (*self.generator_state.borrow()).into() + } + + fn profile_changed(&self) { + self.update_used_text_from_url(); + } +} + +impl Drop for PasswordMaker +{ + fn drop(&mut self) { + if self.to_helper.send(UiToHelper::Shutdown).is_err() { + eprintln!("Failed to tell worker thread to quit. Might need to kill the process."); + } + if let Some(j) = self.helper_thread.take() { + if j.join().is_err() { + eprintln!("Helper Thread crashed. This should not happen, so please report a bug."); + } + } else { + eprintln!("Somehow the information about the helper thread got lost, so we can't wait for it to quit. Might need to kill the process."); + } + } +} + +impl PasswordMaker{ + fn handle_message_from_helper(&self, message : HelperToUi){ + //unless everything is terribly wrong, we are in the UI thread here. + match message { + HelperToUi::Generated{ password } => { + //println!("Password generated."); + { + *self.generator_state.borrow_mut() = GeneratorState::GenerationCompleted; + *self.generated_password.borrow_mut() = password; + } + self.emit().generator_state_changed(); + self.emit().generated_password_changed(); + } + HelperToUi::GenerationFailed { error } => { + //println!("No password generated due to missing input."); + { + *self.generator_state.borrow_mut() = match error { + GenerationIssue::Settings(SettingsError::InsufficientCharset) => GeneratorState::CharsetError, + GenerationIssue::Input(GenerationError::MissingMasterPassword) => GeneratorState::MissingMasterPassword, + GenerationIssue::Input(GenerationError::MissingTextToUse) => GeneratorState::MissingTextToUse, + }; + self.generated_password.borrow_mut().clear(); + } + self.emit().generator_state_changed(); + self.emit().generated_password_changed(); + }, + HelperToUi::GenerationStarted => { + //println!("Setting password generator state as busy."); + { + *self.generator_state.borrow_mut() = GeneratorState::Busy; + self.generated_password.borrow_mut().clear(); + } + self.emit().generator_state_changed(); + self.emit().generated_password_changed(); + }, + } + } + fn update_used_text_from_url(&self) { + let used_text = { + self.profiles.do_with_current_url_parsing_settings( + |settings| { + //have to convert saved data to corresponding runtime data. + let use_protocol = match (settings.use_protocol, settings.use_undefined_as_protocol_fallback) { + (true, false) => ProtocolUsageMode::Used, + (true, true) => ProtocolUsageMode::UsedWithUndefinedIfEmpty, + (false, _) => ProtocolUsageMode::Ignored, + }; + let url_parsing = UrlParsing::new( + use_protocol, + settings.use_userinfo, + settings.use_subdomains, + settings.use_domain, + settings.use_port_path); + url_parsing.parse(&self.url.borrow()) + } + ) + }; + { *self.used_text.borrow_mut() = used_text.unwrap_or_else(|e| e.to_string()); } + self.emit().used_text_changed(); + self.update_generated_password(); //intentionally unconditional. + } + fn update_generated_password(&self) { + let generation_settings = self.profiles.get_copy_current_generation_settings(); + match generation_settings { + Ok(generation_settings) => { + self.to_helper.send( + UiToHelper::GeneratePassword( + GeneratePasswordTask{ + input: self.used_text.borrow().clone(), + master_password: self.master_password.borrow().clone(), + generation_settings } + ) + ).expect("Helper thread no longer listening. Unrecoverable."); + } + Err(error) => { + {*self.generated_password.borrow_mut() = error.to_string();} + {*self.generator_state.borrow_mut() = GeneratorState::GenerationCompleted;} + self.emit().generator_state_changed(); + self.emit().generated_password_changed(); + } + } + } +} + +#[cfg(test)] +mod passwordmaker_tests { + use std::convert::TryInto; + + use super::*; + #[test] + fn generator_state_reciprocity() { + for i in 0..(GeneratorState::variant_count() as u8) { + let g : GeneratorState = i.try_into().unwrap(); + let j : u8 = g.into(); + assert_eq!(j,i); + } + for i in (GeneratorState::variant_count() as u8)..=255 { + let g : Result<GeneratorState,_> = i.try_into(); + assert!(g.is_err()); + } + } +} diff --git a/rust/src/implementation/passwordmaker/settings.rs b/rust/src/implementation/passwordmaker/settings.rs new file mode 100644 index 0000000..487ffea --- /dev/null +++ b/rust/src/implementation/passwordmaker/settings.rs @@ -0,0 +1,159 @@ +use crate::interface::SettingsTrait; +use crate::implementation::{LoadError, StoreError}; + +use serde::{Serialize, Deserialize}; +use std::default::Default; +use std::cell::RefCell; + +use mockall_double::double; + +#[double] +use crate::interface::SettingsEmitter; + +#[derive(Serialize, Deserialize)] +struct SettingsSaveData { + current_profile_index : usize, + settings : SettingsData +} + +#[derive(Serialize, Deserialize, Clone)] +struct SettingsData{ + clear_generated_password_seconds : Option<u32>, + clear_master_password_seconds : Option<u32>, + hide_generated_password : bool +} + +impl Default for SettingsData { + fn default() -> Self { + SettingsData { + clear_generated_password_seconds : Some(60), + clear_master_password_seconds : Some(300), + hide_generated_password : false + } + } +} + +pub struct Settings { + emit : SettingsEmitter, + data : RefCell<SettingsData> +} + +pub struct SettingLoadResult { + pub settings : Settings, + pub current_profile_index : usize +} + +impl Settings { + /// Loads the data from disk and merges them with the current Settings object. Only use during startup, as it does NOT emit. + pub(super) fn load(&self) -> Result<SettingLoadResult, LoadError>{ + super::super::get_config_folder() + .map(|p| p.join("settings")) + .ok_or(LoadError::Xdg) + .and_then(|p| std::fs::read_to_string(p).map_err(Into::into)) + .and_then(|s| toml::from_str(&s).map_err(Into::into)) + .map(|sd| self.merge_with_loaded_data_no_emit(sd)) + } + pub(super) fn store(&self, current_profile_index : usize) -> Result<(), StoreError>{ + toml::to_string(&SettingsSaveData{current_profile_index, settings : self.data.borrow().clone()}) + .map_err(Into::into) + .and_then(|s| Self::write_serialized_settings_data(&s)) + } + + ///Private. Should only be used during load(). + fn merge_with_loaded_data_no_emit(&self, save_data: SettingsSaveData) -> SettingLoadResult { + SettingLoadResult { + settings : Settings { emit : self.emit.clone(), data : RefCell::new(save_data.settings) }, + current_profile_index : save_data.current_profile_index + } + } + fn write_serialized_settings_data(data : &str) -> Result<(), StoreError> { + super::super::get_config_folder() + .ok_or(StoreError::Xdg) + .and_then(|p| std::fs::create_dir_all(p.clone()).map_err(Into::into).map(|()| p)) + .map(|p| p.join("settings")) + .and_then(|f| std::fs::write(f, data).map_err(Into::into)) + } +} + +impl SettingsTrait for Settings { + fn new(emit: SettingsEmitter) -> Self { + Settings {emit, data : RefCell::new(SettingsData::default())} + } + fn emit(&self) -> &SettingsEmitter{ + &self.emit + } + fn clear_generated_password_seconds(&self) -> Option<u32> { + self.data.borrow().clear_generated_password_seconds + } + fn set_clear_generated_password_seconds(&self, value: Option<u32>) { + let changed = self.data.borrow().clear_generated_password_seconds != value; + self.data.borrow_mut().clear_generated_password_seconds = value; + if changed { + self.emit().clear_generated_password_seconds_changed(); + } + } + fn clear_master_password_seconds(&self) -> Option<u32> { + self.data.borrow().clear_master_password_seconds + } + fn set_clear_master_password_seconds(&self, value: Option<u32>) { + let changed = self.data.borrow().clear_master_password_seconds != value; + self.data.borrow_mut().clear_master_password_seconds = value; + if changed { + self.emit().clear_master_password_seconds_changed(); + } + } + + fn hide_generated_password(&self) -> bool { + self.data.borrow().hide_generated_password + } + + fn set_hide_generated_password(&self, value: bool) { + let changed = { + let mut v = self.data.borrow_mut(); + let changed = v.hide_generated_password != value; + v.hide_generated_password = value; + changed + }; + if changed { + self.emit().hide_generated_password_changed(); + } + } +} + +#[cfg(test)] +mod settings_test{ + use super::*; + + #[test] + fn merge_with_loaded_data_test(){ + let mut emit = SettingsEmitter::new(); + emit.expect_clone().return_once(||{ + let mut e2 = SettingsEmitter::new(); + e2.expect_clone().never(); + e2.expect_clear_generated_password_seconds_changed().never(); + e2.expect_clear_master_password_seconds_changed().never(); + e2.expect_hide_generated_password_changed().never(); + e2 + }).once(); + emit.expect_clear_generated_password_seconds_changed().never(); + emit.expect_clear_master_password_seconds_changed().never(); + emit.expect_hide_generated_password_changed().never(); + let old_settings = Settings { + emit, + data: RefCell::new(SettingsData::default()), + }; + let save_data = SettingsSaveData{ + current_profile_index: 2, + settings: SettingsData { + clear_generated_password_seconds: Some(600), + clear_master_password_seconds: None, + hide_generated_password: true + }, + }; + let result = old_settings.merge_with_loaded_data_no_emit(save_data); + assert_eq!(result.current_profile_index, 2); + assert_eq!(result.settings.data.borrow().clear_generated_password_seconds, Some(600)); + assert_eq!(result.settings.data.borrow().clear_master_password_seconds, None); + assert_eq!(result.settings.data.borrow().hide_generated_password, true); + } +}
\ No newline at end of file diff --git a/rust/src/implementation/passwordmaker/thread_messages.rs b/rust/src/implementation/passwordmaker/thread_messages.rs new file mode 100644 index 0000000..bba3dfa --- /dev/null +++ b/rust/src/implementation/passwordmaker/thread_messages.rs @@ -0,0 +1,41 @@ +use std::sync::Arc; +use passwordmaker_rs::{GenerationError,SettingsError}; +use crate::implementation::profiles::GenerationSettings; + +pub(super) enum HelperToUi { + Generated { + password : String, + }, + GenerationFailed { + error : GenerationIssue + }, + GenerationStarted +} + +pub(super) struct GeneratePasswordTask{ + pub(crate) input : String, + pub(crate) master_password : String, + pub(crate) generation_settings : Arc<GenerationSettings> +} + +pub(super) enum UiToHelper { + Shutdown, + GeneratePassword(GeneratePasswordTask), +} + +pub(super) enum GenerationIssue { + Settings(SettingsError), + Input(GenerationError), +} + +impl From<SettingsError> for GenerationIssue { + fn from(s: SettingsError) -> Self { + GenerationIssue::Settings(s) + } +} + +impl From<GenerationError> for GenerationIssue { + fn from(g: GenerationError) -> Self { + GenerationIssue::Input(g) + } +}
\ No newline at end of file diff --git a/rust/src/implementation/profiles.rs b/rust/src/implementation/profiles.rs new file mode 100644 index 0000000..5d9bbfc --- /dev/null +++ b/rust/src/implementation/profiles.rs @@ -0,0 +1,1127 @@ +use std::convert::{From, Into, TryFrom, TryInto}; +use std::cell::RefCell; +use std::fmt::Display; +use std::sync::Arc; +use serde::{Serialize, Deserialize}; +use mockall_double::double; +#[cfg(test)] +use crate::implementation::pwm_macros::*; + +#[double] +use crate::interface::{ProfilesEmitter, ProfilesList}; +use crate::interface::ProfilesTrait; + +use crate::implementation::{LoadError, StoreError}; + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)] +#[cfg_attr(test, derive(EnumVariantCount))] +pub(crate) enum HashAlgorithm { + Md4, + HmacMd4, + Md5, + Md5Version06, + HmacMd5, + HmacMd5Version06, + Sha1, + HmacSha1, + Sha256, + HmacSha256, + Ripemd160, + HmacRipemd160, +} + +impl Display for HashAlgorithm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HashAlgorithm::Md4 => write!(f,"MD4"), + HashAlgorithm::HmacMd4 => write!(f, "MD4-HMAC"), + HashAlgorithm::Md5 => write!(f, "MD5"), + HashAlgorithm::Md5Version06 => write!(f, "MD5 PWM Version 0.6"), + HashAlgorithm::HmacMd5 => write!(f, "MD5-HMAC"), + HashAlgorithm::HmacMd5Version06 => write!(f, "MD5-HMAC PWM Version 0.6"), + HashAlgorithm::Sha1 => write!(f,"SHA-1"), + HashAlgorithm::HmacSha1 => write!(f, "SHA-1-HMAC"), + HashAlgorithm::Sha256 => write!(f, "SHA-256"), + HashAlgorithm::HmacSha256 => write!(f, "SHA-256-HMAC"), + HashAlgorithm::Ripemd160 => write!(f, "RIPEMD-160"), + HashAlgorithm::HmacRipemd160 => write!(f, "RIPEMD-160-HMAC"), + } + } +} + +impl From<HashAlgorithm> for u8 { + fn from(h : HashAlgorithm) -> u8 { + h as u8 + } +} +impl TryFrom<u8> for HashAlgorithm { + type Error = (); + fn try_from(i : u8) -> Result<HashAlgorithm,()> { + match i { + 0 => { Ok(HashAlgorithm::Md4) } + 1 => { Ok(HashAlgorithm::HmacMd4) } + 2 => { Ok(HashAlgorithm::Md5) } + 3 => { Ok(HashAlgorithm::Md5Version06) } + 4 => { Ok(HashAlgorithm::HmacMd5) } + 5 => { Ok(HashAlgorithm::HmacMd5Version06) } + 6 => { Ok(HashAlgorithm::Sha1) } + 7 => { Ok(HashAlgorithm::HmacSha1) } + 8 => { Ok(HashAlgorithm::Sha256) } + 9 => { Ok(HashAlgorithm::HmacSha256) } + 10 => {Ok(HashAlgorithm::Ripemd160) } + 11 => {Ok(HashAlgorithm::HmacRipemd160)} + _ => { Err(()) } + } + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(EnumVariantCount))] +pub(crate) enum LeetLevel { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, +} + +impl From<LeetLevel> for u8 { + fn from(l : LeetLevel) -> u8 { + l as u8 + 1 + } +} +impl TryFrom<u8> for LeetLevel { + type Error = (); + fn try_from(i : u8) -> Result<LeetLevel,()> { + match i { + 1 => { Ok(LeetLevel::One) } + 2 => { Ok(LeetLevel::Two) } + 3 => { Ok(LeetLevel::Three) } + 4 => { Ok(LeetLevel::Four) } + 5 => { Ok(LeetLevel::Five) } + 6 => { Ok(LeetLevel::Six) } + 7 => { Ok(LeetLevel::Seven) } + 8 => { Ok(LeetLevel::Eight) } + 9 => { Ok(LeetLevel::Nine) } + _ => { Err(()) } + } + } +} + + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "UseLeet")] +pub(crate) enum UseLeetWhenGenerating { + NotAtAll, + Before { + level : LeetLevel, + }, + After { + level : LeetLevel, + }, + BeforeAndAfter { + level : LeetLevel, + }, +} + +impl UseLeetWhenGenerating { + pub(crate) fn get_leet_level(&self) -> Option<LeetLevel> { + match self { + UseLeetWhenGenerating::NotAtAll => { None } + UseLeetWhenGenerating::Before { level } + | UseLeetWhenGenerating::After { level } + | UseLeetWhenGenerating::BeforeAndAfter { level } => { Some(*level) } + } + } + pub(crate) fn set_leet_level(&mut self, lvl : LeetLevel) -> Result<(),()> { + match self { + UseLeetWhenGenerating::NotAtAll => { Err(()) } + UseLeetWhenGenerating::Before { level } + | UseLeetWhenGenerating::After { level } + | UseLeetWhenGenerating::BeforeAndAfter { level} => { *level = lvl; Ok(()) } + } + } +} + +#[allow(clippy::struct_excessive_bools)] +#[derive(Serialize, Deserialize, Clone)] +pub(super) struct UrlParsingSettings { + pub(super) use_protocol : bool, + pub(super) use_userinfo : bool, + pub(super) use_subdomains : bool, + pub(super) use_domain : bool, + pub(super) use_port_path : bool, + pub(super) use_undefined_as_protocol_fallback : bool +} + +impl std::default::Default for UrlParsingSettings { + fn default() -> Self { + Self { + use_protocol: false, + use_userinfo : false, + use_subdomains: false, + use_domain: true, + use_port_path: false , + use_undefined_as_protocol_fallback: true + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub(crate) struct GenerationSettings { + pub(crate) password_length : u32, + pub(crate) username : String, + pub(crate) modifier : String, + pub(crate) characters : String, + pub(crate) prefix : String, + pub(crate) suffix : String, + pub(crate) hash_algorithm : HashAlgorithm, + pub(crate) leet : UseLeetWhenGenerating, +} + +impl std::default::Default for GenerationSettings { + fn default() -> Self { + Self { + leet : UseLeetWhenGenerating::NotAtAll, + hash_algorithm : HashAlgorithm::Md5, + password_length : 8, + username : String::new(), + modifier : String::new(), + characters : String::from( + r#"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()_-+={}|[]\:";'<>?,./"# + ), + prefix : String::new(), + suffix : String::new(), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Default)] +struct ProfileSettings { + generation_settings : Arc<GenerationSettings>, + url_parsing_settings : UrlParsingSettings, +} + +#[derive(Serialize, Deserialize)] +struct Profile { + name : String, + settings : ProfileSettings, +} + +#[derive(Serialize, Deserialize)] +struct StoredProfiles { + profiles : Vec<Profile>, +} + +impl std::default::Default for StoredProfiles { + fn default() -> Self { + Self { + profiles : vec![Profile { + name : String::from("Default"), + settings : ProfileSettings::default() + }] + } + } +} + +impl StoredProfiles { + fn load() -> Result<Self, LoadError> { + super::get_config_folder() + .map(|p| p.join("profiles")) + .ok_or(LoadError::Xdg) + .and_then(|p| std::fs::read_to_string(p).map_err(Into::into)) + .and_then(|s| toml::from_str(&s).map_err(Into::into)) + } + + fn store(&self) -> Result<(), StoreError> { + toml::to_string(self).map_err(Into::into) + .and_then(|s| Self::write_serialized_profile_data(&s)) + } + fn write_serialized_profile_data(data : &str) -> Result<(), StoreError> { + super::get_config_folder() + .ok_or(StoreError::Xdg) + .and_then(|p| std::fs::create_dir_all(p.clone()).map_err(Into::into).map(|()| p)) + .map(|p| p.join("profiles")) + .and_then(|f| std::fs::write(f, data).map_err(Into::into)) + } +} + +pub struct Profiles { + emit : ProfilesEmitter, + model : ProfilesList, + + data : RefCell<StoredProfiles>, + + current_profile_idx : RefCell<usize>, +} + +#[derive(Clone, Debug)] +pub(crate) enum ProfileAccessError { + CurrentProfileOutOfBounds +} +impl Display for ProfileAccessError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProfileAccessError::CurrentProfileOutOfBounds => write!(f, "error reading current profile, please reselect profile") + } + } +} +impl std::error::Error for ProfileAccessError{} + +impl Profiles { + fn notify_current_profile_changed(&self) { + self.emit().current_profile_changed(); + self.emit().current_profile_name_changed(); + } + + fn model(&self) -> &ProfilesList { + #[cfg(debug_assertions)] + assert!(!self.is_any_borrowed()); + &self.model + } + + pub(super) fn do_with_current_url_parsing_settings<F, R>(&self, operation : F) -> Result<R, ProfileAccessError> + where F : FnOnce(&UrlParsingSettings) -> R + { + self.data.borrow().profiles.get(*self.current_profile_idx.borrow()) + .map(|p| &p.settings.url_parsing_settings).map(operation) + .ok_or(ProfileAccessError::CurrentProfileOutOfBounds) + } + + pub(crate) fn get_copy_current_generation_settings(&self) -> Result<Arc<GenerationSettings>, ProfileAccessError> { + self.data.borrow().profiles.get(*self.current_profile_idx.borrow()) + .map(|p| p.settings.generation_settings.clone()) + .ok_or(ProfileAccessError::CurrentProfileOutOfBounds) + } + + #[cfg(debug_assertions)] + fn is_any_borrowed(&self) -> bool { + self.data.try_borrow_mut().is_err() || + self.current_profile_idx.try_borrow_mut().is_err() + } +} + +impl ProfilesTrait for Profiles { + fn new(emit : ProfilesEmitter, model : ProfilesList) -> Self { + let data = RefCell::new(StoredProfiles::load().unwrap_or_default()); + Profiles { emit, model, data, current_profile_idx : RefCell::new(0) } + } + fn emit(&self) -> &ProfilesEmitter { + #[cfg(debug_assertions)] + assert!(!self.is_any_borrowed()); + &self.emit + } + + fn row_count(&self) -> usize { + self.data.borrow().profiles.len() + } + fn insert_rows(&self, row : usize, count : usize) -> bool { + let max_index = self.row_count(); + let valid_index = row <= self.data.borrow().profiles.len(); + if valid_index { + self.model().begin_insert_rows(row, row + count - 1); + let new_profiles = (0..count) + .map(|c| format!("New Profile {}", max_index + c)) + .map(|name| Profile { name, settings : ProfileSettings::default() }); + self.data.borrow_mut().profiles.splice(row..row,new_profiles); + self.model().end_insert_rows(); + //if the current profile is at or beyond row, we need to update it, so it still points + //to the same profile + if *self.current_profile_idx.borrow() >= row { + *self.current_profile_idx.borrow_mut() += count; + //no need to notify about field changes - those are still the same. Only the + //internal index changed. + self.emit().current_profile_changed(); + } + } + valid_index + } + fn remove_rows(&self, row : usize, count : usize) -> bool { + let valid_index = row + count <= self.row_count(); + let at_least_one_left = self.row_count() > count; + let operation_allowed = valid_index && at_least_one_left; + if operation_allowed { + self.model().begin_remove_rows(row, row + count - 1); + self.data.borrow_mut().profiles.drain(row..(row+count)); + //we have to update the current profile index before we complete the row removal. + //this is to ensure the index is not going invalid. + //here we can have a destructive operation: the current index might have been removed. + //in that case set it to 0. + let current_idx = *self.current_profile_idx.borrow(); + if row + count <= current_idx { + *self.current_profile_idx.borrow_mut() -= count; + //same as in the insert_rows case here: The field values are still the same, only + //the index changed. + self.emit().current_profile_changed(); + } + else if row <= current_idx { + *self.current_profile_idx.borrow_mut() = 0; + self.notify_current_profile_changed(); + } + self.model().end_remove_rows(); + } + operation_allowed + } + + fn characters<F : FnOnce(&str)>(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.characters) + .unwrap_or_default()); + } + fn set_characters(&self, index : usize, val : String) -> bool { + if let Some(chars) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).characters) + .filter(|c| **c != val) { + *chars = val; + true + } else { + false + } + } + fn hash_algorithm(&self, index : usize) -> u8 { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.generation_settings.hash_algorithm.into()) + .unwrap_or_default() + } + fn set_hash_algorithm(&self, index : usize, val : u8) -> bool { + let hash = val.try_into().ok(); + let mut borrow = self.data.borrow_mut(); + let profile = borrow.profiles.get_mut(index); + if let Some((p, h)) = profile.zip(hash) + .map(|(p, h)| (&mut Arc::make_mut(&mut p.settings.generation_settings).hash_algorithm, h)) + .filter(|(p, h)| *p != h) { + *p = h; + true + } else { + false + } + } + fn leet_level(&self, index : usize) -> u8 { + self.data.borrow().profiles.get(index) + .and_then(|p| p.settings.generation_settings.leet.get_leet_level()) + .map(Into::into) + .unwrap_or_default() + } + fn set_leet_level(&self, index : usize, level : u8) -> bool { + let level = level.try_into().ok(); + let mut borrow = self.data.borrow_mut(); + let profile = borrow.profiles.get_mut(index); + if let Some((p, l)) = profile.zip(level) + .map(|(p,l)| (&mut Arc::make_mut(&mut p.settings.generation_settings).leet, l)) + .filter(|(p,l)| p.get_leet_level() != Some(*l)) { + p.set_leet_level(l).is_ok() + } else { + false + } + } + fn modifier<F : FnOnce(&str)>(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.modifier) + .unwrap_or_default()); + } + fn set_modifier(&self, index : usize, val : String) -> bool { + if let Some(m) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).modifier) + .filter(|m| **m != val) { + *m = val; + true + } else { + false + } + } + fn name<F : FnOnce(&str)>(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map_or("profile index invalid",|p| &*p.name)); + } + fn set_name(&self, index : usize, val : String) -> bool { + let changed = { + if let Some(n) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.name) + .filter(|n| **n != val) { + *n = val; + true + } else { + false + } + }; + if changed && index == *self.current_profile_idx.borrow() { + self.emit().current_profile_name_changed(); + } + changed + } + fn password_length(&self, index : usize) -> u32 { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.generation_settings.password_length) + .unwrap_or_default() + } + fn set_password_length(&self, index : usize, len : u32) -> bool { + if let Some(pl) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).password_length) + .filter(|pl| **pl != len) { + *pl = len; + true + } else { + false + } + } + fn prefix<F : FnOnce(&str)>(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.prefix) + .unwrap_or_default()); + } + fn set_prefix(&self, index : usize, pref : String) -> bool { + if let Some(p) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).prefix) + .filter(|p| **p != pref) { + *p = pref; + true + } else { + false + } + } + fn suffix<F : FnOnce(&str)>(&self, index : usize, setter : F) { + setter(self.data.borrow_mut().profiles.get(index) + .map(|p| &*p.settings.generation_settings.suffix) + .unwrap_or_default()); + } + fn set_suffix(&self, index : usize, suf : String) -> bool { + if let Some(s) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).suffix) + .filter(|s| **s != suf) { + *s=suf; + true + } else { + false + } + } + fn use_domain(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map_or(true,|p| p.settings.url_parsing_settings.use_domain) + } + fn set_use_domain(&self, index : usize, val : bool) -> bool { + if let Some(d) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_domain) + .filter(|d| **d != val) { + *d = val; + true + } else { + false + } + } + fn use_leet(&self, index : usize) -> u8 { + self.data.borrow().profiles.get(index) + .map(|p| match p.settings.generation_settings.leet { + UseLeetWhenGenerating::NotAtAll => { 0 } + UseLeetWhenGenerating::Before{..} => { 1 } + UseLeetWhenGenerating::After {..} => { 2 } + UseLeetWhenGenerating::BeforeAndAfter{..} => { 3 } + }) + .unwrap_or_default() + } + fn set_use_leet(&self, index : usize, l : u8) -> bool { + let mut borrow = self.data.borrow_mut(); + let profile = borrow.profiles.get_mut(index); + let level = |profile : Option<&Profile>| { + profile.as_ref().and_then(|p| p.settings.generation_settings.leet.get_leet_level()) + .unwrap_or(LeetLevel::One) + }; + let use_leet = match l { + 0 => { Some(UseLeetWhenGenerating::NotAtAll) } + 1 => { Some(UseLeetWhenGenerating::Before { level : level(profile.as_deref()) }) } + 2 => { Some(UseLeetWhenGenerating::After { level : level(profile.as_deref()) }) } + 3 => { Some(UseLeetWhenGenerating::BeforeAndAfter{ level : level(profile.as_deref()) }) } + _ => { None } + }; + if let Some((p, l)) = profile.zip(use_leet) + .map(|(p,l)| (&mut Arc::make_mut(&mut p.settings.generation_settings).leet, l)) + .filter(|(p,l)| *p != l) { + *p = l; + true + } else { + false + } + } + fn use_port_path(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_port_path) + .unwrap_or_default() + } + fn set_use_port_path(&self, index : usize, val : bool) -> bool { + if let Some(pp) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_port_path) + .filter(|pp| **pp != val) { + *pp = val; + true + } else { + false + } + } + fn use_protocol(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_protocol) + .unwrap_or_default() + } + fn set_use_protocol(&self, index : usize, val : bool) -> bool { + if let Some(pr) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_protocol) + .filter(|pr| **pr != val) { + *pr = val; + true + } else { + false + } + } + fn use_subdomains(&self, index : usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_subdomains) + .unwrap_or_default() + } + fn set_use_subdomains(&self, index : usize, val : bool) -> bool { + if let Some(sd) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_subdomains) + .filter(|sd| **sd != val) { + *sd = val; + true + } else { + false + } + } + fn username<F : FnOnce(&str)>(&self, index : usize, setter : F) { + setter(self.data.borrow().profiles.get(index) + .map(|p| &*p.settings.generation_settings.username) + .unwrap_or_default()); + } + fn set_username(&self, index : usize, name : String) -> bool { + if let Some(u) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut Arc::make_mut(&mut p.settings.generation_settings).username) + .filter(|u| **u != name) { + *u = name; + true + } else { + false + } + } + + #[allow(clippy::cast_possible_truncation)] + fn current_profile(&self) -> u32 { + *self.current_profile_idx.borrow() as u32 + } + fn set_current_profile(&self, value : u32) { + let valid_index = self.row_count() > value as usize; + let does_change = *self.current_profile_idx.borrow() != value as usize; + if valid_index && does_change { + *self.current_profile_idx.borrow_mut() = value as usize; + self.notify_current_profile_changed(); + } + } + fn current_profile_name<F : FnOnce(&str)>(&self, setter : F) { + self.name(*self.current_profile_idx.borrow(), setter); + } + + fn store(&self) -> bool { + self.data.borrow().store() + .map_err(|e| println!("{}", e)) + .is_ok() + } + + fn use_undefined_as_protocol_fallback(&self, index: usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_undefined_as_protocol_fallback) + .unwrap_or_default() + } + + fn set_use_undefined_as_protocol_fallback(&self, index: usize, val: bool) -> bool { + if let Some(sd) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_undefined_as_protocol_fallback) + .filter(|sd| **sd != val) { + *sd = val; + true + } else { + false + } + } + + fn use_user_info(&self, index: usize) -> bool { + self.data.borrow().profiles.get(index) + .map(|p| p.settings.url_parsing_settings.use_userinfo) + .unwrap_or_default() + } + + fn set_use_user_info(&self, index: usize, val: bool) -> bool { + if let Some(sd) = self.data.borrow_mut().profiles.get_mut(index) + .map(|p| &mut p.settings.url_parsing_settings.use_userinfo) + .filter(|sd| **sd != val) { + *sd = val; + true + } else { + false + } + } +} + +#[cfg(test)] +mod profiles_tests { + use super::*; + fn get_test_profiles_three_entries(emit : ProfilesEmitter, model : ProfilesList) -> Profiles { + Profiles { + emit, + model, + data : RefCell::new( StoredProfiles { + profiles : vec![ + Profile { + name : String::from("First"), + settings : ProfileSettings::default(), + }, + Profile { + name : String::from("Second"), + settings : ProfileSettings::default(), + }, + Profile { + name : String::from("Third"), + settings : ProfileSettings::default(), + }, + ], + }), + current_profile_idx : RefCell::new(1), + } + } + #[test] + fn hash_algo_reciprocicity_test() { + for i in 0..(HashAlgorithm::variant_count() as u8) { + let x : HashAlgorithm = i.try_into().unwrap(); + let y = x.into(); + assert_eq!(i,y); + } + let z: Result<HashAlgorithm,_> = (HashAlgorithm::variant_count() as u8).try_into(); + assert!(z.is_err()); + } + #[test] + fn leet_level_reciprocicity_test() { + for i in 1..=(LeetLevel::variant_count() as u8) { + let x : LeetLevel = i.try_into().unwrap(); + let y = x.into(); + assert_eq!(i,y); + } + let z : Result<LeetLevel,_> = (1+LeetLevel::variant_count() as u8).try_into(); + assert!(z.is_err()); + let a : Result<LeetLevel,_> = 0.try_into(); + assert!(a.is_err()); + } + #[test] + fn set_name_test() { + let (emit_nothing, model_doesnt_emit) = expect_no_emission(); + let profiles_assert_on_emit = get_test_profiles_three_entries(emit_nothing, model_doesnt_emit); + //profiles_assert_on_emit has profile 1 set as active. We can therefore modify the name of 0 (or 2) without emission. + let should_change = profiles_assert_on_emit.set_name(0, "SomethingIJustMadeUp".into()); + assert!(should_change); + profiles_assert_on_emit.name(0, |x| {assert_eq!(x, "SomethingIJustMadeUp")}); + } + #[test] + fn set_current_profile_test() { + { + //setting to same index should not do anything. + let (emit_nothing, model_doesnt_emit) = expect_no_emission(); + let profiles_assert_on_emit = get_test_profiles_three_entries(emit_nothing, model_doesnt_emit); + profiles_assert_on_emit.set_current_profile(profiles_assert_on_emit.current_profile()); + } + { + //setting to different index should emit both name and index changed. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + let profiles_wants_one_name_change = get_test_profiles_three_entries(emit, list); + let previous_profile = profiles_wants_one_name_change.current_profile(); + profiles_wants_one_name_change.set_current_profile(previous_profile + 1); + assert_eq!(previous_profile + 1, profiles_wants_one_name_change.current_profile()); + } + { + //Last, but not least, even if the _name_ of the profiles is the same, we WANT to emit current_profile_name_changed. + //This is because the UI is also functioning as Controller, so every profile change (even if it's the same name) should refresh the + //corresponding UI. + + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + let p = Profiles { + emit, + model :list, + data : RefCell::new( StoredProfiles { + profiles : vec![ + Profile { + name : String::from("Twin"), + settings : ProfileSettings::default(), + }, + Profile { + name : String::from("Twin"), + settings : ProfileSettings::default(), + }, + ], + }), + current_profile_idx : RefCell::new(1), + }; + p.set_current_profile(0); + assert_eq!(p.current_profile(), 0); + } + } + #[test] + fn current_profile_name_test() { + //let's check if setting the same value we already have emits. Hopefully not. Set the index 1 profile's name to "Second" + { + let (emit_nothing, model_doesnt_emit) = expect_no_emission(); + let profiles_assert_on_emit = get_test_profiles_three_entries(emit_nothing, model_doesnt_emit); + let should_not_change = profiles_assert_on_emit.set_name(1,"Second".into()); + assert!(!should_not_change); + } + + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + let profiles_wants_one_name_change = get_test_profiles_three_entries(emit, list); + let should_change2 = profiles_wants_one_name_change.set_name(1, "Agadlagugu".into()); + assert!(should_change2); + profiles_wants_one_name_change.name(1, |x| {assert_eq!(x, "Agadlagugu")}); + } + #[test] + fn insert_rows_before_current_test() { + //if we add a row before the current, only, current_profile should emit. No need to emit profile_name, as the data is still the same. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().return_once(|_,_|{()}).once(); + list.expect_end_insert_rows().return_once(||{()}).once(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + //must make sure that index changes, but data remains same + let profile_tests_insert_before_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_insert_before_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_insert_before_current.current_profile(); + let worked = profile_tests_insert_before_current.insert_rows(1, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_insert_before_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index +1); + } + #[test] + fn insert_rows_after_current_test() { + //if we add a row after current, no signals other than insert_rows related ones should emit: + //if we add a row before the current, only, current_profile should emit. No need to emit profile_name, as the data is still the same. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().return_once(|_,_|{()}).once(); + list.expect_end_insert_rows().return_once(||{()}).once(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + //must make sure that index changes, but data remains same + let profile_tests_insert_before_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_insert_before_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_insert_before_current.current_profile(); + let worked = profile_tests_insert_before_current.insert_rows(3, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_insert_before_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index); + } + #[test] + fn remove_rows_before_current_test() { + //if we remove a row before the current, only current_profile should emit. No need to emit profile_name, as the data is still the same. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().return_once(|_,_|{()}).once(); + list.expect_end_remove_rows().return_once(||{()}).once(); + //must make sure that index changes, but data remains same + let profile_tests_remove_before_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_remove_before_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_remove_before_current.current_profile(); + let worked = profile_tests_remove_before_current.remove_rows(0, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_remove_before_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index - 1); + } + #[test] + fn remove_rows_containign_current_test() { + //if we remove the current row, both, index and name will change and index should be zero. + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().return_once(||{()}).once(); + emit.expect_current_profile_name_changed().return_once(||{()}).once(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().return_once(|_,_|{()}).once(); + list.expect_end_remove_rows().return_once(||{()}).once(); + //must make sure that index changes, but data remains same + let profile_tests_remove_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_remove_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_remove_current.current_profile(); + let worked = profile_tests_remove_current.remove_rows(1, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_remove_current.current_profile(); + assert_ne!(old_name, new_name); + assert_eq!(new_index, 0); + assert_ne!(new_index, old_index); + } + #[test] + fn remove_rows_after_current_test() { + //only remove rows emit, no profile related emit + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().return_once(|_,_|{()}).once(); + list.expect_end_remove_rows().return_once(||{()}).once(); + //must make sure that index changes, but data remains same + let profile_tests_remove_after_current = get_test_profiles_three_entries(emit, list); + let get_current_name_copy = || { let mut res = String::new(); profile_tests_remove_after_current.current_profile_name(|x| res = String::from(x)); res}; + let old_name = get_current_name_copy(); + let old_index = profile_tests_remove_after_current.current_profile(); + let worked = profile_tests_remove_after_current.remove_rows(2, 1); + assert!(worked); + let new_name = get_current_name_copy(); + let new_index = profile_tests_remove_after_current.current_profile(); + assert_eq!(old_name, new_name); + assert_eq!(new_index, old_index); + } + // Leet level needs its own test, as setting leet level depends on use_leet. + #[test] + fn set_leet_level_test() { + let (emit, model) = expect_no_emission(); + //we're not going through the new() method, to avoid touching the filesystem. + let profiles = get_test_profiles_three_entries(emit, model); + + //remember the old values in other rows to confimr they aren't touched + let oldvals = [profiles.leet_level(0), profiles.leet_level(2)]; + + //make sure that leet isn't enabled. + assert_eq!(profiles.use_leet(1), u8::default()); + //make sure we are changing something. + assert_ne!(profiles.leet_level(1), 2); + //assure that nothing happens if we try to change leet level with leet disabled. + let changed = profiles.set_leet_level(1,2); + assert!(!changed); + assert_eq!(profiles.use_leet(1), u8::default()); + + //enable leet. + let changed = profiles.set_use_leet(1,1); + assert!(changed); + //make sure we are changing something. + assert_ne!(profiles.leet_level(1), 2); + //actually change something: + let changed = profiles.set_leet_level(1,2); + assert!(changed); + assert_eq!(profiles.leet_level(1), 2); + //check that other values are unaffected + assert_eq!(profiles.leet_level(0), oldvals[0]); + assert_eq!(profiles.leet_level(2), oldvals[1]); + //IMPORTANT: check that assigning the same value returns not-changed. + let changed = profiles.set_leet_level(1,2); + assert!(!changed); + //disable leet again and make sure leet_level returns 0 again. + let changed = profiles.set_use_leet(1,0); + assert!(changed); + assert_eq!(profiles.leet_level(1), u8::default()); + } + + // The remaining properties should (at the time of writing) not emit. + // This means we can use one and the same test for all of them :-) + // Except that complex types have a different getter syntax... + // Oh well. + fn expect_no_emission() -> (ProfilesEmitter, ProfilesList) { + let mut emit = ProfilesEmitter::new(); + emit.expect_clone().never(); + emit.expect_current_profile_changed().never(); + emit.expect_current_profile_name_changed().never(); + emit.expect_new_data_ready().never(); + let mut list = ProfilesList::new(); + list.expect_layout_about_to_be_changed().never(); + list.expect_layout_changed().never(); + list.expect_data_changed().never(); + list.expect_begin_reset_model().never(); + list.expect_end_reset_model().never(); + list.expect_begin_insert_rows().never(); + list.expect_end_insert_rows().never(); + list.expect_begin_move_rows().never(); + list.expect_end_move_rows().never(); + list.expect_begin_remove_rows().never(); + list.expect_end_remove_rows().never(); + (emit, list) + } + macro_rules! simple_setter_tests { + ( $( $test_name:ident, $setter:ident, $getter:ident, $val:literal ),* ) => { + $(#[test] + fn $test_name() { + let (emit, model) = expect_no_emission(); + //we're not going through the new() method, to avoid touching the filesystem. + let profiles = get_test_profiles_three_entries(emit, model); + + //remember the old values in other rows to confimr they aren't touched + let oldvals = [profiles.$getter(0), profiles.$getter(2)]; + + //make sure we are changing something. + assert_ne!(profiles.$getter(1), $val); + //assure that something changed + let changed = profiles.$setter(1,$val); + assert!(changed); + assert_eq!(profiles.$getter(1), $val); + //check that other values are unaffected + assert_eq!(profiles.$getter(0), oldvals[0]); + assert_eq!(profiles.$getter(2), oldvals[1]); + //IMPORTANT: check that assigning the same value returns not-changed. + let changed = profiles.$setter(1,$val); + assert!(!changed); + })* + }; + } + macro_rules! string_setter_tests { + ( $( $test_name:ident, $setter:ident, $getter:ident, $val:literal ),* ) => { + $(#[test] + fn $test_name() { + let (emit, model) = expect_no_emission(); + //we're not going through the new() method, to avoid touching the filesystem. + let profiles = get_test_profiles_three_entries(emit, model); + + //remember the old values in other rows to confirm they aren't touched. + let mut oldvals = [String::new(), String::new()]; + profiles.$getter(0, |x| { oldvals[0] = String::from(x); }); + profiles.$getter(2, |x| { oldvals[1] = String::from(x); }); + //make sure we are changing something. + profiles.$getter(1, |x| { assert_ne!(x, $val); }); + //assure that something changed: + let changed = profiles.$setter(1,String::from($val)); + assert!(changed); + profiles.$getter(1, |x| { assert_eq!(x, $val); }); + //check that the other values are unaffected. + profiles.$getter(0, |x| { assert_eq!(oldvals[0], x); }); + profiles.$getter(2, |x| { assert_eq!(oldvals[1], x); }); + //IMPORTANT: check that assigning the same value returns not-changed. + let changed = profiles.$setter(1,String::from($val)); + assert!(!changed); + })* + }; + } + string_setter_tests!( + set_characters_test,set_characters,characters,"ABCDEFG", + set_modifier_test, set_modifier, modifier, "ab", + set_prefix_test, set_prefix, prefix, "xy", + set_suffix_test, set_suffix, suffix, "zw", + set_username_test, set_username, username, "Zargothrax" + ); + simple_setter_tests!( + set_hash_algorithm_test, set_hash_algorithm, hash_algorithm, 5, + set_password_length_test, set_password_length, password_length, 12, + set_use_domain_test, set_use_domain, use_domain, false, + set_use_leet_test, set_use_leet, use_leet, 1, + set_use_port_path_test, set_use_port_path, use_port_path, true, + set_use_protocol_test, set_use_protocol, use_protocol, true, + set_use_subdomains_test, set_use_subdomains, use_subdomains, true, + set_use_user_info_test, set_use_user_info, use_user_info, true, + set_use_undefined_as_protocol_fallback_test, set_use_undefined_as_protocol_fallback, use_undefined_as_protocol_fallback, false + ); +} diff --git a/rust/src/implementation/pwm_macros.rs b/rust/src/implementation/pwm_macros.rs new file mode 100644 index 0000000..158e4cd --- /dev/null +++ b/rust/src/implementation/pwm_macros.rs @@ -0,0 +1,27 @@ +extern crate passwordmaker_macros; +pub use self::passwordmaker_macros::*; + +pub trait EnumVariantCount { + fn variant_count() -> usize; +} + +#[cfg(test)] +mod pwm_macro_tests { + use super::*; + + #[allow(dead_code)] + enum Nest{ A, B } + #[allow(dead_code)] + #[derive(EnumVariantCount)] + enum TestNum { + A(usize), + B, + C(Nest), + D, + E{a : usize, b: f64} + } + #[test] + fn enum_variant_count_test(){ + assert_eq!(TestNum::variant_count(), 5); + } +}
\ No newline at end of file diff --git a/rust/src/interface.rs b/rust/src/interface.rs new file mode 100644 index 0000000..e065f22 --- /dev/null +++ b/rust/src/interface.rs @@ -0,0 +1,1062 @@ +/* generated by rust_qt_binding_generator */ +use libc::{c_char, c_ushort, c_int}; +use std::slice; +use std::char::decode_utf16; + +use std::sync::Arc; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::ptr::null; + +#[cfg(test)] +use mockall::automock; + +use crate::implementation::*; + + +#[repr(C)] +pub struct COption<T> { + data: T, + some: bool, +} + +impl<T> COption<T> { + #![allow(dead_code)] + fn into(self) -> Option<T> { + if self.some { + Some(self.data) + } else { + None + } + } +} + +impl<T> From<Option<T>> for COption<T> +where + T: Default, +{ + fn from(t: Option<T>) -> COption<T> { + if let Some(v) = t { + COption { + data: v, + some: true, + } + } else { + COption { + data: T::default(), + some: false, + } + } + } +} + + +pub enum QString {} + +fn set_string_from_utf16(s: &mut String, str: *const c_ushort, len: c_int) { + let utf16 = unsafe { slice::from_raw_parts(str, to_usize(len)) }; + let characters = decode_utf16(utf16.iter().cloned()) + .map(|r| r.unwrap()); + s.clear(); + s.extend(characters); +} + + + +#[repr(C)] +#[derive(PartialEq, Eq, Debug)] +pub enum SortOrder { + Ascending = 0, + Descending = 1, +} + +#[repr(C)] +pub struct QModelIndex { + row: c_int, + internal_id: usize, +} + + +fn to_usize(n: c_int) -> usize { + if n < 0 { + panic!("Cannot cast {} to usize", n); + } + n as usize +} + + +fn to_c_int(n: usize) -> c_int { + if n > c_int::max_value() as usize { + panic!("Cannot cast {} to c_int", n); + } + n as c_int +} + + +pub struct PasswordMakerQObject {} + +pub struct PasswordMakerEmitter { + qobject: Arc<AtomicPtr<PasswordMakerQObject>>, + generated_password_changed: extern fn(*mut PasswordMakerQObject), + generator_state_changed: extern fn(*mut PasswordMakerQObject), + i_say_sexy_things_to_myself_while_im_dancing_changed: extern fn(*mut PasswordMakerQObject), + master_password_changed: extern fn(*mut PasswordMakerQObject), + url_changed: extern fn(*mut PasswordMakerQObject), + used_text_changed: extern fn(*mut PasswordMakerQObject), +} + +unsafe impl Send for PasswordMakerEmitter {} + +#[cfg_attr(test, automock)] +#[cfg_attr(test,allow(dead_code))] +impl PasswordMakerEmitter { + /// Clone the emitter + pub fn clone(&self) -> Self { + Self { + qobject: self.qobject.clone(), + generated_password_changed: self.generated_password_changed, + generator_state_changed: self.generator_state_changed, + i_say_sexy_things_to_myself_while_im_dancing_changed: self.i_say_sexy_things_to_myself_while_im_dancing_changed, + master_password_changed: self.master_password_changed, + url_changed: self.url_changed, + used_text_changed: self.used_text_changed, + } + } + fn clear(&self) { + let n: *const PasswordMakerQObject = null(); + self.qobject.store(n as *mut PasswordMakerQObject, Ordering::SeqCst); + } + pub fn generated_password_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.generated_password_changed)(ptr); + } + } + pub fn generator_state_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.generator_state_changed)(ptr); + } + } + pub fn i_say_sexy_things_to_myself_while_im_dancing_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.i_say_sexy_things_to_myself_while_im_dancing_changed)(ptr); + } + } + pub fn master_password_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.master_password_changed)(ptr); + } + } + pub fn url_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.url_changed)(ptr); + } + } + pub fn used_text_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.used_text_changed)(ptr); + } + } +} + +pub trait PasswordMakerTrait { + #[cfg(not(test))] + fn new(emit: PasswordMakerEmitter, + profiles: Profiles, + settings: Settings) -> Self; + #[cfg(not(test))] + fn emit(&self) -> &PasswordMakerEmitter; + #[cfg(test)] + fn new(emit: MockPasswordMakerEmitter, + profiles: Profiles, + settings: Settings) -> Self; + #[cfg(test)] + fn emit(&self) -> &MockPasswordMakerEmitter; + fn generated_password<F>(&self, setter: F) where F: FnOnce(&str); + fn generator_state(&self) -> u8; + fn i_say_sexy_things_to_myself_while_im_dancing(&self) -> bool; + fn set_i_say_sexy_things_to_myself_while_im_dancing(&self, value: bool); + fn master_password<F>(&self, setter: F) where F: FnOnce(&str); + fn set_master_password(&self, value: String); + fn profiles(&self) -> &Profiles; + fn settings(&self) -> &Settings; + fn url<F>(&self, setter: F) where F: FnOnce(&str); + fn set_url(&self, value: String); + fn used_text<F>(&self, setter: F) where F: FnOnce(&str); + fn set_used_text(&self, value: String); + fn profile_changed(&self) -> (); + fn store_settings(&self) -> bool; +} +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn password_maker_new( + password_maker: *mut PasswordMakerQObject, + password_maker_generated_password_changed: extern fn(*mut PasswordMakerQObject), + password_maker_generator_state_changed: extern fn(*mut PasswordMakerQObject), + password_maker_i_say_sexy_things_to_myself_while_im_dancing_changed: extern fn(*mut PasswordMakerQObject), + password_maker_master_password_changed: extern fn(*mut PasswordMakerQObject), + profiles: *mut ProfilesQObject, + profiles_current_profile_changed: extern fn(*mut ProfilesQObject), + profiles_current_profile_name_changed: extern fn(*mut ProfilesQObject), + profiles_new_data_ready: extern fn(*mut ProfilesQObject), + profiles_layout_about_to_be_changed: extern fn(*mut ProfilesQObject), + profiles_layout_changed: extern fn(*mut ProfilesQObject), + profiles_data_changed: extern fn(*mut ProfilesQObject, usize, usize), + profiles_begin_reset_model: extern fn(*mut ProfilesQObject), + profiles_end_reset_model: extern fn(*mut ProfilesQObject), + profiles_begin_insert_rows: extern fn(*mut ProfilesQObject, usize, usize), + profiles_end_insert_rows: extern fn(*mut ProfilesQObject), + profiles_begin_move_rows: extern fn(*mut ProfilesQObject, usize, usize, usize), + profiles_end_move_rows: extern fn(*mut ProfilesQObject), + profiles_begin_remove_rows: extern fn(*mut ProfilesQObject, usize, usize), + profiles_end_remove_rows: extern fn(*mut ProfilesQObject), + settings: *mut SettingsQObject, + settings_clear_generated_password_seconds_changed: extern fn(*mut SettingsQObject), + settings_clear_master_password_seconds_changed: extern fn(*mut SettingsQObject), + settings_hide_generated_password_changed: extern fn(*mut SettingsQObject), + password_maker_url_changed: extern fn(*mut PasswordMakerQObject), + password_maker_used_text_changed: extern fn(*mut PasswordMakerQObject), +) -> *mut PasswordMaker { + let profiles_emit = ProfilesEmitter { + qobject: Arc::new(AtomicPtr::new(profiles)), + current_profile_changed: profiles_current_profile_changed, + current_profile_name_changed: profiles_current_profile_name_changed, + new_data_ready: profiles_new_data_ready, + }; + let model = ProfilesList { + qobject: profiles, + layout_about_to_be_changed: profiles_layout_about_to_be_changed, + layout_changed: profiles_layout_changed, + data_changed: profiles_data_changed, + begin_reset_model: profiles_begin_reset_model, + end_reset_model: profiles_end_reset_model, + begin_insert_rows: profiles_begin_insert_rows, + end_insert_rows: profiles_end_insert_rows, + begin_move_rows: profiles_begin_move_rows, + end_move_rows: profiles_end_move_rows, + begin_remove_rows: profiles_begin_remove_rows, + end_remove_rows: profiles_end_remove_rows, + }; + let d_profiles = Profiles::new(profiles_emit, model); + let settings_emit = SettingsEmitter { + qobject: Arc::new(AtomicPtr::new(settings)), + clear_generated_password_seconds_changed: settings_clear_generated_password_seconds_changed, + clear_master_password_seconds_changed: settings_clear_master_password_seconds_changed, + hide_generated_password_changed: settings_hide_generated_password_changed, + }; + let d_settings = Settings::new(settings_emit); + let password_maker_emit = PasswordMakerEmitter { + qobject: Arc::new(AtomicPtr::new(password_maker)), + generated_password_changed: password_maker_generated_password_changed, + generator_state_changed: password_maker_generator_state_changed, + i_say_sexy_things_to_myself_while_im_dancing_changed: password_maker_i_say_sexy_things_to_myself_while_im_dancing_changed, + master_password_changed: password_maker_master_password_changed, + url_changed: password_maker_url_changed, + used_text_changed: password_maker_used_text_changed, + }; + let d_password_maker = PasswordMaker::new(password_maker_emit, + d_profiles, + d_settings); + Box::into_raw(Box::new(d_password_maker)) +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_free(ptr: *mut PasswordMaker) { + Box::from_raw(ptr).emit().clear(); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_generated_password_get( +ptr: *const PasswordMaker, +p: *mut QString, +set: extern fn(*mut QString, *const c_char, c_int), +) { +let o = &*ptr; +o.generated_password(|v| { + let s: *const c_char = v.as_ptr() as *const c_char; + set(p, s, to_c_int(v.len())); +}); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_generator_state_get(ptr: *const PasswordMaker) -> u8 { + (&*ptr).generator_state() +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_i_say_sexy_things_to_myself_while_im_dancing_get(ptr: *const PasswordMaker) -> bool { + (&*ptr).i_say_sexy_things_to_myself_while_im_dancing() +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_i_say_sexy_things_to_myself_while_im_dancing_set(ptr: *const PasswordMaker, v: bool) { + (&*ptr).set_i_say_sexy_things_to_myself_while_im_dancing(v); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_master_password_get( +ptr: *const PasswordMaker, +p: *mut QString, +set: extern fn(*mut QString, *const c_char, c_int), +) { +let o = &*ptr; +o.master_password(|v| { + let s: *const c_char = v.as_ptr() as *const c_char; + set(p, s, to_c_int(v.len())); +}); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_master_password_set(ptr: *const PasswordMaker, v: *const c_ushort, len: c_int) { + let o = &*ptr; + let mut s = String::new(); + set_string_from_utf16(&mut s, v, len); + o.set_master_password(s); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_profiles_get(ptr: *const PasswordMaker) -> *const Profiles { + (&*ptr).profiles() +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_settings_get(ptr: *const PasswordMaker) -> *const Settings { + (&*ptr).settings() +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_url_get( +ptr: *const PasswordMaker, +p: *mut QString, +set: extern fn(*mut QString, *const c_char, c_int), +) { +let o = &*ptr; +o.url(|v| { + let s: *const c_char = v.as_ptr() as *const c_char; + set(p, s, to_c_int(v.len())); +}); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_url_set(ptr: *const PasswordMaker, v: *const c_ushort, len: c_int) { + let o = &*ptr; + let mut s = String::new(); + set_string_from_utf16(&mut s, v, len); + o.set_url(s); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_used_text_get( +ptr: *const PasswordMaker, +p: *mut QString, +set: extern fn(*mut QString, *const c_char, c_int), +) { +let o = &*ptr; +o.used_text(|v| { + let s: *const c_char = v.as_ptr() as *const c_char; + set(p, s, to_c_int(v.len())); +}); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_used_text_set(ptr: *const PasswordMaker, v: *const c_ushort, len: c_int) { + let o = &*ptr; + let mut s = String::new(); + set_string_from_utf16(&mut s, v, len); + o.set_used_text(s); +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_profile_changed(ptr: *const PasswordMaker) { + let o = &*ptr; + o.profile_changed() +} + +#[no_mangle] +pub unsafe extern "C" fn password_maker_store_settings(ptr: *const PasswordMaker) -> bool { + let o = &*ptr; + o.store_settings() +} + +pub struct ProfilesQObject {} + +pub struct ProfilesEmitter { + qobject: Arc<AtomicPtr<ProfilesQObject>>, + current_profile_changed: extern fn(*mut ProfilesQObject), + current_profile_name_changed: extern fn(*mut ProfilesQObject), + new_data_ready: extern fn(*mut ProfilesQObject), +} + +unsafe impl Send for ProfilesEmitter {} + +#[cfg_attr(test, automock)] +#[cfg_attr(test,allow(dead_code))] +impl ProfilesEmitter { + /// Clone the emitter + pub fn clone(&self) -> Self { + Self { + qobject: self.qobject.clone(), + current_profile_changed: self.current_profile_changed, + current_profile_name_changed: self.current_profile_name_changed, + new_data_ready: self.new_data_ready, + } + } + fn clear(&self) { + let n: *const ProfilesQObject = null(); + self.qobject.store(n as *mut ProfilesQObject, Ordering::SeqCst); + } + pub fn current_profile_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.current_profile_changed)(ptr); + } + } + pub fn current_profile_name_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.current_profile_name_changed)(ptr); + } + } + pub fn new_data_ready(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.new_data_ready)(ptr); + } + } +} + +#[derive(Clone)] +pub struct ProfilesList { + qobject: *mut ProfilesQObject, + layout_about_to_be_changed: extern fn(*mut ProfilesQObject), + layout_changed: extern fn(*mut ProfilesQObject), + data_changed: extern fn(*mut ProfilesQObject, usize, usize), + begin_reset_model: extern fn(*mut ProfilesQObject), + end_reset_model: extern fn(*mut ProfilesQObject), + begin_insert_rows: extern fn(*mut ProfilesQObject, usize, usize), + end_insert_rows: extern fn(*mut ProfilesQObject), + begin_move_rows: extern fn(*mut ProfilesQObject, usize, usize, usize), + end_move_rows: extern fn(*mut ProfilesQObject), + begin_remove_rows: extern fn(*mut ProfilesQObject, usize, usize), + end_remove_rows: extern fn(*mut ProfilesQObject), +} + + +#[cfg_attr(test, automock)] +impl ProfilesList { + pub fn layout_about_to_be_changed(&self) { + (self.layout_about_to_be_changed)(self.qobject); + } + pub fn layout_changed(&self) { + (self.layout_changed)(self.qobject); + } + pub fn data_changed(&self, first: usize, last: usize) { + (self.data_changed)(self.qobject, first, last); + } + pub fn begin_reset_model(&self) { + (self.begin_reset_model)(self.qobject); + } + pub fn end_reset_model(&self) { + (self.end_reset_model)(self.qobject); + } + pub fn begin_insert_rows(&self, first: usize, last: usize) { + (self.begin_insert_rows)(self.qobject, first, last); + } + pub fn end_insert_rows(&self) { + (self.end_insert_rows)(self.qobject); + } + pub fn begin_move_rows(&self, first: usize, last: usize, destination: usize) { + (self.begin_move_rows)(self.qobject, first, last, destination); + } + pub fn end_move_rows(&self) { + (self.end_move_rows)(self.qobject); + } + pub fn begin_remove_rows(&self, first: usize, last: usize) { + (self.begin_remove_rows)(self.qobject, first, last); + } + pub fn end_remove_rows(&self) { + (self.end_remove_rows)(self.qobject); + } +} + +pub trait ProfilesTrait { + #[cfg(not(test))] + fn new(emit: ProfilesEmitter, model: ProfilesList) -> Self; + #[cfg(not(test))] + fn emit(&self) -> &ProfilesEmitter; + #[cfg(test)] + fn new(emit: MockProfilesEmitter, model: MockProfilesList) -> Self; + #[cfg(test)] + fn emit(&self) -> &MockProfilesEmitter; + fn current_profile(&self) -> u32; + fn set_current_profile(&self, value: u32); + fn current_profile_name<F>(&self, setter: F) where F: FnOnce(&str); + fn store(&self) -> bool; + fn row_count(&self) -> usize; + fn insert_rows(&self, _row: usize, _count: usize) -> bool { false } + fn remove_rows(&self, _row: usize, _count: usize) -> bool { false } + fn can_fetch_more(&self) -> bool { + false + } + fn fetch_more(&self) {} + fn sort(&self, _: u8, _: SortOrder) {} + fn characters<F: FnOnce(&str)>(&self, index: usize, setter: F); + fn set_characters(&self, index: usize, _: String) -> bool; + fn hash_algorithm(&self, index: usize) -> u8; + fn set_hash_algorithm(&self, index: usize, _: u8) -> bool; + fn leet_level(&self, index: usize) -> u8; + fn set_leet_level(&self, index: usize, _: u8) -> bool; + fn modifier<F: FnOnce(&str)>(&self, index: usize, setter: F); + fn set_modifier(&self, index: usize, _: String) -> bool; + fn name<F: FnOnce(&str)>(&self, index: usize, setter: F); + fn set_name(&self, index: usize, _: String) -> bool; + fn password_length(&self, index: usize) -> u32; + fn set_password_length(&self, index: usize, _: u32) -> bool; + fn prefix<F: FnOnce(&str)>(&self, index: usize, setter: F); + fn set_prefix(&self, index: usize, _: String) -> bool; + fn suffix<F: FnOnce(&str)>(&self, index: usize, setter: F); + fn set_suffix(&self, index: usize, _: String) -> bool; + fn use_domain(&self, index: usize) -> bool; + fn set_use_domain(&self, index: usize, _: bool) -> bool; + fn use_leet(&self, index: usize) -> u8; + fn set_use_leet(&self, index: usize, _: u8) -> bool; + fn use_port_path(&self, index: usize) -> bool; + fn set_use_port_path(&self, index: usize, _: bool) -> bool; + fn use_protocol(&self, index: usize) -> bool; + fn set_use_protocol(&self, index: usize, _: bool) -> bool; + fn use_subdomains(&self, index: usize) -> bool; + fn set_use_subdomains(&self, index: usize, _: bool) -> bool; + fn use_undefined_as_protocol_fallback(&self, index: usize) -> bool; + fn set_use_undefined_as_protocol_fallback(&self, index: usize, _: bool) -> bool; + fn use_user_info(&self, index: usize) -> bool; + fn set_use_user_info(&self, index: usize, _: bool) -> bool; + fn username<F: FnOnce(&str)>(&self, index: usize, setter: F); + fn set_username(&self, index: usize, _: String) -> bool; +} +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn profiles_new( + profiles: *mut ProfilesQObject, + profiles_current_profile_changed: extern fn(*mut ProfilesQObject), + profiles_current_profile_name_changed: extern fn(*mut ProfilesQObject), + profiles_new_data_ready: extern fn(*mut ProfilesQObject), + profiles_layout_about_to_be_changed: extern fn(*mut ProfilesQObject), + profiles_layout_changed: extern fn(*mut ProfilesQObject), + profiles_data_changed: extern fn(*mut ProfilesQObject, usize, usize), + profiles_begin_reset_model: extern fn(*mut ProfilesQObject), + profiles_end_reset_model: extern fn(*mut ProfilesQObject), + profiles_begin_insert_rows: extern fn(*mut ProfilesQObject, usize, usize), + profiles_end_insert_rows: extern fn(*mut ProfilesQObject), + profiles_begin_move_rows: extern fn(*mut ProfilesQObject, usize, usize, usize), + profiles_end_move_rows: extern fn(*mut ProfilesQObject), + profiles_begin_remove_rows: extern fn(*mut ProfilesQObject, usize, usize), + profiles_end_remove_rows: extern fn(*mut ProfilesQObject), +) -> *mut Profiles { + let profiles_emit = ProfilesEmitter { + qobject: Arc::new(AtomicPtr::new(profiles)), + current_profile_changed: profiles_current_profile_changed, + current_profile_name_changed: profiles_current_profile_name_changed, + new_data_ready: profiles_new_data_ready, + }; + let model = ProfilesList { + qobject: profiles, + layout_about_to_be_changed: profiles_layout_about_to_be_changed, + layout_changed: profiles_layout_changed, + data_changed: profiles_data_changed, + begin_reset_model: profiles_begin_reset_model, + end_reset_model: profiles_end_reset_model, + begin_insert_rows: profiles_begin_insert_rows, + end_insert_rows: profiles_end_insert_rows, + begin_move_rows: profiles_begin_move_rows, + end_move_rows: profiles_end_move_rows, + begin_remove_rows: profiles_begin_remove_rows, + end_remove_rows: profiles_end_remove_rows, + }; + let d_profiles = Profiles::new(profiles_emit, model); + Box::into_raw(Box::new(d_profiles)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_free(ptr: *mut Profiles) { + Box::from_raw(ptr).emit().clear(); +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_current_profile_get(ptr: *const Profiles) -> u32 { + (&*ptr).current_profile() +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_current_profile_set(ptr: *const Profiles, v: u32) { + (&*ptr).set_current_profile(v); +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_current_profile_name_get( +ptr: *const Profiles, +p: *mut QString, +set: extern fn(*mut QString, *const c_char, c_int), +) { +let o = &*ptr; +o.current_profile_name(|v| { + let s: *const c_char = v.as_ptr() as *const c_char; + set(p, s, to_c_int(v.len())); +}); +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_store(ptr: *const Profiles) -> bool { + let o = &*ptr; + o.store() +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_row_count(ptr: *const Profiles) -> c_int { + to_c_int((&*ptr).row_count()) +} +#[no_mangle] +pub unsafe extern "C" fn profiles_insert_rows(ptr: *const Profiles, row: c_int, count: c_int) -> bool { + (&*ptr).insert_rows(to_usize(row), to_usize(count)) +} +#[no_mangle] +pub unsafe extern "C" fn profiles_remove_rows(ptr: *const Profiles, row: c_int, count: c_int) -> bool { + (&*ptr).remove_rows(to_usize(row), to_usize(count)) +} +#[no_mangle] +pub unsafe extern "C" fn profiles_can_fetch_more(ptr: *const Profiles) -> bool { + (&*ptr).can_fetch_more() +} +#[no_mangle] +pub unsafe extern "C" fn profiles_fetch_more(ptr: *const Profiles) { + (&*ptr).fetch_more() +} +#[no_mangle] +pub unsafe extern "C" fn profiles_sort( + ptr: *const Profiles, + column: u8, + order: SortOrder, +) { + (&*ptr).sort(column, order) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_characters( + ptr: *const Profiles, row: c_int, + d: *mut QString, + set: extern fn(*mut QString, *const c_char, len: c_int), +) { + let o = &*ptr; + o.characters(to_usize(row), |data| { + let s: *const c_char = data.as_ptr() as *const c_char; + set(d, s, to_c_int(data.len())); + }) + } + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_characters( + ptr: *const Profiles, row: c_int, + s: *const c_ushort, len: c_int, +) -> bool { + let o = &*ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_characters(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_hash_algorithm(ptr: *const Profiles, row: c_int) -> u8 { + let o = &*ptr; + o.hash_algorithm(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_hash_algorithm( + ptr: *const Profiles, row: c_int, + v: u8, +) -> bool { + (&*ptr).set_hash_algorithm(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_leet_level(ptr: *const Profiles, row: c_int) -> u8 { + let o = &*ptr; + o.leet_level(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_leet_level( + ptr: *const Profiles, row: c_int, + v: u8, +) -> bool { + (&*ptr).set_leet_level(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_modifier( + ptr: *const Profiles, row: c_int, + d: *mut QString, + set: extern fn(*mut QString, *const c_char, len: c_int), +) { + let o = &*ptr; + o.modifier(to_usize(row), |data| { + let s: *const c_char = data.as_ptr() as *const c_char; + set(d, s, to_c_int(data.len())); + }) + } + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_modifier( + ptr: *const Profiles, row: c_int, + s: *const c_ushort, len: c_int, +) -> bool { + let o = &*ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_modifier(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_name( + ptr: *const Profiles, row: c_int, + d: *mut QString, + set: extern fn(*mut QString, *const c_char, len: c_int), +) { + let o = &*ptr; + o.name(to_usize(row), |data| { + let s: *const c_char = data.as_ptr() as *const c_char; + set(d, s, to_c_int(data.len())); + }) + } + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_name( + ptr: *const Profiles, row: c_int, + s: *const c_ushort, len: c_int, +) -> bool { + let o = &*ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_name(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_password_length(ptr: *const Profiles, row: c_int) -> u32 { + let o = &*ptr; + o.password_length(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_password_length( + ptr: *const Profiles, row: c_int, + v: u32, +) -> bool { + (&*ptr).set_password_length(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_prefix( + ptr: *const Profiles, row: c_int, + d: *mut QString, + set: extern fn(*mut QString, *const c_char, len: c_int), +) { + let o = &*ptr; + o.prefix(to_usize(row), |data| { + let s: *const c_char = data.as_ptr() as *const c_char; + set(d, s, to_c_int(data.len())); + }) + } + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_prefix( + ptr: *const Profiles, row: c_int, + s: *const c_ushort, len: c_int, +) -> bool { + let o = &*ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_prefix(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_suffix( + ptr: *const Profiles, row: c_int, + d: *mut QString, + set: extern fn(*mut QString, *const c_char, len: c_int), +) { + let o = &*ptr; + o.suffix(to_usize(row), |data| { + let s: *const c_char = data.as_ptr() as *const c_char; + set(d, s, to_c_int(data.len())); + }) + } + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_suffix( + ptr: *const Profiles, row: c_int, + s: *const c_ushort, len: c_int, +) -> bool { + let o = &*ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_suffix(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_domain(ptr: *const Profiles, row: c_int) -> bool { + let o = &*ptr; + o.use_domain(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_domain( + ptr: *const Profiles, row: c_int, + v: bool, +) -> bool { + (&*ptr).set_use_domain(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_leet(ptr: *const Profiles, row: c_int) -> u8 { + let o = &*ptr; + o.use_leet(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_leet( + ptr: *const Profiles, row: c_int, + v: u8, +) -> bool { + (&*ptr).set_use_leet(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_port_path(ptr: *const Profiles, row: c_int) -> bool { + let o = &*ptr; + o.use_port_path(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_port_path( + ptr: *const Profiles, row: c_int, + v: bool, +) -> bool { + (&*ptr).set_use_port_path(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_protocol(ptr: *const Profiles, row: c_int) -> bool { + let o = &*ptr; + o.use_protocol(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_protocol( + ptr: *const Profiles, row: c_int, + v: bool, +) -> bool { + (&*ptr).set_use_protocol(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_subdomains(ptr: *const Profiles, row: c_int) -> bool { + let o = &*ptr; + o.use_subdomains(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_subdomains( + ptr: *const Profiles, row: c_int, + v: bool, +) -> bool { + (&*ptr).set_use_subdomains(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_undefined_as_protocol_fallback(ptr: *const Profiles, row: c_int) -> bool { + let o = &*ptr; + o.use_undefined_as_protocol_fallback(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_undefined_as_protocol_fallback( + ptr: *const Profiles, row: c_int, + v: bool, +) -> bool { + (&*ptr).set_use_undefined_as_protocol_fallback(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_use_user_info(ptr: *const Profiles, row: c_int) -> bool { + let o = &*ptr; + o.use_user_info(to_usize(row)) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_use_user_info( + ptr: *const Profiles, row: c_int, + v: bool, +) -> bool { + (&*ptr).set_use_user_info(to_usize(row), v) +} + +#[no_mangle] +pub unsafe extern "C" fn profiles_data_username( + ptr: *const Profiles, row: c_int, + d: *mut QString, + set: extern fn(*mut QString, *const c_char, len: c_int), +) { + let o = &*ptr; + o.username(to_usize(row), |data| { + let s: *const c_char = data.as_ptr() as *const c_char; + set(d, s, to_c_int(data.len())); + }) + } + +#[no_mangle] +pub unsafe extern "C" fn profiles_set_data_username( + ptr: *const Profiles, row: c_int, + s: *const c_ushort, len: c_int, +) -> bool { + let o = &*ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_username(to_usize(row), v) +} + +pub struct SettingsQObject {} + +pub struct SettingsEmitter { + qobject: Arc<AtomicPtr<SettingsQObject>>, + clear_generated_password_seconds_changed: extern fn(*mut SettingsQObject), + clear_master_password_seconds_changed: extern fn(*mut SettingsQObject), + hide_generated_password_changed: extern fn(*mut SettingsQObject), +} + +unsafe impl Send for SettingsEmitter {} + +#[cfg_attr(test, automock)] +#[cfg_attr(test,allow(dead_code))] +impl SettingsEmitter { + /// Clone the emitter + pub fn clone(&self) -> Self { + Self { + qobject: self.qobject.clone(), + clear_generated_password_seconds_changed: self.clear_generated_password_seconds_changed, + clear_master_password_seconds_changed: self.clear_master_password_seconds_changed, + hide_generated_password_changed: self.hide_generated_password_changed, + } + } + fn clear(&self) { + let n: *const SettingsQObject = null(); + self.qobject.store(n as *mut SettingsQObject, Ordering::SeqCst); + } + pub fn clear_generated_password_seconds_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.clear_generated_password_seconds_changed)(ptr); + } + } + pub fn clear_master_password_seconds_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.clear_master_password_seconds_changed)(ptr); + } + } + pub fn hide_generated_password_changed(&self) { + let ptr = self.qobject.load(Ordering::SeqCst); + if !ptr.is_null() { + (self.hide_generated_password_changed)(ptr); + } + } +} + +pub trait SettingsTrait { + #[cfg(not(test))] + fn new(emit: SettingsEmitter) -> Self; + #[cfg(not(test))] + fn emit(&self) -> &SettingsEmitter; + #[cfg(test)] + fn new(emit: MockSettingsEmitter) -> Self; + #[cfg(test)] + fn emit(&self) -> &MockSettingsEmitter; + fn clear_generated_password_seconds(&self) -> Option<u32>; + fn set_clear_generated_password_seconds(&self, value: Option<u32>); + fn clear_master_password_seconds(&self) -> Option<u32>; + fn set_clear_master_password_seconds(&self, value: Option<u32>); + fn hide_generated_password(&self) -> bool; + fn set_hide_generated_password(&self, value: bool); +} +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn settings_new( + settings: *mut SettingsQObject, + settings_clear_generated_password_seconds_changed: extern fn(*mut SettingsQObject), + settings_clear_master_password_seconds_changed: extern fn(*mut SettingsQObject), + settings_hide_generated_password_changed: extern fn(*mut SettingsQObject), +) -> *mut Settings { + let settings_emit = SettingsEmitter { + qobject: Arc::new(AtomicPtr::new(settings)), + clear_generated_password_seconds_changed: settings_clear_generated_password_seconds_changed, + clear_master_password_seconds_changed: settings_clear_master_password_seconds_changed, + hide_generated_password_changed: settings_hide_generated_password_changed, + }; + let d_settings = Settings::new(settings_emit); + Box::into_raw(Box::new(d_settings)) +} + +#[no_mangle] +pub unsafe extern "C" fn settings_free(ptr: *mut Settings) { + Box::from_raw(ptr).emit().clear(); +} + +#[no_mangle] +pub unsafe extern "C" fn settings_clear_generated_password_seconds_get(ptr: *const Settings) -> COption<u32> { + match (&*ptr).clear_generated_password_seconds() { + Some(value) => COption { data: value, some: true }, + None => COption { data: u32::default(), some: false} + } +} + +#[no_mangle] +pub unsafe extern "C" fn settings_clear_generated_password_seconds_set(ptr: *const Settings, v: u32) { + (&*ptr).set_clear_generated_password_seconds(Some(v)); +} + +#[no_mangle] +pub unsafe extern "C" fn settings_clear_generated_password_seconds_set_none(ptr: *const Settings) { + let o = &*ptr; + o.set_clear_generated_password_seconds(None); +} + +#[no_mangle] +pub unsafe extern "C" fn settings_clear_master_password_seconds_get(ptr: *const Settings) -> COption<u32> { + match (&*ptr).clear_master_password_seconds() { + Some(value) => COption { data: value, some: true }, + None => COption { data: u32::default(), some: false} + } +} + +#[no_mangle] +pub unsafe extern "C" fn settings_clear_master_password_seconds_set(ptr: *const Settings, v: u32) { + (&*ptr).set_clear_master_password_seconds(Some(v)); +} + +#[no_mangle] +pub unsafe extern "C" fn settings_clear_master_password_seconds_set_none(ptr: *const Settings) { + let o = &*ptr; + o.set_clear_master_password_seconds(None); +} + +#[no_mangle] +pub unsafe extern "C" fn settings_hide_generated_password_get(ptr: *const Settings) -> bool { + (&*ptr).hide_generated_password() +} + +#[no_mangle] +pub unsafe extern "C" fn settings_hide_generated_password_set(ptr: *const Settings, v: bool) { + (&*ptr).set_hide_generated_password(v); +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..abdb917 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,12 @@ +extern crate libc; +extern crate serde; +extern crate dirs; +extern crate mockall_double; + +#[cfg(test)] +extern crate mockall; + +#[allow(clippy::all)] +pub mod interface; +#[warn(clippy::pedantic)] +mod implementation; |
