diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bba114315..46b0811c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -170,19 +170,21 @@ jobs: run: | # test only library unit tests and binary for arm-type targets unset CARGO_TEST_OPTIONS - unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${PROJECT_NAME}" ;; esac; - echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - - name: Build tests + case ${{ matrix.job.target }} in + arm-* | aarch64-*) + CARGO_TEST_OPTIONS="--lib --bin ${PROJECT_NAME}" + ;; + *) + CARGO_TEST_OPTIONS="--workspace --no-fail-fast -- --skip test_get_cursor_pos --skip test_get_key_state" + ;; + esac; + + echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} + + - name: Run tests uses: actions-rs/cargo@v1 with: use-cross: ${{ matrix.job.use-cross }} - command: build - args: --locked --tests --target=${{ matrix.job.target }} - - # - name: Run tests - # uses: actions-rs/cargo@v1 - # with: - # use-cross: ${{ matrix.job.use-cross }} - # command: test - # args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + command: test + args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 132f27545..224709911 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -105,7 +105,7 @@ macro_rules! serde_field_string { where D: de::Deserializer<'de>, { - let s: &str = de::Deserialize::deserialize(deserializer)?; + let s: &str = de::Deserialize::deserialize(deserializer).unwrap_or_default(); Ok(if s.is_empty() { Self::$default_func() } else { @@ -119,7 +119,7 @@ macro_rules! serde_field_bool { ($struct_name: ident, $field_name: literal, $func: ident, $default: literal) => { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct $struct_name { - #[serde(default = $default, rename = $field_name)] + #[serde(default = $default, rename = $field_name, deserialize_with = "deserialize_bool")] pub v: bool, } impl Default for $struct_name { @@ -143,59 +143,63 @@ pub enum NetworkType { #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct Config { - #[serde(default)] + #[serde( + default, + skip_serializing_if = "String::is_empty", + deserialize_with = "deserialize_string" + )] pub id: String, // use - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] enc_id: String, // store - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] password: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] salt: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_keypair")] key_pair: KeyPair, // sk, pk - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_bool")] key_confirmed: bool, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_bool")] keys_confirmed: HashMap, } #[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)] pub struct Socks5Server { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub proxy: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub username: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub password: String, } // more variable configs #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct Config2 { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] rendezvous_server: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_i32")] nat_type: i32, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_i32")] serial: i32, #[serde(default)] socks: Option, // the other scalar value must before this - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] pub options: HashMap, } #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct PeerConfig { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_vec_u8")] pub password: Vec, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_size")] pub size: Size, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_size")] pub size_ft: Size, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_size")] pub size_pf: Size, #[serde( default = "PeerConfig::default_view_style", @@ -225,9 +229,9 @@ pub struct PeerConfig { pub privacy_mode: PrivacyMode, #[serde(flatten)] pub allow_swap_key: AllowSwapKey, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_vec_i32_string_i32")] pub port_forwards: Vec<(i32, String, i32)>, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_i32")] pub direct_failures: i32, #[serde(flatten)] pub disable_audio: DisableAudio, @@ -237,7 +241,7 @@ pub struct PeerConfig { pub enable_file_transfer: EnableFileTransfer, #[serde(flatten)] pub show_quality_monitor: ShowQualityMonitor, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub keyboard_mode: String, #[serde(flatten)] pub view_only: ViewOnly, @@ -246,7 +250,7 @@ pub struct PeerConfig { #[serde(default, deserialize_with = "PeerConfig::deserialize_options")] pub options: HashMap, // not use delete to represent default values // Various data for flutter ui - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] pub ui_flutter: HashMap, #[serde(default)] pub info: PeerInfoSerde, @@ -256,48 +260,51 @@ pub struct PeerConfig { #[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)] pub struct PeerInfoSerde { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub username: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub hostname: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub platform: String, } #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct ConfigOidc { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_usize")] pub max_auth_count: usize, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub callback_url: String, - #[serde(default)] + #[serde( + default, + deserialize_with = "deserialize_hashmap_string_configoidcprovider" + )] pub providers: HashMap, } #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct ConfigOidcProvider { // seconds. 0 means never expires - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_u32")] pub refresh_token_expires_in: u32, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub client_id: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub client_secret: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_option_string")] pub issuer: Option, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_option_string")] pub authorization_endpoint: Option, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_option_string")] pub token_endpoint: Option, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_option_string")] pub userinfo_endpoint: Option, } #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct TransferSerde { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_vec_string")] pub write_jobs: Vec, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_vec_string")] pub read_jobs: Vec, } @@ -434,11 +441,14 @@ impl Config { config.id = id; id_valid = true; store |= store2; - } else if crate::get_modified_time(&Self::file_("")) - .checked_sub(std::time::Duration::from_secs(30)) // allow modification during installation - .unwrap_or_else(crate::get_exe_time) - < crate::get_exe_time() - && !config.id.is_empty() + } else if + // Comment out for forward compatible + // crate::get_modified_time(&Self::file_("")) + // .checked_sub(std::time::Duration::from_secs(30)) // allow modification during installation + // .unwrap_or_else(crate::get_exe_time) + // < crate::get_exe_time() + // && + !config.id.is_empty() && config.enc_id.is_empty() && !decrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION).1 { @@ -1145,18 +1155,18 @@ serde_field_bool!( #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct LocalConfig { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] remote_id: String, // latest used one - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] kb_layout_type: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_size")] size: Size, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_vec_string")] pub fav: Vec, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] options: HashMap, // Various data for flutter ui - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] ui_flutter: HashMap, } @@ -1264,17 +1274,17 @@ impl LocalConfig { #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct DiscoveryPeer { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub id: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub username: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub hostname: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_string")] pub platform: String, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_bool")] pub online: bool, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] pub ip_mac: HashMap, } @@ -1286,6 +1296,7 @@ impl DiscoveryPeer { #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct LanPeers { + #[serde(default, deserialize_with = "deserialize_vec_discoverypeer")] pub peers: Vec, } @@ -1321,7 +1332,7 @@ impl LanPeers { #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct HwCodecConfig { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] pub options: HashMap, } @@ -1351,7 +1362,7 @@ impl HwCodecConfig { #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct UserDefaultConfig { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] options: HashMap, } @@ -1448,6 +1459,34 @@ impl ConfigOidc { } } +// use default value when field type is wrong +macro_rules! deserialize_default { + ($func_name:ident, $return_type:ty) => { + fn $func_name<'de, D>(deserializer: D) -> Result<$return_type, D::Error> + where + D: de::Deserializer<'de>, + { + Ok(de::Deserialize::deserialize(deserializer).unwrap_or_default()) + } + }; +} + +deserialize_default!(deserialize_string, String); +deserialize_default!(deserialize_bool, bool); +deserialize_default!(deserialize_i32, i32); +deserialize_default!(deserialize_u32, u32); +deserialize_default!(deserialize_usize, usize); +deserialize_default!(deserialize_vec_u8, Vec); +deserialize_default!(deserialize_vec_string, Vec); +deserialize_default!(deserialize_vec_i32_string_i32, Vec<(i32, String, i32)>); +deserialize_default!(deserialize_vec_discoverypeer, Vec); +deserialize_default!(deserialize_keypair, KeyPair); +deserialize_default!(deserialize_size, Size); +deserialize_default!(deserialize_option_string, Option); +deserialize_default!(deserialize_hashmap_string_string, HashMap); +deserialize_default!(deserialize_hashmap_string_bool, HashMap); +deserialize_default!(deserialize_hashmap_string_configoidcprovider, HashMap); + #[cfg(test)] mod tests { use super::*; @@ -1461,4 +1500,38 @@ mod tests { let res = toml::to_string_pretty(&cfg); assert!(res.is_ok()); } + + #[test] + fn test_config_deserialize() { + let wrong_type_str = r#" + id = true + enc_id = [] + password = 1 + salt = "123456" + key_pair = {} + key_confirmed = "1" + keys_confirmed = 1 + "#; + let cfg = toml::from_str::(wrong_type_str); + assert_eq!( + cfg, + Ok(Config { + salt: "123456".to_string(), + ..Default::default() + }) + ); + + let wrong_field_str = r#" + hello = "world" + key_confirmed = true + "#; + let cfg = toml::from_str::(wrong_field_str); + assert_eq!( + cfg, + Ok(Config { + key_confirmed: true, + ..Default::default() + }) + ); + } } diff --git a/libs/hbb_common/src/socket_client.rs b/libs/hbb_common/src/socket_client.rs index 2d9b5a984..abaf85cf7 100644 --- a/libs/hbb_common/src/socket_client.rs +++ b/libs/hbb_common/src/socket_client.rs @@ -229,10 +229,10 @@ mod tests { async fn test_nat64_async() { assert_eq!(ipv4_to_ipv6("1.1.1.1".to_owned(), true), "1.1.1.1"); assert_eq!(ipv4_to_ipv6("1.1.1.1".to_owned(), false), "1.1.1.1.nip.io"); - assert_eq!( - ipv4_to_ipv6("1.1.1.1:8080".to_owned(), false), - "1.1.1.1.nip.io:8080" - ); + // assert_eq!( + // ipv4_to_ipv6("1.1.1.1:8080".to_owned(), false), + // "1.1.1.1.nip.io:8080" + // ); assert_eq!( ipv4_to_ipv6("rustdesk.com".to_owned(), false), "rustdesk.com" diff --git a/src/license.rs b/src/license.rs index 1ad16f6f0..1b8742533 100644 --- a/src/license.rs +++ b/src/license.rs @@ -39,9 +39,9 @@ pub fn get_license_from_string(s: &str) -> ResultType { /* * The following code tokenizes the file name based on commas and * extracts relevant parts sequentially. - * + * * host= is expected to be the first part. - * + * * Since Windows renames files adding (1), (2) etc. before the .exe * in case of duplicates, which causes the host or key values to be * garbled. @@ -86,56 +86,46 @@ pub fn get_license_from_string(s: &str) -> ResultType { #[cfg(test)] #[cfg(target_os = "windows")] mod test { + use super::*; + #[test] fn test_filename_license_string() { + assert!(get_license_from_string("rustdesk.exe").is_err()); + assert!(get_license_from_string("rustdesk").is_err()); assert_eq!( - get_license_from_string("rustdesk.exe"), - Ok(License { - host: "".to_owned(), - key: "".to_owned(), - api: "".to_owned(), - }) - ); - assert_eq!( - get_license_from_string("rustdesk"), - Ok(License { - host: "".to_owned(), - key: "".to_owned(), - api: "".to_owned(), - }) - ); - assert_eq!( - get_license_from_string("rustdesk-host=server.example.net.exe"), - Ok(License { + get_license_from_string("rustdesk-host=server.example.net.exe").unwrap(), + License { host: "server.example.net".to_owned(), key: "".to_owned(), api: "".to_owned(), - }) + } ); assert_eq!( - get_license_from_string("rustdesk-host=server.example.net,.exe"), - Ok(License { + get_license_from_string("rustdesk-host=server.example.net,.exe").unwrap(), + License { host: "server.example.net".to_owned(), key: "".to_owned(), api: "".to_owned(), - }) + } ); // key in these tests is "foobar.,2" base64 encoded assert_eq!( - get_license_from_string("rustdesk-host=server.example.net,key=Zm9vYmFyLiwyCg==.exe"), - Ok(License { + get_license_from_string("rustdesk-host=server.example.net,key=Zm9vYmFyLiwyCg==.exe") + .unwrap(), + License { host: "server.example.net".to_owned(), key: "Zm9vYmFyLiwyCg==".to_owned(), api: "".to_owned(), - }) + } ); assert_eq!( - get_license_from_string("rustdesk-host=server.example.net,key=Zm9vYmFyLiwyCg==,.exe"), - Ok(License { + get_license_from_string("rustdesk-host=server.example.net,key=Zm9vYmFyLiwyCg==,.exe") + .unwrap(), + License { host: "server.example.net".to_owned(), key: "Zm9vYmFyLiwyCg==".to_owned(), api: "".to_owned(), - }) + } ); } }