aboutsummaryrefslogtreecommitdiff
path: root/rust/src
diff options
context:
space:
mode:
authorAndreas Grois <andi@grois.info>2022-10-10 21:30:02 +0200
committerAndreas Grois <andi@grois.info>2022-10-10 21:37:15 +0200
commite4ad766315879e1ff05bb111229f073f8f0ed68e (patch)
tree4b043ff47c78b2c00c80c94ebda622c32c8b6d3d /rust/src
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/src')
-rw-r--r--rust/src/implementation/mod.rs70
-rw-r--r--rust/src/implementation/passwordmaker/emittingsender.rs17
-rw-r--r--rust/src/implementation/passwordmaker/helperthread/hashers/mod.rs31
-rw-r--r--rust/src/implementation/passwordmaker/helperthread/hashers/qt_hash.rs131
-rw-r--r--rust/src/implementation/passwordmaker/helperthread/message_parsing.rs102
-rw-r--r--rust/src/implementation/passwordmaker/helperthread/mod.rs49
-rw-r--r--rust/src/implementation/passwordmaker/helperthread/profile_to_domain.rs46
-rw-r--r--rust/src/implementation/passwordmaker/mod.rs305
-rw-r--r--rust/src/implementation/passwordmaker/settings.rs159
-rw-r--r--rust/src/implementation/passwordmaker/thread_messages.rs41
-rw-r--r--rust/src/implementation/profiles.rs1127
-rw-r--r--rust/src/implementation/pwm_macros.rs27
-rw-r--r--rust/src/interface.rs1062
-rw-r--r--rust/src/lib.rs12
14 files changed, 3179 insertions, 0 deletions
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;