From 7e6ddf06a988d31beaf80dc6ea12ad70068b38cb Mon Sep 17 00:00:00 2001 From: Ronan Date: Tue, 9 Apr 2024 14:55:28 +0200 Subject: [PATCH 1/4] Add KeyExchange and Encrypted tcpstream --- Cargo.lock | 980 +++++++++++++---- libs/hbb_common/.gitignore | 1 - libs/hbb_common/Cargo.toml | 35 +- libs/hbb_common/protos/message.proto | 174 ++- libs/hbb_common/protos/rendezvous.proto | 12 + libs/hbb_common/src/compress.rs | 50 +- libs/hbb_common/src/config.rs | 1239 ++++++++++++++++++---- libs/hbb_common/src/fs.rs | 105 +- libs/hbb_common/src/lib.rs | 139 ++- libs/hbb_common/src/password_security.rs | 85 +- libs/hbb_common/src/platform/linux.rs | 285 +++++ libs/hbb_common/src/platform/macos.rs | 55 + libs/hbb_common/src/platform/mod.rs | 81 ++ libs/hbb_common/src/platform/windows.rs | 199 ++++ libs/hbb_common/src/tcp.rs | 96 +- libs/hbb_common/src/udp.rs | 6 +- src/rendezvous_server.rs | 167 ++- 17 files changed, 3183 insertions(+), 526 deletions(-) create mode 100644 libs/hbb_common/src/platform/linux.rs create mode 100644 libs/hbb_common/src/platform/macos.rs create mode 100644 libs/hbb_common/src/platform/mod.rs create mode 100644 libs/hbb_common/src/platform/windows.rs diff --git a/Cargo.lock b/Cargo.lock index f62e537c..2b66bba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -15,13 +30,28 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -60,9 +90,9 @@ version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", ] [[package]] @@ -80,7 +110,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -137,6 +167,21 @@ dependencies = [ "mime", ] +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.0" @@ -155,6 +200,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bindgen" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger 0.9.0", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2 1.0.79", + "quote 1.0.35", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -194,20 +262,30 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" dependencies = [ "jobserver", + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.1", ] [[package]] @@ -218,15 +296,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ - "libc", - "num-integer", + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", - "time 0.1.43", - "winapi", + "wasm-bindgen", + "windows-targets 0.52.4", ] [[package]] @@ -239,6 +318,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.34.0" @@ -273,7 +363,7 @@ dependencies = [ "directories-next", "serde", "thiserror", - "toml", + "toml 0.5.9", ] [[package]] @@ -449,6 +539,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlopen" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" +dependencies = [ + "dlopen_derive", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "dlopen_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" +dependencies = [ + "libc", + "quote 0.6.13", + "syn 0.15.44", +] + [[package]] name = "dlv-list" version = "0.3.0" @@ -501,6 +614,25 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "event-listener" version = "2.5.2" @@ -544,7 +676,25 @@ dependencies = [ "regex", "rustversion", "thiserror", - "time 0.3.9", + "time", +] + +[[package]] +name = "flexi_logger" +version = "0.27.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469e584c031833564840fb0cdbce99bdfe946fd45480a188545e73a76f45461c" +dependencies = [ + "chrono", + "crossbeam-channel", + "crossbeam-queue", + "glob", + "is-terminal", + "lazy_static", + "log", + "nu-ansi-term", + "regex", + "thiserror", ] [[package]] @@ -640,9 +790,9 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", ] [[package]] @@ -681,15 +831,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.5" @@ -711,6 +852,12 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "glob" version = "0.3.0" @@ -732,6 +879,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "hashlink" version = "0.8.0" @@ -746,19 +899,24 @@ name = "hbb_common" version = "0.1.0" dependencies = [ "anyhow", + "backtrace", "bytes", "chrono", "confy", "directories-next", "dirs-next", - "env_logger", + "dlopen", + "env_logger 0.10.2", "filetime", + "flexi_logger 0.27.4", "futures", "futures-util", "lazy_static", + "libc", "log", "mac_address", - "machine-uid", + "machine-uid 0.3.0", + "osascript", "protobuf", "protobuf-codegen", "quinn", @@ -769,10 +927,12 @@ dependencies = [ "serde_json", "socket2 0.3.19", "sodiumoxide", + "sysinfo", "tokio", "tokio-socks", "tokio-util 0.7.1", - "toml", + "toml 0.7.8", + "uuid", "winapi", "zstd", ] @@ -790,7 +950,7 @@ dependencies = [ "clap", "deadpool", "dns-lookup", - "flexi_logger", + "flexi_logger 0.22.3", "hbb_common", "headers", "http", @@ -799,7 +959,7 @@ dependencies = [ "lazy_static", "local-ip-address", "mac_address", - "machine-uid", + "machine-uid 0.2.0", "minreq", "once_cell", "ping", @@ -860,6 +1020,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -935,6 +1101,29 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.2.3" @@ -956,6 +1145,16 @@ dependencies = [ "hashbrown 0.11.2", ] +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + [[package]] name = "inout" version = "0.1.3" @@ -983,6 +1182,17 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "itertools" version = "0.10.3" @@ -1036,6 +1246,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lexical-core" version = "0.7.6" @@ -1051,9 +1267,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.125" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] [[package]] name = "libsodium-sys" @@ -1125,7 +1351,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f1595709b0a7386bcd56ba34d250d626e5503917d05d32cdccddcd68603e212" dependencies = [ - "winreg", + "winreg 0.6.2", +] + +[[package]] +name = "machine-uid" +version = "0.3.0" +source = "git+https://github.com/21pages/machine-uid#381ff579c1dc3a6c54db9dfec47c44bcb0246542" +dependencies = [ + "bindgen", + "cc", + "winreg 0.11.0", ] [[package]] @@ -1178,47 +1414,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "minreq" -version = "2.6.0" +name = "miniz_oxide" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c785bc6027fd359756e538541c8624012ba3776d3d3fe123885643092ed4132" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ - "log", - "punycode", + "adler", ] [[package]] -name = "mio" -version = "0.7.14" +name = "minreq" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "4c785bc6027fd359756e538541c8624012ba3776d3d3fe123885643092ed4132" dependencies = [ - "libc", "log", - "miow", - "ntapi", - "winapi", + "punycode", ] [[package]] name = "mio" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1240,10 +1462,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" dependencies = [ "either", - "proc-macro2", - "quote", + "proc-macro2 1.0.79", + "quote 1.0.35", "serde", - "syn", + "syn 1.0.93", ] [[package]] @@ -1282,13 +1504,22 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1325,7 +1556,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -1338,11 +1569,20 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.10.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl-probe" @@ -1360,6 +1600,17 @@ dependencies = [ "hashbrown 0.12.1", ] +[[package]] +name = "osascript" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38731fa859ef679f1aec66ca9562165926b442f298467f76f5990f431efe87dc" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -1414,6 +1665,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pem" version = "1.0.2" @@ -1444,16 +1701,16 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1486,18 +1743,27 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.38" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "protobuf" -version = "3.1.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee4a7d8b91800c8f167a6268d1a1026607368e1adc84e98fe044aeb905302f7" +checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0" dependencies = [ "bytes", "once_cell", @@ -1507,9 +1773,9 @@ dependencies = [ [[package]] name = "protobuf-codegen" -version = "3.1.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b893e5e7d3395545d5244f8c0d33674025bd566b26c03bfda49b82c6dec45e" +checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77" dependencies = [ "anyhow", "once_cell", @@ -1522,12 +1788,12 @@ dependencies = [ [[package]] name = "protobuf-parse" -version = "3.1.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1447dd751c434cc1b415579837ebd0411ed7d67d465f38010da5d7cd33af4d" +checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642" dependencies = [ "anyhow", - "indexmap", + "indexmap 1.8.1", "log", "protobuf", "protobuf-support", @@ -1538,9 +1804,9 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.1.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca157fe12fc7ee2e315f2f735e27df41b3d97cdd70ea112824dac1ffb08ee1c" +checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7" dependencies = [ "thiserror", ] @@ -1562,16 +1828,15 @@ dependencies = [ [[package]] name = "quinn" -version = "0.8.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d147472bc9a09f13b06c044787b6683cdffa02e2865b7f0fb53d67c49ed2988e" +checksum = "2e8b432585672228923edbbf64b8b12c14e1112f62e88737655b4a083dbcd78e" dependencies = [ "bytes", - "futures-channel", - "futures-util", - "fxhash", + "pin-project-lite", "quinn-proto", "quinn-udp", + "rustc-hash", "rustls", "thiserror", "tokio", @@ -1581,17 +1846,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.8.2" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "359c5eb33845f3ee05c229e65f87cdbc503eea394964b8f1330833d460b4ff3e" +checksum = "94b0b33c13a79f669c85defaf4c275dc86a0c0372807d0ca3d78e0bb87274863" dependencies = [ "bytes", - "fxhash", "rand", "ring", + "rustc-hash", "rustls", "rustls-native-certs", - "rustls-pemfile 0.2.1", "slab", "thiserror", "tinyvec", @@ -1601,26 +1865,33 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.1.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df185e5e5f7611fa6e628ed8f9633df10114b03bbaecab186ec55822c44ac727" +checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4" dependencies = [ - "futures-util", "libc", - "mio 0.7.14", "quinn-proto", "socket2 0.4.4", - "tokio", "tracing", + "windows-sys 0.42.0", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.79", ] [[package]] @@ -1653,6 +1924,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1675,9 +1966,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -1686,9 +1989,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "remove_dir_all" @@ -1724,6 +2027,18 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustls" version = "0.20.4" @@ -1743,20 +2058,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.0", + "rustls-pemfile", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" -dependencies = [ - "base64", -] - [[package]] name = "rustls-pemfile" version = "1.0.0" @@ -1838,22 +2144,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.58", ] [[package]] @@ -1867,6 +2173,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1901,6 +2216,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1925,7 +2246,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.9", + "time", ] [[package]] @@ -1961,6 +2282,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "sodiumoxide" version = "0.2.7" @@ -2033,7 +2364,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap", + "indexmap 1.8.1", "itoa", "libc", "libsqlite3-sys", @@ -2043,7 +2374,7 @@ dependencies = [ "paste", "percent-encoding", "rustls", - "rustls-pemfile 1.0.0", + "rustls-pemfile", "serde", "serde_json", "sha2", @@ -2067,13 +2398,13 @@ dependencies = [ "either", "heck", "once_cell", - "proc-macro2", - "quote", + "proc-macro2 1.0.79", + "quote 1.0.35", "serde_json", "sha2", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.93", "url", ] @@ -2110,15 +2441,37 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + [[package]] name = "syn" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "proc-macro2 1.0.79", + "quote 1.0.35", + "unicode-xid 0.2.3", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2 1.0.79", + "quote 1.0.35", + "unicode-ident", ] [[package]] @@ -2127,6 +2480,20 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +[[package]] +name = "sysinfo" +version = "0.29.10" +source = "git+https://github.com/rustdesk-org/sysinfo#f45dcc6510d48c3a1401c5a33eedccc8899f67b2" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -2174,19 +2541,9 @@ version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", ] [[package]] @@ -2225,34 +2582,32 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", - "mio 0.8.3", + "mio", "num_cpus", - "once_cell", "parking_lot 0.12.0", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.4", + "socket2 0.5.6", "tokio-macros", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.58", ] [[package]] @@ -2345,6 +2700,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.12" @@ -2419,9 +2808,9 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", ] [[package]] @@ -2479,6 +2868,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -2500,6 +2895,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unicode-xid" version = "0.2.3" @@ -2538,9 +2939,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.1.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] @@ -2615,9 +3016,9 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", "wasm-bindgen-shared", ] @@ -2627,7 +3028,7 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ - "quote", + "quote 1.0.35", "wasm-bindgen-macro-support", ] @@ -2637,9 +3038,9 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 1.0.93", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2731,6 +3132,34 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core 0.51.1", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.36.1" @@ -2744,35 +3173,86 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -2782,9 +3262,21 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -2794,9 +3286,21 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -2806,9 +3310,21 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -2818,15 +3334,39 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -2836,9 +3376,30 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -2849,6 +3410,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "zeroize" version = "1.5.5" @@ -2857,29 +3428,28 @@ checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" [[package]] name = "zstd" -version = "0.9.2+zstd.1.5.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ - "libc", "zstd-sys", ] [[package]] name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", - "libc", + "pkg-config", ] diff --git a/libs/hbb_common/.gitignore b/libs/hbb_common/.gitignore index b1cf151e..69369904 100644 --- a/libs/hbb_common/.gitignore +++ b/libs/hbb_common/.gitignore @@ -1,4 +1,3 @@ /target **/*.rs.bk Cargo.lock -src/protos/ diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index 59f0896c..01274c4f 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -7,45 +7,54 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -protobuf = { version = "3.1", features = ["with-bytes"] } -tokio = { version = "1.20", features = ["full"] } +flexi_logger = { version = "0.27", features = ["async"] } +protobuf = { version = "3.3", features = ["with-bytes"] } +tokio = { version = "1.36", features = ["full"] } tokio-util = { version = "0.7", features = ["full"] } futures = "0.3" -bytes = { version = "1.2", features = ["serde"] } +bytes = { version = "1.4", features = ["serde"] } log = "0.4" -env_logger = "0.9" +env_logger = "0.10" socket2 = { version = "0.3", features = ["reuseport"] } -zstd = "0.9" -quinn = {version = "0.8", optional = true } +zstd = "0.13" +quinn = {version = "0.9", optional = true } anyhow = "1.0" futures-util = "0.3" directories-next = "2.0" rand = "0.8" serde_derive = "1.0" serde = "1.0" +serde_json = "1.0" lazy_static = "1.4" confy = { git = "https://github.com/open-trade/confy" } dirs-next = "2.0" filetime = "0.2" sodiumoxide = "0.2" -regex = "1.4" +regex = "1.8" tokio-socks = { git = "https://github.com/open-trade/tokio-socks" } chrono = "0.4" +backtrace = "0.3" +libc = "0.2" +dlopen = "0.1" +toml = "0.7" +uuid = { version = "1.3", features = ["v4"] } +# crash, versions >= 0.29.1 are affected by #GuillaumeGomez/sysinfo/1052 +sysinfo = { git = "https://github.com/rustdesk-org/sysinfo" } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] mac_address = "1.1" -machine-uid = "0.2" +machine-uid = { git = "https://github.com/21pages/machine-uid" } [features] quic = [] flatpak = [] [build-dependencies] -protobuf-codegen = { version = "3.1" } +protobuf-codegen = { version = "3.3" } [target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "0.3", features = ["winuser"] } +winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi"] } + +[target.'cfg(target_os = "macos")'.dependencies] +osascript = "0.3" -[dev-dependencies] -toml = "0.5" -serde_json = "1.0" diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index ed270638..b2001ba6 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -17,6 +17,11 @@ message YUV { int32 stride = 2; } +enum Chroma { + I420 = 0; + I444 = 1; +} + message VideoFrame { oneof union { EncodedVideoFrames vp9s = 6; @@ -24,8 +29,10 @@ message VideoFrame { YUV yuv = 8; EncodedVideoFrames h264s = 10; EncodedVideoFrames h265s = 11; + EncodedVideoFrames vp8s = 12; + EncodedVideoFrames av1s = 13; } - int64 timestamp = 9; + int32 display = 14; } message IdPk { @@ -41,6 +48,8 @@ message DisplayInfo { string name = 5; bool online = 6; bool cursor_embedded = 7; + Resolution original_resolution = 8; + double scale = 9; } message PortForward { @@ -53,6 +62,11 @@ message FileTransfer { bool show_hidden = 2; } +message OSLogin { + string username = 1; + string password = 2; +} + message LoginRequest { string username = 1; bytes password = 2; @@ -66,6 +80,11 @@ message LoginRequest { bool video_ack_required = 9; uint64 session_id = 10; string version = 11; + OSLogin os_login = 12; +} + +message Auth2FA { + string code = 1; } message ChatMessage { string text = 1; } @@ -74,9 +93,20 @@ message Features { bool privacy_mode = 1; } +message CodecAbility { + bool vp8 = 1; + bool vp9 = 2; + bool av1 = 3; + bool h264 = 4; + bool h265 = 5; +} + message SupportedEncoding { bool h264 = 1; bool h265 = 2; + bool vp8 = 3; + bool av1 = 4; + CodecAbility i444 = 5; } message PeerInfo { @@ -87,11 +117,20 @@ message PeerInfo { int32 current_display = 5; bool sas_enabled = 6; string version = 7; - int32 conn_id = 8; Features features = 9; SupportedEncoding encoding = 10; + SupportedResolutions resolutions = 11; + // Use JSON's key-value format which is friendly for peer to handle. + // NOTE: Only support one-level dictionaries (for peer to update), and the key is of type string. + string platform_additions = 12; + WindowsSessions windows_sessions = 13; } +message WindowsSession { + uint32 sid = 1; + string name = 2; +} + message LoginResponse { oneof union { string error = 1; @@ -99,6 +138,46 @@ message LoginResponse { } } +message TouchScaleUpdate { + // The delta scale factor relative to the previous scale. + // delta * 1000 + // 0 means scale end + int32 scale = 1; +} + +message TouchPanStart { + int32 x = 1; + int32 y = 2; +} + +message TouchPanUpdate { + // The delta x position relative to the previous position. + int32 x = 1; + // The delta y position relative to the previous position. + int32 y = 2; +} + +message TouchPanEnd { + int32 x = 1; + int32 y = 2; +} + +message TouchEvent { + oneof union { + TouchScaleUpdate scale_update = 1; + TouchPanStart pan_start = 2; + TouchPanUpdate pan_update = 3; + TouchPanEnd pan_end = 4; + } +} + +message PointerDeviceEvent { + oneof union { + TouchEvent touch_event = 1; + } + repeated ControlKey modifiers = 2; +} + message MouseEvent { int32 mask = 1; sint32 x = 2; @@ -201,9 +280,13 @@ message KeyEvent { bool press = 2; oneof union { ControlKey control_key = 3; + // position key code. win: scancode, linux: key code, macos: key code uint32 chr = 4; uint32 unicode = 5; string seq = 6; + // high word. virtual keycode + // low word. unicode + uint32 win2win_hotkey = 7; } repeated ControlKey modifiers = 8; KeyboardMode mode = 9; @@ -298,6 +381,7 @@ message FileTransferDigest { uint64 last_modified = 3; uint64 file_size = 4; bool is_upload = 5; + bool is_identical = 6; } message FileTransferBlock { @@ -340,6 +424,7 @@ message FileTransferReceiveRequest { string path = 2; // path written to repeated FileEntry files = 3; int32 file_num = 4; + uint64 total_size = 5; } message FileRemoveDir { @@ -414,6 +499,13 @@ message Cliprdr { } } +message Resolution { + int32 width = 1; + int32 height = 2; +} + +message SupportedResolutions { repeated Resolution resolutions = 1; } + message SwitchDisplay { int32 display = 1; sint32 x = 2; @@ -421,6 +513,25 @@ message SwitchDisplay { int32 width = 4; int32 height = 5; bool cursor_embedded = 6; + SupportedResolutions resolutions = 7; + // Do not care about the origin point for now. + Resolution original_resolution = 8; +} + +message CaptureDisplays { + repeated int32 add = 1; + repeated int32 sub = 2; + repeated int32 set = 3; +} + +message ToggleVirtualDisplay { + int32 display = 1; + bool on = 2; +} + +message TogglePrivacyMode { + string impl_key = 1; + bool on = 2; } message PermissionInfo { @@ -431,6 +542,7 @@ message PermissionInfo { File = 4; Restart = 5; Recording = 6; + BlockInput = 7; } Permission permission = 1; @@ -444,18 +556,24 @@ enum ImageQuality { Best = 4; } -message VideoCodecState { +message SupportedDecoding { enum PreferCodec { Auto = 0; - VPX = 1; + VP9 = 1; H264 = 2; H265 = 3; + VP8 = 4; + AV1 = 5; } - int32 score_vpx = 1; - int32 score_h264 = 2; - int32 score_h265 = 3; + int32 ability_vp9 = 1; + int32 ability_h264 = 2; + int32 ability_h265 = 3; PreferCodec prefer = 4; + int32 ability_vp8 = 5; + int32 ability_av1 = 6; + CodecAbility i444 = 7; + Chroma prefer_chroma = 8; } message OptionMessage { @@ -473,8 +591,12 @@ message OptionMessage { BoolOption disable_audio = 7; BoolOption disable_clipboard = 8; BoolOption enable_file_transfer = 9; - VideoCodecState video_codec_state = 10; + SupportedDecoding supported_decoding = 10; int32 custom_fps = 11; + BoolOption disable_keyboard = 12; +// Position 13 is used for Resolution. Remove later. +// Resolution custom_resolution = 13; + BoolOption support_windows_specific_session = 14; } message TestDelay { @@ -498,7 +620,6 @@ message AudioFormat { message AudioFrame { bytes data = 1; - int64 timestamp = 2; } // Notify peer to show message box. @@ -550,6 +671,10 @@ message BackNotification { PrivacyModeState privacy_mode_state = 1; BlockInputState block_input_state = 2; } + // Supplementary message, for "PrvOnFailed" and "PrvOffFailed" + string details = 3; + // The key of the implementation + string impl_key = 4; } message ElevationRequestWithLogon { @@ -575,6 +700,22 @@ message SwitchSidesResponse { message SwitchBack {} +message PluginRequest { + string id = 1; + bytes content = 2; +} + +message PluginFailure { + string id = 1; + string name = 2; + string msg = 3; +} + +message WindowsSessions { + repeated WindowsSession sessions = 1; + uint32 current_sid = 2; +} + message Misc { oneof union { ChatMessage chat_message = 4; @@ -595,6 +736,18 @@ message Misc { bool portable_service_running = 20; SwitchSidesRequest switch_sides_request = 21; SwitchBack switch_back = 22; + Resolution change_resolution = 24; + PluginRequest plugin_request = 25; + PluginFailure plugin_failure = 26; + uint32 full_speed_fps = 27; // deprecated + uint32 auto_adjust_fps = 28; + bool client_record_status = 29; + CaptureDisplays capture_displays = 30; + int32 refresh_video_display = 31; + ToggleVirtualDisplay toggle_virtual_display = 32; + TogglePrivacyMode toggle_privacy_mode = 33; + SupportedEncoding supported_encoding = 34; + uint32 selected_sid = 35; } } @@ -634,5 +787,8 @@ message Message { SwitchSidesResponse switch_sides_response = 22; VoiceCallRequest voice_call_request = 23; VoiceCallResponse voice_call_response = 24; + PeerInfo peer_info = 25; + PointerDeviceEvent pointer_device_event = 26; + Auth2FA auth_2fa = 27; } } diff --git a/libs/hbb_common/protos/rendezvous.proto b/libs/hbb_common/protos/rendezvous.proto index 1ac60f3f..fac9aa43 100644 --- a/libs/hbb_common/protos/rendezvous.proto +++ b/libs/hbb_common/protos/rendezvous.proto @@ -27,6 +27,7 @@ message PunchHole { bytes socket_addr = 1; string relay_server = 2; NatType nat_type = 3; + string request_region = 4; } message TestNatRequest { @@ -51,6 +52,7 @@ message PunchHoleSent { string relay_server = 3; NatType nat_type = 4; string version = 5; + string request_region = 6; } message RegisterPk { @@ -71,6 +73,7 @@ message RegisterPkResponse { SERVER_ERROR = 7; } Result result = 1; + int32 keep_alive = 2; } message PunchHoleResponse { @@ -105,6 +108,7 @@ message RequestRelay { string licence_key = 6; ConnType conn_type = 7; string token = 8; + string request_region = 9; } message RelayResponse { @@ -117,6 +121,7 @@ message RelayResponse { } string refuse_reason = 6; string version = 7; + string request_region = 8; } message SoftwareUpdate { string url = 1; } @@ -128,6 +133,7 @@ message SoftwareUpdate { string url = 1; } message FetchLocalAddr { bytes socket_addr = 1; string relay_server = 2; + string request_region = 3; } message LocalAddr { @@ -136,6 +142,7 @@ message LocalAddr { string relay_server = 3; string id = 4; string version = 5; + string request_region = 6; } message PeerDiscovery { @@ -157,6 +164,10 @@ message OnlineResponse { bytes states = 1; } +message KeyExchange { + repeated bytes keys = 1; +} + message RendezvousMessage { oneof union { RegisterPeer register_peer = 6; @@ -178,5 +189,6 @@ message RendezvousMessage { PeerDiscovery peer_discovery = 22; OnlineRequest online_request = 23; OnlineResponse online_response = 24; + KeyExchange key_exchange = 25; } } diff --git a/libs/hbb_common/src/compress.rs b/libs/hbb_common/src/compress.rs index e7668a94..c52dd93a 100644 --- a/libs/hbb_common/src/compress.rs +++ b/libs/hbb_common/src/compress.rs @@ -1,23 +1,28 @@ -use std::cell::RefCell; -use zstd::block::{Compressor, Decompressor}; +use std::{cell::RefCell, io}; +use zstd::bulk::{Compressor, Decompressor}; +// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(), +// which is currently 22. Levels >= 20 +// Default level is ZSTD_CLEVEL_DEFAULT==3. +// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT thread_local! { - static COMPRESSOR: RefCell = RefCell::new(Compressor::new()); - static DECOMPRESSOR: RefCell = RefCell::new(Decompressor::new()); + static COMPRESSOR: RefCell>> = RefCell::new(Compressor::new(crate::config::COMPRESS_LEVEL)); + static DECOMPRESSOR: RefCell>> = RefCell::new(Decompressor::new()); } -/// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(), -/// which is currently 22. Levels >= 20 -/// Default level is ZSTD_CLEVEL_DEFAULT==3. -/// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT -pub fn compress(data: &[u8], level: i32) -> Vec { +pub fn compress(data: &[u8]) -> Vec { let mut out = Vec::new(); COMPRESSOR.with(|c| { if let Ok(mut c) = c.try_borrow_mut() { - match c.compress(data, level) { - Ok(res) => out = res, + match &mut *c { + Ok(c) => match c.compress(data) { + Ok(res) => out = res, + Err(err) => { + crate::log::debug!("Failed to compress: {}", err); + } + }, Err(err) => { - crate::log::debug!("Failed to compress: {}", err); + crate::log::debug!("Failed to get compressor: {}", err); } } } @@ -29,14 +34,21 @@ pub fn decompress(data: &[u8]) -> Vec { let mut out = Vec::new(); DECOMPRESSOR.with(|d| { if let Ok(mut d) = d.try_borrow_mut() { - const MAX: usize = 1024 * 1024 * 64; - const MIN: usize = 1024 * 1024; - let mut n = 30 * data.len(); - n = n.clamp(MIN, MAX); - match d.decompress(data, n) { - Ok(res) => out = res, + match &mut *d { + Ok(d) => { + const MAX: usize = 1024 * 1024 * 64; + const MIN: usize = 1024 * 1024; + let mut n = 30 * data.len(); + n = n.clamp(MIN, MAX); + match d.decompress(data, n) { + Ok(res) => out = res, + Err(err) => { + crate::log::debug!("Failed to decompress: {}", err); + } + } + } Err(err) => { - crate::log::debug!("Failed to decompress: {}", err); + crate::log::debug!("Failed to get decompressor: {}", err); } } } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 099a4540..acf30452 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1,10 +1,12 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, fs, + io::{Read, Write}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + ops::{Deref, DerefMut}, path::{Path, PathBuf}, - sync::{Arc, Mutex, RwLock}, - time::SystemTime, + sync::{Mutex, RwLock}, + time::{Duration, Instant, SystemTime}, }; use anyhow::Result; @@ -12,61 +14,85 @@ use rand::Rng; use regex::Regex; use serde as de; use serde_derive::{Deserialize, Serialize}; +use serde_json; use sodiumoxide::base64; use sodiumoxide::crypto::sign; use crate::{ + compress::{compress, decompress}, log, password_security::{ decrypt_str_or_original, decrypt_vec_or_original, encrypt_str_or_original, - encrypt_vec_or_original, + encrypt_vec_or_original, symmetric_crypt, }, }; pub const RENDEZVOUS_TIMEOUT: u64 = 12_000; pub const CONNECT_TIMEOUT: u64 = 18_000; -pub const READ_TIMEOUT: u64 = 30_000; -pub const REG_INTERVAL: i64 = 12_000; +pub const READ_TIMEOUT: u64 = 18_000; +// https://github.com/quic-go/quic-go/issues/525#issuecomment-294531351 +// https://datatracker.ietf.org/doc/html/draft-hamilton-early-deployment-quic-00#section-6.10 +// 15 seconds is recommended by quic, though oneSIP recommend 25 seconds, +// https://www.onsip.com/voip-resources/voip-fundamentals/what-is-nat-keepalive +pub const REG_INTERVAL: i64 = 15_000; pub const COMPRESS_LEVEL: i32 = 3; const SERIAL: i32 = 3; const PASSWORD_ENC_VERSION: &str = "00"; +const ENCRYPT_MAX_LEN: usize = 128; + +// config2 options +#[cfg(target_os = "linux")] +pub const CONFIG_OPTION_ALLOW_LINUX_HEADLESS: &str = "allow-linux-headless"; #[cfg(target_os = "macos")] lazy_static::lazy_static! { - pub static ref ORG: Arc> = Arc::new(RwLock::new("com.carriez".to_owned())); + pub static ref ORG: RwLock = RwLock::new("com.carriez".to_owned()); } type Size = (i32, i32, i32, i32); type KeyPair = (Vec, Vec); lazy_static::lazy_static! { - static ref CONFIG: Arc> = Arc::new(RwLock::new(Config::load())); - static ref CONFIG2: Arc> = Arc::new(RwLock::new(Config2::load())); - static ref LOCAL_CONFIG: Arc> = Arc::new(RwLock::new(LocalConfig::load())); - pub static ref ONLINE: Arc>> = Default::default(); - pub static ref PROD_RENDEZVOUS_SERVER: Arc> = Arc::new(RwLock::new(match option_env!("RENDEZVOUS_SERVER") { + static ref CONFIG: RwLock = RwLock::new(Config::load()); + static ref CONFIG2: RwLock = RwLock::new(Config2::load()); + static ref LOCAL_CONFIG: RwLock = RwLock::new(LocalConfig::load()); + static ref ONLINE: Mutex> = Default::default(); + pub static ref PROD_RENDEZVOUS_SERVER: RwLock = RwLock::new(match option_env!("RENDEZVOUS_SERVER") { Some(key) if !key.is_empty() => key, _ => "", - }.to_owned())); - pub static ref APP_NAME: Arc> = Arc::new(RwLock::new("RustDesk".to_owned())); - static ref KEY_PAIR: Arc>> = Default::default(); - static ref HW_CODEC_CONFIG: Arc> = Arc::new(RwLock::new(HwCodecConfig::load())); + }.to_owned()); + pub static ref EXE_RENDEZVOUS_SERVER: RwLock = Default::default(); + pub static ref APP_NAME: RwLock = RwLock::new("RustDesk".to_owned()); + static ref KEY_PAIR: Mutex> = Default::default(); + static ref USER_DEFAULT_CONFIG: RwLock<(UserDefaultConfig, Instant)> = RwLock::new((UserDefaultConfig::load(), Instant::now())); + pub static ref NEW_STORED_PEER_CONFIG: Mutex> = Default::default(); + pub static ref DEFAULT_SETTINGS: RwLock> = Default::default(); + pub static ref OVERWRITE_SETTINGS: RwLock> = Default::default(); + pub static ref DEFAULT_DISPLAY_SETTINGS: RwLock> = Default::default(); + pub static ref OVERWRITE_DISPLAY_SETTINGS: RwLock> = Default::default(); + pub static ref DEFAULT_LOCAL_SETTINGS: RwLock> = Default::default(); + pub static ref OVERWRITE_LOCAL_SETTINGS: RwLock> = Default::default(); + pub static ref HARD_SETTINGS: RwLock> = Default::default(); } lazy_static::lazy_static! { - pub static ref APP_DIR: Arc> = Default::default(); + pub static ref APP_DIR: RwLock = Default::default(); } #[cfg(any(target_os = "android", target_os = "ios"))] lazy_static::lazy_static! { - pub static ref APP_HOME_DIR: Arc> = Default::default(); + pub static ref APP_HOME_DIR: RwLock = Default::default(); } -// #[cfg(any(target_os = "android", target_os = "ios"))] +pub const LINK_DOCS_HOME: &str = "https://rustdesk.com/docs/en/"; +pub const LINK_DOCS_X11_REQUIRED: &str = "https://rustdesk.com/docs/en/manual/linux/#x11-required"; +pub const LINK_HEADLESS_LINUX_SUPPORT: &str = + "https://github.com/rustdesk/rustdesk/wiki/Headless-Linux-Support"; lazy_static::lazy_static! { pub static ref HELPER_URL: HashMap<&'static str, &'static str> = HashMap::from([ - ("rustdesk docs home", "https://rustdesk.com/docs/en/"), - ("rustdesk docs x11-required", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), + ("rustdesk docs home", LINK_DOCS_HOME), + ("rustdesk docs x11-required", LINK_DOCS_X11_REQUIRED), + ("rustdesk x11 headless", LINK_HEADLESS_LINUX_SUPPORT), ]); } @@ -75,15 +101,12 @@ const CHARS: &[char] = &[ 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ]; -pub const RENDEZVOUS_SERVERS: &[&str] = &[ - "rs-ny.rustdesk.com", - "rs-sg.rustdesk.com", - "rs-cn.rustdesk.com", -]; +pub const RENDEZVOUS_SERVERS: &[&str] = &["rs-ny.rustdesk.com"]; +pub const PUBLIC_RS_PUB_KEY: &str = "OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw="; pub const RS_PUB_KEY: &str = match option_env!("RS_PUB_KEY") { Some(key) if !key.is_empty() => key, - _ => "OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=", + _ => PUBLIC_RS_PUB_KEY, }; pub const RENDEZVOUS_PORT: i32 = 21116; @@ -99,21 +122,21 @@ macro_rules! serde_field_string { where D: de::Deserializer<'de>, { - let s: &str = de::Deserialize::deserialize(deserializer)?; - Ok(if s.is_empty() { - Self::$default_func() - } else { - s.to_owned() - }) + let s: String = + de::Deserialize::deserialize(deserializer).unwrap_or(Self::$default_func()); + if s.is_empty() { + return Ok(Self::$default_func()); + } + Ok(s) } }; } macro_rules! serde_field_bool { - ($struct_name: ident, $field_name: literal, $func: ident) => { + ($struct_name: ident, $field_name: literal, $func: ident, $default: literal) => { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct $struct_name { - #[serde(rename = $field_name)] + #[serde(default = $default, rename = $field_name, deserialize_with = "deserialize_bool")] pub v: bool, } impl Default for $struct_name { @@ -123,7 +146,19 @@ macro_rules! serde_field_bool { } impl $struct_name { pub fn $func() -> bool { - UserDefaultConfig::load().get($field_name) == "Y" + UserDefaultConfig::read($field_name) == "Y" + } + } + impl Deref for $struct_name { + type Target = bool; + + fn deref(&self) -> &Self::Target { + &self.v + } + } + impl DerefMut for $struct_name { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.v } } }; @@ -137,78 +172,93 @@ 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 Resolution { + pub w: i32, + pub h: i32, +} + +#[derive(Debug, 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", - deserialize_with = "PeerConfig::deserialize_view_style" + deserialize_with = "PeerConfig::deserialize_view_style", + skip_serializing_if = "String::is_empty" )] pub view_style: String, + // Image scroll style, scrollbar or scroll auto #[serde( default = "PeerConfig::default_scroll_style", - deserialize_with = "PeerConfig::deserialize_scroll_style" + deserialize_with = "PeerConfig::deserialize_scroll_style", + skip_serializing_if = "String::is_empty" )] pub scroll_style: String, #[serde( default = "PeerConfig::default_image_quality", - deserialize_with = "PeerConfig::deserialize_image_quality" + deserialize_with = "PeerConfig::deserialize_image_quality", + skip_serializing_if = "String::is_empty" )] pub image_quality: String, #[serde( default = "PeerConfig::default_custom_image_quality", - deserialize_with = "PeerConfig::deserialize_custom_image_quality" + deserialize_with = "PeerConfig::deserialize_custom_image_quality", + skip_serializing_if = "Vec::is_empty" )] pub custom_image_quality: Vec, #[serde(flatten)] @@ -217,9 +267,11 @@ pub struct PeerConfig { pub lock_after_session_end: LockAfterSessionEnd, #[serde(flatten)] pub privacy_mode: PrivacyMode, - #[serde(default)] + #[serde(flatten)] + pub allow_swap_key: AllowSwapKey, + #[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, @@ -229,14 +281,50 @@ pub struct PeerConfig { pub enable_file_transfer: EnableFileTransfer, #[serde(flatten)] pub show_quality_monitor: ShowQualityMonitor, - #[serde(default)] + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] pub keyboard_mode: String, + #[serde(flatten)] + pub view_only: ViewOnly, + // Mouse wheel or touchpad scroll mode + #[serde( + default = "PeerConfig::default_reverse_mouse_wheel", + deserialize_with = "PeerConfig::deserialize_reverse_mouse_wheel", + skip_serializing_if = "String::is_empty" + )] + pub reverse_mouse_wheel: String, + #[serde( + default = "PeerConfig::default_displays_as_individual_windows", + deserialize_with = "PeerConfig::deserialize_displays_as_individual_windows", + skip_serializing_if = "String::is_empty" + )] + pub displays_as_individual_windows: String, + #[serde( + default = "PeerConfig::default_use_all_my_displays_for_the_remote_session", + deserialize_with = "PeerConfig::deserialize_use_all_my_displays_for_the_remote_session", + skip_serializing_if = "String::is_empty" + )] + pub use_all_my_displays_for_the_remote_session: String, + + #[serde( + default, + deserialize_with = "deserialize_hashmap_resolutions", + skip_serializing_if = "HashMap::is_empty" + )] + pub custom_resolutions: HashMap, // The other scalar value must before this - #[serde(default, deserialize_with = "PeerConfig::deserialize_options")] - pub options: HashMap, + #[serde( + default, + deserialize_with = "deserialize_hashmap_string_string", + skip_serializing_if = "HashMap::is_empty" + )] + 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, @@ -244,24 +332,66 @@ pub struct PeerConfig { pub transfer: TransferSerde, } +impl Default for PeerConfig { + fn default() -> Self { + Self { + password: Default::default(), + size: Default::default(), + size_ft: Default::default(), + size_pf: Default::default(), + view_style: Self::default_view_style(), + scroll_style: Self::default_scroll_style(), + image_quality: Self::default_image_quality(), + custom_image_quality: Self::default_custom_image_quality(), + show_remote_cursor: Default::default(), + lock_after_session_end: Default::default(), + privacy_mode: Default::default(), + allow_swap_key: Default::default(), + port_forwards: Default::default(), + direct_failures: Default::default(), + disable_audio: Default::default(), + disable_clipboard: Default::default(), + enable_file_transfer: Default::default(), + show_quality_monitor: Default::default(), + keyboard_mode: Default::default(), + view_only: Default::default(), + reverse_mouse_wheel: Self::default_reverse_mouse_wheel(), + displays_as_individual_windows: Self::default_displays_as_individual_windows(), + use_all_my_displays_for_the_remote_session: + Self::default_use_all_my_displays_for_the_remote_session(), + custom_resolutions: Default::default(), + options: Self::default_options(), + ui_flutter: Default::default(), + info: Default::default(), + transfer: Default::default(), + } + } +} + #[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 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, } +#[inline] +pub fn get_online_state() -> i64 { + *ONLINE.lock().unwrap().values().max().unwrap_or(&0) +} + +#[cfg(not(any(target_os = "android", target_os = "ios")))] fn patch(path: PathBuf) -> PathBuf { if let Some(_tmp) = path.to_str() { #[cfg(windows)] @@ -276,12 +406,14 @@ fn patch(path: PathBuf) -> PathBuf { #[cfg(target_os = "linux")] { if _tmp == "/root" { - if let Ok(output) = std::process::Command::new("whoami").output() { - let user = String::from_utf8_lossy(&output.stdout) - .to_string() - .trim() - .to_owned(); + if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") { if user != "root" { + let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user); + if let Ok(output) = + crate::platform::linux::run_cmds_trim_newline(&cmd) + { + return output.into(); + } return format!("/home/{user}").into(); } } @@ -313,7 +445,8 @@ impl Config2 { fn store(&self) { let mut config = self.clone(); if let Some(mut socks) = config.socks { - socks.password = encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION); + socks.password = + encrypt_str_or_original(&socks.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); config.socks = Some(socks); } Config::store_(&config, "2"); @@ -337,13 +470,19 @@ impl Config2 { pub fn load_path( file: PathBuf, ) -> T { - match confy::load_path(file) { + let cfg = match confy::load_path(&file) { Ok(config) => config, Err(err) => { - log::error!("Failed to load config: {}", err); + if let confy::ConfyError::GeneralLoadError(err) = &err { + if err.kind() == std::io::ErrorKind::NotFound { + return T::default(); + } + } + log::error!("Failed to load config '{}': {}", file.display(), err); T::default() } - } + }; + cfg } #[inline] @@ -356,7 +495,6 @@ impl Config { suffix: &str, ) -> T { let file = Self::file_(suffix); - log::debug!("Configuration path: {}", file.display()); let cfg = load_path(file); if suffix.is_empty() { log::trace!("{:?}", cfg); @@ -383,11 +521,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 { @@ -413,8 +554,9 @@ impl Config { fn store(&self) { let mut config = self.clone(); - config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION); - config.enc_id = encrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION); + config.password = + encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); + config.enc_id = encrypt_str_or_original(&config.id, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); config.id = "".to_owned(); Config::store_(&config, ""); } @@ -434,7 +576,7 @@ impl Config { pub fn get_home() -> PathBuf { #[cfg(any(target_os = "android", target_os = "ios"))] - return Self::path(APP_HOME_DIR.read().unwrap().as_str()); + return PathBuf::from(APP_HOME_DIR.read().unwrap().as_str()); #[cfg(not(any(target_os = "android", target_os = "ios")))] { if let Some(path) = dirs_next::home_dir() { @@ -488,6 +630,13 @@ impl Config { std::fs::create_dir_all(&path).ok(); return path; } + #[cfg(target_os = "android")] + { + let mut path = Self::get_home(); + path.push(format!("{}/Logs", *APP_NAME.read().unwrap())); + std::fs::create_dir_all(&path).ok(); + return path; + } if let Some(path) = Self::path("").parent() { let mut path: PathBuf = path.into(); path.push("log"); @@ -541,7 +690,10 @@ impl Config { } pub fn get_rendezvous_server() -> String { - let mut rendezvous_server = Self::get_option("custom-rendezvous-server"); + let mut rendezvous_server = EXE_RENDEZVOUS_SERVER.read().unwrap().clone(); + if rendezvous_server.is_empty() { + rendezvous_server = Self::get_option("custom-rendezvous-server"); + } if rendezvous_server.is_empty() { rendezvous_server = PROD_RENDEZVOUS_SERVER.read().unwrap().clone(); } @@ -561,6 +713,10 @@ impl Config { } pub fn get_rendezvous_servers() -> Vec { + let s = EXE_RENDEZVOUS_SERVER.read().unwrap().clone(); + if !s.is_empty() { + return vec![s]; + } let s = Self::get_option("custom-rendezvous-server"); if !s.is_empty() { return vec![s]; @@ -713,6 +869,7 @@ impl Config { } let mut config = Config::load_::(""); if config.key_pair.0.is_empty() { + log::info!("Generated new keypair for id: {}", config.id); let (pk, sk) = sign::gen_keypair(); let key_pair = (sk.0.to_vec(), pk.0.into()); config.key_pair = key_pair.clone(); @@ -747,10 +904,19 @@ impl Config { } pub fn get_options() -> HashMap { - CONFIG2.read().unwrap().options.clone() + let mut res = DEFAULT_SETTINGS.read().unwrap().clone(); + res.extend(CONFIG2.read().unwrap().options.clone()); + res.extend(OVERWRITE_SETTINGS.read().unwrap().clone()); + res } - pub fn set_options(v: HashMap) { + #[inline] + fn purify_options(v: &mut HashMap) { + v.retain(|k, v| is_option_can_save(&OVERWRITE_SETTINGS, &DEFAULT_SETTINGS, k, v)); + } + + pub fn set_options(mut v: HashMap) { + Self::purify_options(&mut v); let mut config = CONFIG2.write().unwrap(); if config.options == v { return; @@ -760,14 +926,19 @@ impl Config { } pub fn get_option(k: &str) -> String { - if let Some(v) = CONFIG2.read().unwrap().options.get(k) { - v.clone() - } else { - "".to_owned() - } + get_or( + &OVERWRITE_SETTINGS, + &CONFIG2.read().unwrap().options, + &DEFAULT_SETTINGS, + k, + ) + .unwrap_or_default() } pub fn set_option(k: String, v: String) { + if !is_option_can_save(&OVERWRITE_SETTINGS, &DEFAULT_SETTINGS, &k, &v) { + return; + } let mut config = CONFIG2.write().unwrap(); let v2 = if v.is_empty() { None } else { Some(&v) }; if v2 != config.options.get(&k) { @@ -790,6 +961,14 @@ impl Config { } pub fn set_permanent_password(password: &str) { + if HARD_SETTINGS + .read() + .unwrap() + .get("password") + .map_or(false, |v| v == password) + { + return; + } let mut config = CONFIG.write().unwrap(); if password == config.password { return; @@ -799,7 +978,13 @@ impl Config { } pub fn get_permanent_password() -> String { - CONFIG.read().unwrap().password.clone() + let mut password = CONFIG.read().unwrap().password.clone(); + if password.is_empty() { + if let Some(v) = HARD_SETTINGS.read().unwrap().get("password") { + password = v.to_owned(); + } + } + password } pub fn set_salt(salt: &str) { @@ -833,6 +1018,11 @@ impl Config { CONFIG2.read().unwrap().socks.clone() } + #[inline] + pub fn is_proxy() -> bool { + Self::get_network_type() != NetworkType::Direct + } + pub fn get_network_type() -> NetworkType { match &CONFIG2.read().unwrap().socks { None => NetworkType::Direct, @@ -878,15 +1068,13 @@ impl PeerConfig { decrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION); config.password = password; store = store || store2; - if let Some(v) = config.options.get_mut("rdp_password") { - let (password, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION); - *v = password; - store = store || store2; - } - if let Some(v) = config.options.get_mut("os-password") { - let (password, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION); - *v = password; - store = store || store2; + for opt in ["rdp_password", "os-username", "os-password"] { + if let Some(v) = config.options.get_mut(opt) { + let (encrypted, _, store2) = + decrypt_str_or_original(v, PASSWORD_ENC_VERSION); + *v = encrypted; + store = store || store2; + } } if store { config.store(id); @@ -894,7 +1082,12 @@ impl PeerConfig { config } Err(err) => { - log::error!("Failed to load config: {}", err); + if let confy::ConfyError::GeneralLoadError(err) = &err { + if err.kind() == std::io::ErrorKind::NotFound { + return Default::default(); + } + } + log::error!("Failed to load peer config '{}': {}", id, err); Default::default() } } @@ -903,16 +1096,17 @@ impl PeerConfig { pub fn store(&self, id: &str) { let _lock = CONFIG.read().unwrap(); let mut config = self.clone(); - config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION); - if let Some(v) = config.options.get_mut("rdp_password") { - *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION) + config.password = + encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN); + for opt in ["rdp_password", "os-username", "os-password"] { + if let Some(v) = config.options.get_mut(opt) { + *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION, ENCRYPT_MAX_LEN) + } } - if let Some(v) = config.options.get_mut("os-password") { - *v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION) - }; if let Err(err) = store_path(Self::path(id), config) { log::error!("Failed to store config: {}", err); } + NEW_STORED_PEER_CONFIG.lock().unwrap().insert(id.to_owned()); } pub fn remove(id: &str) { @@ -921,17 +1115,24 @@ impl PeerConfig { fn path(id: &str) -> PathBuf { //If the id contains invalid chars, encode it - let forbidden_paths = Regex::new(r".*[<>:/\\|\?\*].*").unwrap(); - let id_encoded = if forbidden_paths.is_match(id) { - "base64_".to_string() + base64::encode(id, base64::Variant::Original).as_str() + let forbidden_paths = Regex::new(r".*[<>:/\\|\?\*].*"); + let path: PathBuf; + if let Ok(forbidden_paths) = forbidden_paths { + let id_encoded = if forbidden_paths.is_match(id) { + "base64_".to_string() + base64::encode(id, base64::Variant::Original).as_str() + } else { + id.to_string() + }; + path = [PEERS, id_encoded.as_str()].iter().collect(); } else { - id.to_string() - }; - let path: PathBuf = [PEERS, id_encoded.as_str()].iter().collect(); + log::warn!("Regex create failed: {:?}", forbidden_paths.err()); + // fallback for failing to create this regex. + path = [PEERS, id.replace(":", "_").as_str()].iter().collect(); + } Config::with_extension(Config::path(path)) } - pub fn peers() -> Vec<(String, SystemTime, PeerConfig)> { + pub fn peers(id_filters: Option>) -> Vec<(String, SystemTime, PeerConfig)> { if let Ok(peers) = Config::path(PEERS).read_dir() { if let Ok(peers) = peers .map(|res| res.map(|e| e.path())) @@ -944,7 +1145,6 @@ impl PeerConfig { && p.extension().map(|p| p.to_str().unwrap_or("")) == Some("toml") }) .map(|p| { - let t = crate::get_modified_time(p); let id = p .file_stem() .map(|p| p.to_str().unwrap_or("")) @@ -958,12 +1158,21 @@ impl PeerConfig { } else { id }; - - let c = PeerConfig::load(&id_decoded_string); + (id_decoded_string, p) + }) + .filter(|(id, _)| { + let Some(filters) = &id_filters else { + return true; + }; + filters.contains(id) + }) + .map(|(id, p)| { + let t = crate::get_modified_time(p); + let c = PeerConfig::load(&id); if c.info.platform.is_empty() { fs::remove_file(p).ok(); } - (id_decoded_string, t, c) + (id, t, c) }) .filter(|p| !p.2.info.platform.is_empty()) .collect(); @@ -974,25 +1183,43 @@ impl PeerConfig { Default::default() } + pub fn exists(id: &str) -> bool { + Self::path(id).exists() + } + serde_field_string!( default_view_style, deserialize_view_style, - UserDefaultConfig::load().get("view_style") + UserDefaultConfig::read("view_style") ); serde_field_string!( default_scroll_style, deserialize_scroll_style, - UserDefaultConfig::load().get("scroll_style") + UserDefaultConfig::read("scroll_style") ); serde_field_string!( default_image_quality, deserialize_image_quality, - UserDefaultConfig::load().get("image_quality") + UserDefaultConfig::read("image_quality") + ); + serde_field_string!( + default_reverse_mouse_wheel, + deserialize_reverse_mouse_wheel, + UserDefaultConfig::read("reverse_mouse_wheel") + ); + serde_field_string!( + default_displays_as_individual_windows, + deserialize_displays_as_individual_windows, + UserDefaultConfig::read("displays_as_individual_windows") + ); + serde_field_string!( + default_use_all_my_displays_for_the_remote_session, + deserialize_use_all_my_displays_for_the_remote_session, + UserDefaultConfig::read("use_all_my_displays_for_the_remote_session") ); fn default_custom_image_quality() -> Vec { - let f: f64 = UserDefaultConfig::load() - .get("custom_image_quality") + let f: f64 = UserDefaultConfig::read("custom_image_quality") .parse() .unwrap_or(50.0); vec![f as _] @@ -1003,76 +1230,101 @@ impl PeerConfig { D: de::Deserializer<'de>, { let v: Vec = de::Deserialize::deserialize(deserializer)?; - if v.len() == 1 && v[0] >= 10 && v[0] <= 100 { + if v.len() == 1 && v[0] >= 10 && v[0] <= 0xFFF { Ok(v) } else { Ok(Self::default_custom_image_quality()) } } - fn deserialize_options<'de, D>(deserializer: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - let mut mp: HashMap = de::Deserialize::deserialize(deserializer)?; - let mut key = "codec-preference"; - if !mp.contains_key(key) { - mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); - } - key = "custom-fps"; - if !mp.contains_key(key) { - mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); - } - key = "zoom-cursor"; - if !mp.contains_key(key) { - mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); - } - Ok(mp) + fn default_options() -> HashMap { + let mut mp: HashMap = Default::default(); + [ + "codec-preference", + "custom-fps", + "zoom-cursor", + "touch-mode", + "i444", + "swap-left-right-mouse", + ] + .map(|key| { + mp.insert(key.to_owned(), UserDefaultConfig::read(key)); + }); + mp } } serde_field_bool!( ShowRemoteCursor, "show_remote_cursor", - default_show_remote_cursor + default_show_remote_cursor, + "ShowRemoteCursor::default_show_remote_cursor" ); serde_field_bool!( ShowQualityMonitor, "show_quality_monitor", - default_show_quality_monitor + default_show_quality_monitor, + "ShowQualityMonitor::default_show_quality_monitor" +); +serde_field_bool!( + DisableAudio, + "disable_audio", + default_disable_audio, + "DisableAudio::default_disable_audio" ); -serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio); serde_field_bool!( EnableFileTransfer, "enable_file_transfer", - default_enable_file_transfer + default_enable_file_transfer, + "EnableFileTransfer::default_enable_file_transfer" ); serde_field_bool!( DisableClipboard, "disable_clipboard", - default_disable_clipboard + default_disable_clipboard, + "DisableClipboard::default_disable_clipboard" ); serde_field_bool!( LockAfterSessionEnd, "lock_after_session_end", - default_lock_after_session_end + default_lock_after_session_end, + "LockAfterSessionEnd::default_lock_after_session_end" +); +serde_field_bool!( + PrivacyMode, + "privacy_mode", + default_privacy_mode, + "PrivacyMode::default_privacy_mode" +); + +serde_field_bool!( + AllowSwapKey, + "allow_swap_key", + default_allow_swap_key, + "AllowSwapKey::default_allow_swap_key" +); + +serde_field_bool!( + ViewOnly, + "view_only", + default_view_only, + "ViewOnly::default_view_only" ); -serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode); #[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, } @@ -1136,14 +1388,19 @@ impl LocalConfig { } pub fn get_option(k: &str) -> String { - if let Some(v) = LOCAL_CONFIG.read().unwrap().options.get(k) { - v.clone() - } else { - "".to_owned() - } + get_or( + &OVERWRITE_LOCAL_SETTINGS, + &LOCAL_CONFIG.read().unwrap().options, + &DEFAULT_LOCAL_SETTINGS, + k, + ) + .unwrap_or_default() } pub fn set_option(k: String, v: String) { + if !is_option_can_save(&OVERWRITE_LOCAL_SETTINGS, &DEFAULT_LOCAL_SETTINGS, &k, &v) { + return; + } let mut config = LOCAL_CONFIG.write().unwrap(); let v2 = if v.is_empty() { None } else { Some(&v) }; if v2 != config.options.get(&k) { @@ -1156,7 +1413,7 @@ impl LocalConfig { } } - pub fn get_flutter_config(k: &str) -> String { + pub fn get_flutter_option(k: &str) -> String { if let Some(v) = LOCAL_CONFIG.read().unwrap().ui_flutter.get(k) { v.clone() } else { @@ -1164,7 +1421,7 @@ impl LocalConfig { } } - pub fn set_flutter_config(k: String, v: String) { + pub fn set_flutter_option(k: String, v: String) { let mut config = LOCAL_CONFIG.write().unwrap(); let v2 = if v.is_empty() { None } else { Some(&v) }; if v2 != config.ui_flutter.get(&k) { @@ -1180,17 +1437,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, } @@ -1202,6 +1459,7 @@ impl DiscoveryPeer { #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct LanPeers { + #[serde(default, deserialize_with = "deserialize_vec_discoverypeer")] pub peers: Vec, } @@ -1237,7 +1495,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, } @@ -1250,28 +1508,48 @@ impl HwCodecConfig { Config::store_(self, "_hwcodec"); } - pub fn remove() { - std::fs::remove_file(Config::file_("_hwcodec")).ok(); + pub fn clear() { + HwCodecConfig::default().store(); } +} - /// refresh current global HW_CODEC_CONFIG, usually uesd after HwCodecConfig::remove() - pub fn refresh() { - *HW_CODEC_CONFIG.write().unwrap() = HwCodecConfig::load(); - log::debug!("HW_CODEC_CONFIG refreshed successfully"); +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct GpucodecConfig { + #[serde(default, deserialize_with = "deserialize_string")] + pub available: String, +} + +impl GpucodecConfig { + pub fn load() -> GpucodecConfig { + Config::load_::("_gpucodec") } - pub fn get() -> HwCodecConfig { - return HW_CODEC_CONFIG.read().unwrap().clone(); + pub fn store(&self) { + Config::store_(self, "_gpucodec"); + } + + pub fn clear() { + GpucodecConfig::default().store(); } } #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct UserDefaultConfig { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_hashmap_string_string")] options: HashMap, } impl UserDefaultConfig { + fn read(key: &str) -> String { + let mut cfg = USER_DEFAULT_CONFIG.write().unwrap(); + // we do so, because default config may changed in another process, but we don't sync it + // but no need to read every time, give a small interval to avoid too many redundant read waste + if cfg.1.elapsed() > Duration::from_secs(1) { + *cfg = (Self::load(), Instant::now()); + } + cfg.0.get(key) + } + pub fn load() -> UserDefaultConfig { Config::load_::("_default") } @@ -1286,25 +1564,38 @@ impl UserDefaultConfig { "view_style" => self.get_string(key, "original", vec!["adaptive"]), "scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]), "image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]), - "codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]), - "custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0), - "custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0), + "codec-preference" => { + self.get_string(key, "auto", vec!["vp8", "vp9", "av1", "h264", "h265"]) + } + "custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 0xFFF as f64), + "custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0), _ => self - .options - .get(key) + .get_after(key) .map(|v| v.to_string()) .unwrap_or_default(), } } pub fn set(&mut self, key: String, value: String) { - self.options.insert(key, value); + if !is_option_can_save( + &OVERWRITE_DISPLAY_SETTINGS, + &DEFAULT_DISPLAY_SETTINGS, + &key, + &value, + ) { + return; + } + if value.is_empty() { + self.options.remove(&key); + } else { + self.options.insert(key, value); + } self.store(); } #[inline] fn get_string(&self, key: &str, default: &str, others: Vec<&str>) -> String { - match self.options.get(key) { + match self.get_after(key) { Some(option) => { if others.contains(&option.as_str()) { option.to_owned() @@ -1318,7 +1609,7 @@ impl UserDefaultConfig { #[inline] fn get_double_string(&self, key: &str, default: f64, min: f64, max: f64) -> String { - match self.options.get(key) { + match self.get_after(key) { Some(option) => { let v: f64 = option.parse().unwrap_or(default); if v >= min && v <= max { @@ -1330,6 +1621,355 @@ impl UserDefaultConfig { None => default.to_string(), } } + + fn get_after(&self, k: &str) -> Option { + get_or( + &OVERWRITE_DISPLAY_SETTINGS, + &self.options, + &DEFAULT_DISPLAY_SETTINGS, + k, + ) + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct AbPeer { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub id: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub hash: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub username: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub hostname: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub platform: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub alias: String, + #[serde(default, deserialize_with = "deserialize_vec_string")] + pub tags: Vec, +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct AbEntry { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub guid: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub name: String, + #[serde(default, deserialize_with = "deserialize_vec_abpeer")] + pub peers: Vec, + #[serde(default, deserialize_with = "deserialize_vec_string")] + pub tags: Vec, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub tag_colors: String, +} + +impl AbEntry { + pub fn personal(&self) -> bool { + self.name == "My address book" || self.name == "Legacy address book" + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct Ab { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub access_token: String, + #[serde(default, deserialize_with = "deserialize_vec_abentry")] + pub ab_entries: Vec, +} + +impl Ab { + fn path() -> PathBuf { + let filename = format!("{}_ab", APP_NAME.read().unwrap().clone()); + Config::path(filename) + } + + pub fn store(json: String) { + if let Ok(mut file) = std::fs::File::create(Self::path()) { + let data = compress(json.as_bytes()); + let max_len = 64 * 1024 * 1024; + if data.len() > max_len { + // maxlen of function decompress + log::error!("ab data too large, {} > {}", data.len(), max_len); + return; + } + if let Ok(data) = symmetric_crypt(&data, true) { + file.write_all(&data).ok(); + } + }; + } + + pub fn load() -> Ab { + if let Ok(mut file) = std::fs::File::open(Self::path()) { + let mut data = vec![]; + if file.read_to_end(&mut data).is_ok() { + if let Ok(data) = symmetric_crypt(&data, false) { + let data = decompress(&data); + if let Ok(ab) = serde_json::from_str::(&String::from_utf8_lossy(&data)) { + return ab; + } + } + } + }; + Self::remove(); + Ab::default() + } + + pub fn remove() { + std::fs::remove_file(Self::path()).ok(); + } +} + +// 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()) + } + }; +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct GroupPeer { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub id: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub username: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub hostname: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub platform: String, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub login_name: String, +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct GroupUser { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub name: String, +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct Group { + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub access_token: String, + #[serde(default, deserialize_with = "deserialize_vec_groupuser")] + pub users: Vec, + #[serde(default, deserialize_with = "deserialize_vec_grouppeer")] + pub peers: Vec, +} + +impl Group { + fn path() -> PathBuf { + let filename = format!("{}_group", APP_NAME.read().unwrap().clone()); + Config::path(filename) + } + + pub fn store(json: String) { + if let Ok(mut file) = std::fs::File::create(Self::path()) { + let data = compress(json.as_bytes()); + let max_len = 64 * 1024 * 1024; + if data.len() > max_len { + // maxlen of function decompress + return; + } + if let Ok(data) = symmetric_crypt(&data, true) { + file.write_all(&data).ok(); + } + }; + } + + pub fn load() -> Self { + if let Ok(mut file) = std::fs::File::open(Self::path()) { + let mut data = vec![]; + if file.read_to_end(&mut data).is_ok() { + if let Ok(data) = symmetric_crypt(&data, false) { + let data = decompress(&data); + if let Ok(group) = serde_json::from_str::(&String::from_utf8_lossy(&data)) + { + return group; + } + } + } + }; + Self::remove(); + Self::default() + } + + pub fn remove() { + std::fs::remove_file(Self::path()).ok(); + } +} + +deserialize_default!(deserialize_string, String); +deserialize_default!(deserialize_bool, bool); +deserialize_default!(deserialize_i32, i32); +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_vec_abpeer, Vec); +deserialize_default!(deserialize_vec_abentry, Vec); +deserialize_default!(deserialize_vec_groupuser, Vec); +deserialize_default!(deserialize_vec_grouppeer, Vec); +deserialize_default!(deserialize_keypair, KeyPair); +deserialize_default!(deserialize_size, Size); +deserialize_default!(deserialize_hashmap_string_string, HashMap); +deserialize_default!(deserialize_hashmap_string_bool, HashMap); +deserialize_default!(deserialize_hashmap_resolutions, HashMap); + +#[inline] +fn get_or( + a: &RwLock>, + b: &HashMap, + c: &RwLock>, + k: &str, +) -> Option { + a.read() + .unwrap() + .get(k) + .or(b.get(k)) + .or(c.read().unwrap().get(k)) + .cloned() +} + +#[inline] +fn is_option_can_save( + overwrite: &RwLock>, + defaults: &RwLock>, + k: &str, + v: &str, +) -> bool { + if overwrite.read().unwrap().contains_key(k) { + return false; + } + if defaults.read().unwrap().get(k).map_or(false, |x| x == v) { + return false; + } + true +} + +#[inline] +pub fn is_incoming_only() -> bool { + HARD_SETTINGS + .read() + .unwrap() + .get("conn-type") + .map_or(false, |x| x == ("incoming")) +} + +#[inline] +pub fn is_outgoing_only() -> bool { + HARD_SETTINGS + .read() + .unwrap() + .get("conn-type") + .map_or(false, |x| x == ("outgoing")) +} + +#[inline] +fn is_some_hard_opton(name: &str) -> bool { + HARD_SETTINGS + .read() + .unwrap() + .get(name) + .map_or(false, |x| x == ("Y")) +} + +#[inline] +pub fn is_disable_tcp_listen() -> bool { + is_some_hard_opton("disable-tcp-listen") +} + +#[inline] +pub fn is_disable_settings() -> bool { + is_some_hard_opton("disable-settings") +} + +#[inline] +pub fn is_disable_ab() -> bool { + is_some_hard_opton("disable-ab") +} + +#[inline] +pub fn is_disable_account() -> bool { + is_some_hard_opton("disable-account") +} + +#[inline] +pub fn is_disable_installation() -> bool { + is_some_hard_opton("disable-installation") } #[cfg(test)] @@ -1345,4 +1985,221 @@ mod tests { let res = toml::to_string_pretty(&cfg); assert!(res.is_ok()); } + + #[test] + fn test_overwrite_settings() { + DEFAULT_SETTINGS + .write() + .unwrap() + .insert("b".to_string(), "a".to_string()); + DEFAULT_SETTINGS + .write() + .unwrap() + .insert("c".to_string(), "a".to_string()); + CONFIG2 + .write() + .unwrap() + .options + .insert("a".to_string(), "b".to_string()); + CONFIG2 + .write() + .unwrap() + .options + .insert("b".to_string(), "b".to_string()); + OVERWRITE_SETTINGS + .write() + .unwrap() + .insert("b".to_string(), "c".to_string()); + OVERWRITE_SETTINGS + .write() + .unwrap() + .insert("d".to_string(), "c".to_string()); + let mut res: HashMap = Default::default(); + res.insert("b".to_owned(), "c".to_string()); + res.insert("d".to_owned(), "c".to_string()); + res.insert("c".to_owned(), "a".to_string()); + Config::purify_options(&mut res); + assert!(res.len() == 0); + res.insert("b".to_owned(), "c".to_string()); + res.insert("d".to_owned(), "c".to_string()); + res.insert("c".to_owned(), "a".to_string()); + res.insert("f".to_owned(), "a".to_string()); + Config::purify_options(&mut res); + assert!(res.len() == 1); + res.insert("b".to_owned(), "c".to_string()); + res.insert("d".to_owned(), "c".to_string()); + res.insert("c".to_owned(), "a".to_string()); + res.insert("f".to_owned(), "a".to_string()); + res.insert("c".to_owned(), "d".to_string()); + Config::purify_options(&mut res); + assert!(res.len() == 2); + res.insert("b".to_owned(), "c".to_string()); + res.insert("d".to_owned(), "c".to_string()); + res.insert("c".to_owned(), "a".to_string()); + res.insert("f".to_owned(), "a".to_string()); + res.insert("c".to_owned(), "d".to_string()); + res.insert("d".to_owned(), "cc".to_string()); + Config::purify_options(&mut res); + assert!(res.len() == 2); + let res = Config::get_options(); + assert!(res["a"] == "b"); + assert!(res["c"] == "a"); + assert!(res["b"] == "c"); + assert!(res["d"] == "c"); + assert!(Config::get_option("a") == "b"); + assert!(Config::get_option("c") == "a"); + assert!(Config::get_option("b") == "c"); + assert!(Config::get_option("d") == "c"); + DEFAULT_SETTINGS.write().unwrap().clear(); + OVERWRITE_SETTINGS.write().unwrap().clear(); + CONFIG2.write().unwrap().options.clear(); + + DEFAULT_LOCAL_SETTINGS + .write() + .unwrap() + .insert("b".to_string(), "a".to_string()); + DEFAULT_LOCAL_SETTINGS + .write() + .unwrap() + .insert("c".to_string(), "a".to_string()); + LOCAL_CONFIG + .write() + .unwrap() + .options + .insert("a".to_string(), "b".to_string()); + LOCAL_CONFIG + .write() + .unwrap() + .options + .insert("b".to_string(), "b".to_string()); + OVERWRITE_LOCAL_SETTINGS + .write() + .unwrap() + .insert("b".to_string(), "c".to_string()); + OVERWRITE_LOCAL_SETTINGS + .write() + .unwrap() + .insert("d".to_string(), "c".to_string()); + assert!(LocalConfig::get_option("a") == "b"); + assert!(LocalConfig::get_option("c") == "a"); + assert!(LocalConfig::get_option("b") == "c"); + assert!(LocalConfig::get_option("d") == "c"); + DEFAULT_LOCAL_SETTINGS.write().unwrap().clear(); + OVERWRITE_LOCAL_SETTINGS.write().unwrap().clear(); + LOCAL_CONFIG.write().unwrap().options.clear(); + + DEFAULT_DISPLAY_SETTINGS + .write() + .unwrap() + .insert("b".to_string(), "a".to_string()); + DEFAULT_DISPLAY_SETTINGS + .write() + .unwrap() + .insert("c".to_string(), "a".to_string()); + USER_DEFAULT_CONFIG + .write() + .unwrap() + .0 + .options + .insert("a".to_string(), "b".to_string()); + USER_DEFAULT_CONFIG + .write() + .unwrap() + .0 + .options + .insert("b".to_string(), "b".to_string()); + OVERWRITE_DISPLAY_SETTINGS + .write() + .unwrap() + .insert("b".to_string(), "c".to_string()); + OVERWRITE_DISPLAY_SETTINGS + .write() + .unwrap() + .insert("d".to_string(), "c".to_string()); + assert!(UserDefaultConfig::read("a") == "b"); + assert!(UserDefaultConfig::read("c") == "a"); + assert!(UserDefaultConfig::read("b") == "c"); + assert!(UserDefaultConfig::read("d") == "c"); + DEFAULT_DISPLAY_SETTINGS.write().unwrap().clear(); + OVERWRITE_DISPLAY_SETTINGS.write().unwrap().clear(); + LOCAL_CONFIG.write().unwrap().options.clear(); + } + + #[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() + }) + ); + } + + #[test] + fn test_peer_config_deserialize() { + let default_peer_config = toml::from_str::("").unwrap(); + // test custom_resolution + { + let wrong_type_str = r#" + view_style = "adaptive" + scroll_style = "scrollbar" + custom_resolutions = true + "#; + let mut cfg_to_compare = default_peer_config.clone(); + cfg_to_compare.view_style = "adaptive".to_string(); + cfg_to_compare.scroll_style = "scrollbar".to_string(); + let cfg = toml::from_str::(wrong_type_str); + assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_type_str"); + + let wrong_type_str = r#" + view_style = "adaptive" + scroll_style = "scrollbar" + [custom_resolutions.0] + w = "1920" + h = 1080 + "#; + let mut cfg_to_compare = default_peer_config.clone(); + cfg_to_compare.view_style = "adaptive".to_string(); + cfg_to_compare.scroll_style = "scrollbar".to_string(); + let cfg = toml::from_str::(wrong_type_str); + assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_type_str"); + + let wrong_field_str = r#" + [custom_resolutions.0] + w = 1920 + h = 1080 + hello = "world" + [ui_flutter] + "#; + let mut cfg_to_compare = default_peer_config.clone(); + cfg_to_compare.custom_resolutions = + HashMap::from([("0".to_string(), Resolution { w: 1920, h: 1080 })]); + let cfg = toml::from_str::(wrong_field_str); + assert_eq!(cfg, Ok(cfg_to_compare), "Failed to test wrong_field_str"); + } + } } diff --git a/libs/hbb_common/src/fs.rs b/libs/hbb_common/src/fs.rs index ea54e113..235cb483 100644 --- a/libs/hbb_common/src/fs.rs +++ b/libs/hbb_common/src/fs.rs @@ -4,13 +4,14 @@ use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use serde_derive::{Deserialize, Serialize}; +use serde_json::json; use tokio::{fs::File, io::*}; -use crate::{bail, get_version_number, message_proto::*, ResultType, Stream}; +use crate::{anyhow::anyhow, bail, get_version_number, message_proto::*, ResultType, Stream}; // https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html use crate::{ compress::{compress, decompress}, - config::{Config, COMPRESS_LEVEL}, + config::Config, }; pub fn read_dir(path: &Path, include_hidden: bool) -> ResultType { @@ -194,7 +195,8 @@ pub fn can_enable_overwrite_detection(version: i64) -> bool { version >= get_version_number("1.1.10") } -#[derive(Default)] +#[derive(Default, Serialize, Debug)] +#[serde(rename_all = "camelCase")] pub struct TransferJob { pub id: i32, pub remote: String, @@ -203,10 +205,13 @@ pub struct TransferJob { pub is_remote: bool, pub is_last_job: bool, pub file_num: i32, + #[serde(skip_serializing)] pub files: Vec, + pub conn_id: i32, // server only + #[serde(skip_serializing)] file: Option, - total_size: u64, + pub total_size: u64, finished_size: u64, transferred: u64, enable_overwrite_detection: bool, @@ -403,10 +408,18 @@ impl TransferJob { } if block.compressed { let tmp = decompress(&block.data); - self.file.as_mut().unwrap().write_all(&tmp).await?; + self.file + .as_mut() + .ok_or(anyhow!("file is None"))? + .write_all(&tmp) + .await?; self.finished_size += tmp.len() as u64; } else { - self.file.as_mut().unwrap().write_all(&block.data).await?; + self.file + .as_mut() + .ok_or(anyhow!("file is None"))? + .write_all(&block.data) + .await?; self.finished_size += block.data.len() as u64; } self.transferred += block.data.len() as u64; @@ -456,7 +469,13 @@ impl TransferJob { let mut compressed = false; let mut offset: usize = 0; loop { - match self.file.as_mut().unwrap().read(&mut buf[offset..]).await { + match self + .file + .as_mut() + .ok_or(anyhow!("file is None"))? + .read(&mut buf[offset..]) + .await + { Err(err) => { self.file_num += 1; self.file = None; @@ -481,7 +500,7 @@ impl TransferJob { } else { self.finished_size += offset as u64; if !is_compressed_file(name) { - let tmp = compress(&buf, COMPRESS_LEVEL); + let tmp = compress(&buf); if tmp.len() < buf.len() { buf = tmp; compressed = true; @@ -501,7 +520,12 @@ impl TransferJob { async fn send_current_digest(&mut self, stream: &mut Stream) -> ResultType<()> { let mut msg = Message::new(); let mut resp = FileResponse::new(); - let meta = self.file.as_ref().unwrap().metadata().await?; + let meta = self + .file + .as_ref() + .ok_or(anyhow!("file is None"))? + .metadata() + .await?; let last_modified = meta .modified()? .duration_since(SystemTime::UNIX_EPOCH)? @@ -516,7 +540,7 @@ impl TransferJob { msg.set_file_response(resp); stream.send(&msg).await?; log::info!( - "id: {}, file_num:{}, digest message is sent. waiting for confirm. msg: {:?}", + "id: {}, file_num: {}, digest message is sent. waiting for confirm. msg: {:?}", self.id, self.file_num, msg @@ -676,13 +700,20 @@ pub fn new_send_confirm(r: FileTransferSendConfirmRequest) -> Message { } #[inline] -pub fn new_receive(id: i32, path: String, file_num: i32, files: Vec) -> Message { +pub fn new_receive( + id: i32, + path: String, + file_num: i32, + files: Vec, + total_size: u64, +) -> Message { let mut action = FileAction::new(); action.set_receive(FileTransferReceiveRequest { id, path, files, file_num, + total_size, ..Default::default() }); let mut msg_out = Message::new(); @@ -692,7 +723,7 @@ pub fn new_receive(id: i32, path: String, file_num: i32, files: Vec) #[inline] pub fn new_send(id: i32, path: String, file_num: i32, include_hidden: bool) -> Message { - log::info!("new send: {},id : {}", path, id); + log::info!("new send: {}, id: {}", path, id); let mut action = FileAction::new(); action.set_send(FileTransferSendRequest { id, @@ -729,10 +760,16 @@ pub fn get_job(id: i32, jobs: &mut [TransferJob]) -> Option<&mut TransferJob> { jobs.iter_mut().find(|x| x.id() == id) } +#[inline] +pub fn get_job_immutable(id: i32, jobs: &[TransferJob]) -> Option<&TransferJob> { + jobs.iter().find(|x| x.id() == id) +} + pub async fn handle_read_jobs( jobs: &mut Vec, stream: &mut crate::Stream, -) -> ResultType<()> { +) -> ResultType { + let mut job_log = Default::default(); let mut finished = Vec::new(); for job in jobs.iter_mut() { if job.is_last_job { @@ -749,14 +786,16 @@ pub async fn handle_read_jobs( } Ok(None) => { if job.job_completed() { + job_log = serialize_transfer_job(job, true, false, ""); finished.push(job.id()); - let err = job.job_error(); - if err.is_some() { - stream - .send(&new_error(job.id(), err.unwrap(), job.file_num())) - .await?; - } else { - stream.send(&new_done(job.id(), job.file_num())).await?; + match job.job_error() { + Some(err) => { + job_log = serialize_transfer_job(job, false, false, &err); + stream + .send(&new_error(job.id(), err, job.file_num())) + .await? + } + None => stream.send(&new_done(job.id(), job.file_num())).await?, } } else { // waiting confirmation. @@ -767,7 +806,7 @@ pub async fn handle_read_jobs( for id in finished { remove_job(id, jobs); } - Ok(()) + Ok(job_log) } pub fn remove_all_empty_dir(path: &PathBuf) -> ResultType<()> { @@ -823,17 +862,39 @@ pub fn is_write_need_confirmation( let modified_time = metadata.modified()?; let remote_mt = Duration::from_secs(digest.last_modified); let local_mt = modified_time.duration_since(UNIX_EPOCH)?; + // [Note] + // We decide to give the decision whether to override the existing file to users, + // which obey the behavior of the file manager in our system. + let mut is_identical = false; if remote_mt == local_mt && digest.file_size == metadata.len() { - return Ok(DigestCheckResult::IsSame); + is_identical = true; } Ok(DigestCheckResult::NeedConfirm(FileTransferDigest { id: digest.id, file_num: digest.file_num, last_modified: local_mt.as_secs(), file_size: metadata.len(), + is_identical, ..Default::default() })) } else { Ok(DigestCheckResult::NoSuchFile) } } + +pub fn serialize_transfer_jobs(jobs: &[TransferJob]) -> String { + let mut v = vec![]; + for job in jobs { + let value = serde_json::to_value(job).unwrap_or_default(); + v.push(value); + } + serde_json::to_string(&v).unwrap_or_default() +} + +pub fn serialize_transfer_job(job: &TransferJob, done: bool, cancel: bool, error: &str) -> String { + let mut value = serde_json::to_value(job).unwrap_or_default(); + value["done"] = json!(done); + value["cancel"] = json!(cancel); + value["error"] = json!(error); + serde_json::to_string(&value).unwrap_or_default() +} diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index cf1e45ed..e802730a 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -1,4 +1,5 @@ pub mod compress; +pub mod platform; pub mod protos; pub use bytes; use config::Config; @@ -39,12 +40,21 @@ pub use tokio_socks::TargetAddr; pub mod password_security; pub use chrono; pub use directories_next; +pub use libc; pub mod keyboard; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub use dlopen; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub use machine_uid; +pub use sysinfo; +pub use toml; +pub use uuid; #[cfg(feature = "quic")] pub type Stream = quic::Connection; #[cfg(not(feature = "quic"))] pub type Stream = tcp::FramedStream; +pub type SessionID = uuid::Uuid; #[inline] pub async fn sleep(sec: f32) { @@ -117,7 +127,7 @@ impl AddrMangle { SocketAddr::V4(addr_v4) => { let tm = (SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap() + .unwrap_or(std::time::Duration::ZERO) .as_micros() as u32) as u128; let ip = u32::from_le_bytes(addr_v4.ip().octets()) as u128; let port = addr.port() as u128; @@ -150,9 +160,9 @@ impl AddrMangle { if bytes.len() != 18 { return Config::get_any_listen_addr(false); } - let tmp: [u8; 2] = bytes[16..].try_into().unwrap(); + let tmp: [u8; 2] = bytes[16..].try_into().unwrap_or_default(); let port = u16::from_le_bytes(tmp); - let tmp: [u8; 16] = bytes[..16].try_into().unwrap(); + let tmp: [u8; 16] = bytes[..16].try_into().unwrap_or_default(); let ip = std::net::Ipv6Addr::from(tmp); return SocketAddr::new(IpAddr::V6(ip), port); } @@ -230,11 +240,31 @@ pub fn is_valid_custom_id(id: &str) -> bool { .is_match(id) } +// Support 1.1.10-1, the number after - is a patch version. pub fn get_version_number(v: &str) -> i64 { + let mut versions = v.split('-'); + let mut n = 0; - for x in v.split('.') { - n = n * 1000 + x.parse::().unwrap_or(0); + + // The first part is the version number. + // 1.1.10 -> 1001100, 1.2.3 -> 1001030, multiple the last number by 10 + // to leave space for patch version. + if let Some(v) = versions.next() { + let mut last = 0; + for x in v.split('.') { + last = x.parse::().unwrap_or(0); + n = n * 1000 + last; + } + n -= last; + n += last * 10; + } + + if let Some(v) = versions.next() { + n += v.parse::().unwrap_or(0); } + + // Ignore the rest + n } @@ -280,16 +310,24 @@ pub fn get_time() -> i64 { #[inline] pub fn is_ipv4_str(id: &str) -> bool { - regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+(:\d+)?$") - .unwrap() - .is_match(id) + if let Ok(reg) = regex::Regex::new( + r"^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:\d+)?$", + ) { + reg.is_match(id) + } else { + false + } } #[inline] pub fn is_ipv6_str(id: &str) -> bool { - regex::Regex::new(r"^((([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4})|(\[([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4}\]:\d+))$") - .unwrap() - .is_match(id) + if let Ok(reg) = regex::Regex::new( + r"^((([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4})|(\[([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4}\]:\d+))$", + ) { + reg.is_match(id) + } else { + false + } } #[inline] @@ -302,11 +340,58 @@ pub fn is_domain_port_str(id: &str) -> bool { // modified regex for RFC1123 hostname. check https://stackoverflow.com/a/106223 for original version for hostname. // according to [TLD List](https://data.iana.org/TLD/tlds-alpha-by-domain.txt) version 2023011700, // there is no digits in TLD, and length is 2~63. - regex::Regex::new( + if let Ok(reg) = regex::Regex::new( r"(?i)^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z-]{0,61}[a-z]:\d{1,5}$", - ) - .unwrap() - .is_match(id) + ) { + reg.is_match(id) + } else { + false + } +} + +pub fn init_log(_is_async: bool, _name: &str) -> Option { + static INIT: std::sync::Once = std::sync::Once::new(); + #[allow(unused_mut)] + let mut logger_holder: Option = None; + INIT.call_once(|| { + #[cfg(debug_assertions)] + { + use env_logger::*; + init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); + } + #[cfg(not(debug_assertions))] + { + // https://docs.rs/flexi_logger/latest/flexi_logger/error_info/index.html#write + // though async logger more efficient, but it also causes more problems, disable it for now + let mut path = config::Config::log_path(); + #[cfg(target_os = "android")] + if !config::Config::get_home().exists() { + return; + } + if !_name.is_empty() { + path.push(_name); + } + use flexi_logger::*; + if let Ok(x) = Logger::try_with_env_or_str("debug") { + logger_holder = x + .log_to_file(FileSpec::default().directory(path)) + .write_mode(if _is_async { + WriteMode::Async + } else { + WriteMode::Direct + }) + .format(opt_format) + .rotate( + Criterion::Age(Age::Day), + Naming::Timestamps, + Cleanup::KeepLogFiles(6), + ) + .start() + .ok(); + } + } + }); + logger_holder } #[cfg(test)] @@ -351,6 +436,22 @@ mod test { assert!(!is_ipv6_str("1:2::0]:1")); } + #[test] + fn test_ipv4() { + assert!(is_ipv4_str("1.2.3.4")); + assert!(is_ipv4_str("1.2.3.4:90")); + assert!(is_ipv4_str("192.168.0.1")); + assert!(is_ipv4_str("0.0.0.0")); + assert!(is_ipv4_str("255.255.255.255")); + assert!(!is_ipv4_str("256.0.0.0")); + assert!(!is_ipv4_str("256.256.256.256")); + assert!(!is_ipv4_str("1:2:")); + assert!(!is_ipv4_str("192.168.0.256")); + assert!(!is_ipv4_str("192.168.0.1/24")); + assert!(!is_ipv4_str("192.168.0.")); + assert!(!is_ipv4_str("192.168..1")); + } + #[test] fn test_hostname_port() { assert!(!is_domain_port_str("a:12")); @@ -387,4 +488,12 @@ mod test { let addr_v6 = "[::1]:8080".parse().unwrap(); assert_eq!(AddrMangle::decode(&AddrMangle::encode(addr_v6)), addr_v6); } + + #[test] + fn test_get_version_number() { + assert_eq!(get_version_number("1.1.10"), 1001100); + assert_eq!(get_version_number("1.1.10-1"), 1001101); + assert_eq!(get_version_number("1.1.11-1"), 1001111); + assert_eq!(get_version_number("1.2.3"), 1002030); + } } diff --git a/libs/hbb_common/src/password_security.rs b/libs/hbb_common/src/password_security.rs index ddfe28ba..9584ab6c 100644 --- a/libs/hbb_common/src/password_security.rs +++ b/libs/hbb_common/src/password_security.rs @@ -84,13 +84,16 @@ pub fn hide_cm() -> bool { const VERSION_LEN: usize = 2; -pub fn encrypt_str_or_original(s: &str, version: &str) -> String { +pub fn encrypt_str_or_original(s: &str, version: &str, max_len: usize) -> String { if decrypt_str_or_original(s, version).1 { log::error!("Duplicate encryption!"); return s.to_owned(); } + if s.bytes().len() > max_len { + return String::default(); + } if version == "00" { - if let Ok(s) = encrypt(s.as_bytes()) { + if let Ok(s) = encrypt(s.as_bytes(), max_len) { return version.to_owned() + &s; } } @@ -100,15 +103,16 @@ pub fn encrypt_str_or_original(s: &str, version: &str) -> String { // String: password // bool: whether decryption is successful // bool: whether should store to re-encrypt when load +// note: s.len() return length in bytes, s.chars().count() return char count +// &[..2] return the left 2 bytes, s.chars().take(2) return the left 2 chars pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool, bool) { if s.len() > VERSION_LEN { - let version = &s[..VERSION_LEN]; - if version == "00" { + if s.starts_with("00") { if let Ok(v) = decrypt(s[VERSION_LEN..].as_bytes()) { return ( String::from_utf8_lossy(&v).to_string(), true, - version != current_version, + "00" != current_version, ); } } @@ -117,13 +121,16 @@ pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool, (s.to_owned(), false, !s.is_empty()) } -pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec { +pub fn encrypt_vec_or_original(v: &[u8], version: &str, max_len: usize) -> Vec { if decrypt_vec_or_original(v, version).1 { log::error!("Duplicate encryption!"); return v.to_owned(); } + if v.len() > max_len { + return vec![]; + } if version == "00" { - if let Ok(s) = encrypt(v) { + if let Ok(s) = encrypt(v, max_len) { let mut version = version.to_owned().into_bytes(); version.append(&mut s.into_bytes()); return version; @@ -148,8 +155,8 @@ pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec, boo (v.to_owned(), false, !v.is_empty()) } -fn encrypt(v: &[u8]) -> Result { - if !v.is_empty() { +fn encrypt(v: &[u8], max_len: usize) -> Result { + if !v.is_empty() && v.len() <= max_len { symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original)) } else { Err(()) @@ -164,7 +171,7 @@ fn decrypt(v: &[u8]) -> Result, ()> { } } -fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result, ()> { +pub fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result, ()> { use sodiumoxide::crypto::secretbox; use std::convert::TryInto; @@ -185,12 +192,15 @@ mod test { #[test] fn test() { use super::*; + use rand::{thread_rng, Rng}; + use std::time::Instant; let version = "00"; + let max_len = 128; println!("test str"); - let data = "Hello World"; - let encrypted = encrypt_str_or_original(data, version); + let data = "1ü1111"; + let encrypted = encrypt_str_or_original(data, version, max_len); let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version); println!("data: {data}"); println!("encrypted: {encrypted}"); @@ -202,11 +212,14 @@ mod test { let (_, _, store) = decrypt_str_or_original(&encrypted, "99"); assert!(store); assert!(!decrypt_str_or_original(&decrypted, version).1); - assert_eq!(encrypt_str_or_original(&encrypted, version), encrypted); + assert_eq!( + encrypt_str_or_original(&encrypted, version, max_len), + encrypted + ); println!("test vec"); - let data: Vec = vec![1, 2, 3, 4, 5, 6]; - let encrypted = encrypt_vec_or_original(&data, version); + let data: Vec = "1ü1111".as_bytes().to_vec(); + let encrypted = encrypt_vec_or_original(&data, version, max_len); let (decrypted, succ, store) = decrypt_vec_or_original(&encrypted, version); println!("data: {data:?}"); println!("encrypted: {encrypted:?}"); @@ -218,7 +231,10 @@ mod test { let (_, _, store) = decrypt_vec_or_original(&encrypted, "99"); assert!(store); assert!(!decrypt_vec_or_original(&decrypted, version).1); - assert_eq!(encrypt_vec_or_original(&encrypted, version), encrypted); + assert_eq!( + encrypt_vec_or_original(&encrypted, version, max_len), + encrypted + ); println!("test original"); let data = version.to_string() + "Hello World"; @@ -238,5 +254,42 @@ mod test { let (_, succ, store) = decrypt_vec_or_original(&[], version); assert!(!store); assert!(!succ); + let data = "1ü1111"; + assert_eq!(decrypt_str_or_original(data, version).0, data); + let data: Vec = "1ü1111".as_bytes().to_vec(); + assert_eq!(decrypt_vec_or_original(&data, version).0, data); + + println!("test speed"); + let test_speed = |len: usize, name: &str| { + let mut data: Vec = vec![]; + let mut rng = thread_rng(); + for _ in 0..len { + data.push(rng.gen_range(0..255)); + } + let start: Instant = Instant::now(); + let encrypted = encrypt_vec_or_original(&data, version, len); + assert_ne!(data, decrypted); + let t1 = start.elapsed(); + let start = Instant::now(); + let (decrypted, _, _) = decrypt_vec_or_original(&encrypted, version); + let t2 = start.elapsed(); + assert_eq!(data, decrypted); + println!("{name}"); + println!("encrypt:{:?}, decrypt:{:?}", t1, t2); + + let start: Instant = Instant::now(); + let encrypted = base64::encode(&data, base64::Variant::Original); + let t1 = start.elapsed(); + let start = Instant::now(); + let decrypted = base64::decode(&encrypted, base64::Variant::Original).unwrap(); + let t2 = start.elapsed(); + assert_eq!(data, decrypted); + println!("base64, encrypt:{:?}, decrypt:{:?}", t1, t2,); + }; + test_speed(128, "128"); + test_speed(1024, "1k"); + test_speed(1024 * 1024, "1M"); + test_speed(10 * 1024 * 1024, "10M"); + test_speed(100 * 1024 * 1024, "100M"); } } diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs new file mode 100644 index 00000000..f7a6244d --- /dev/null +++ b/libs/hbb_common/src/platform/linux.rs @@ -0,0 +1,285 @@ +use crate::ResultType; +use std::{collections::HashMap, process::Command}; + +lazy_static::lazy_static! { + pub static ref DISTRO: Distro = Distro::new(); +} + +pub const DISPLAY_SERVER_WAYLAND: &str = "wayland"; +pub const DISPLAY_SERVER_X11: &str = "x11"; + +pub struct Distro { + pub name: String, + pub version_id: String, +} + +impl Distro { + fn new() -> Self { + let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release") + .unwrap_or_default() + .trim() + .trim_matches('"') + .to_string(); + let version_id = run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release") + .unwrap_or_default() + .trim() + .trim_matches('"') + .to_string(); + Self { name, version_id } + } +} + +#[inline] +pub fn is_gdm_user(username: &str) -> bool { + username == "gdm" + // || username == "lightgdm" +} + +#[inline] +pub fn is_desktop_wayland() -> bool { + get_display_server() == DISPLAY_SERVER_WAYLAND +} + +#[inline] +pub fn is_x11_or_headless() -> bool { + !is_desktop_wayland() +} + +// -1 +const INVALID_SESSION: &str = "4294967295"; + +pub fn get_display_server() -> String { + let mut session = get_values_of_seat0(&[0])[0].clone(); + if session.is_empty() { + // loginctl has not given the expected output. try something else. + if let Ok(sid) = std::env::var("XDG_SESSION_ID") { + // could also execute "cat /proc/self/sessionid" + session = sid; + } + if session.is_empty() { + session = run_cmds("cat /proc/self/sessionid").unwrap_or_default(); + if session == INVALID_SESSION { + session = "".to_owned(); + } + } + } + if session.is_empty() { + "".to_owned() + } else { + get_display_server_of_session(&session) + } +} + +pub fn get_display_server_of_session(session: &str) -> String { + let mut display_server = if let Ok(output) = + run_loginctl(Some(vec!["show-session", "-p", "Type", session])) + // Check session type of the session + { + let display_server = String::from_utf8_lossy(&output.stdout) + .replace("Type=", "") + .trim_end() + .into(); + if display_server == "tty" { + // If the type is tty... + if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "TTY", session])) + // Get the tty number + { + let tty: String = String::from_utf8_lossy(&output.stdout) + .replace("TTY=", "") + .trim_end() + .into(); + if let Ok(xorg_results) = run_cmds(&format!("ps -e | grep \"{tty}.\\\\+Xorg\"")) + // And check if Xorg is running on that tty + { + if xorg_results.trim_end() != "" { + // If it is, manually return "x11", otherwise return tty + return "x11".to_owned(); + } + } + } + } + display_server + } else { + "".to_owned() + }; + if display_server.is_empty() || display_server == "tty" { + // loginctl has not given the expected output. try something else. + if let Ok(sestype) = std::env::var("XDG_SESSION_TYPE") { + display_server = sestype; + } + } + if display_server == "" { + display_server = "x11".to_owned(); + } + display_server.to_lowercase() +} + +#[inline] +fn line_values(indices: &[usize], line: &str) -> Vec { + indices + .into_iter() + .map(|idx| line.split_whitespace().nth(*idx).unwrap_or("").to_owned()) + .collect::>() +} + +#[inline] +pub fn get_values_of_seat0(indices: &[usize]) -> Vec { + _get_values_of_seat0(indices, true) +} + +#[inline] +pub fn get_values_of_seat0_with_gdm_wayland(indices: &[usize]) -> Vec { + _get_values_of_seat0(indices, false) +} + +fn _get_values_of_seat0(indices: &[usize], ignore_gdm_wayland: bool) -> Vec { + if let Ok(output) = run_loginctl(None) { + for line in String::from_utf8_lossy(&output.stdout).lines() { + if line.contains("seat0") { + if let Some(sid) = line.split_whitespace().next() { + if is_active(sid) { + if ignore_gdm_wayland { + if is_gdm_user(line.split_whitespace().nth(2).unwrap_or("")) + && get_display_server_of_session(sid) == DISPLAY_SERVER_WAYLAND + { + continue; + } + } + return line_values(indices, line); + } + } + } + } + + // some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73 + for line in String::from_utf8_lossy(&output.stdout).lines() { + if let Some(sid) = line.split_whitespace().next() { + if is_active(sid) { + let d = get_display_server_of_session(sid); + if ignore_gdm_wayland { + if is_gdm_user(line.split_whitespace().nth(2).unwrap_or("")) + && d == DISPLAY_SERVER_WAYLAND + { + continue; + } + } + if d == "tty" { + continue; + } + return line_values(indices, line); + } + } + } + } + + line_values(indices, "") +} + +pub fn is_active(sid: &str) -> bool { + if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid])) { + String::from_utf8_lossy(&output.stdout).contains("active") + } else { + false + } +} + +pub fn is_active_and_seat0(sid: &str) -> bool { + if let Ok(output) = run_loginctl(Some(vec!["show-session", sid])) { + String::from_utf8_lossy(&output.stdout).contains("State=active") + && String::from_utf8_lossy(&output.stdout).contains("Seat=seat0") + } else { + false + } +} + +// **Note** that the return value here, the last character is '\n'. +// Use `run_cmds_trim_newline()` if you want to remove '\n' at the end. +pub fn run_cmds(cmds: &str) -> ResultType { + let output = std::process::Command::new("sh") + .args(vec!["-c", cmds]) + .output()?; + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} + +pub fn run_cmds_trim_newline(cmds: &str) -> ResultType { + let output = std::process::Command::new("sh") + .args(vec!["-c", cmds]) + .output()?; + let out = String::from_utf8_lossy(&output.stdout); + Ok(if out.ends_with('\n') { + out[..out.len() - 1].to_string() + } else { + out.to_string() + }) +} + +#[cfg(not(feature = "flatpak"))] +fn run_loginctl(args: Option>) -> std::io::Result { + let mut cmd = std::process::Command::new("loginctl"); + if let Some(a) = args { + return cmd.args(a).output(); + } + cmd.output() +} + +#[cfg(feature = "flatpak")] +fn run_loginctl(args: Option>) -> std::io::Result { + let mut l_args = String::from("loginctl"); + if let Some(a) = args { + l_args = format!("{} {}", l_args, a.join(" ")); + } + std::process::Command::new("flatpak-spawn") + .args(vec![String::from("--host"), l_args]) + .output() +} + +/// forever: may not work +#[cfg(target_os = "linux")] +pub fn system_message(title: &str, msg: &str, forever: bool) -> ResultType<()> { + let cmds: HashMap<&str, Vec<&str>> = HashMap::from([ + ("notify-send", [title, msg].to_vec()), + ( + "zenity", + [ + "--info", + "--timeout", + if forever { "0" } else { "3" }, + "--title", + title, + "--text", + msg, + ] + .to_vec(), + ), + ("kdialog", ["--title", title, "--msgbox", msg].to_vec()), + ( + "xmessage", + [ + "-center", + "-timeout", + if forever { "0" } else { "3" }, + title, + msg, + ] + .to_vec(), + ), + ]); + for (k, v) in cmds { + if Command::new(k).args(v).spawn().is_ok() { + return Ok(()); + } + } + crate::bail!("failed to post system message"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_run_cmds_trim_newline() { + assert_eq!(run_cmds_trim_newline("echo -n 123").unwrap(), "123"); + assert_eq!(run_cmds_trim_newline("echo 123").unwrap(), "123"); + assert_eq!(run_cmds_trim_newline("whoami").unwrap() + "\n", run_cmds("whoami").unwrap()); + } +} diff --git a/libs/hbb_common/src/platform/macos.rs b/libs/hbb_common/src/platform/macos.rs new file mode 100644 index 00000000..dd83a873 --- /dev/null +++ b/libs/hbb_common/src/platform/macos.rs @@ -0,0 +1,55 @@ +use crate::ResultType; +use osascript; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Serialize)] +struct AlertParams { + title: String, + message: String, + alert_type: String, + buttons: Vec, +} + +#[derive(Deserialize)] +struct AlertResult { + #[serde(rename = "buttonReturned")] + button: String, +} + +/// Firstly run the specified app, then alert a dialog. Return the clicked button value. +/// +/// # Arguments +/// +/// * `app` - The app to execute the script. +/// * `alert_type` - Alert type. . informational, warning, critical +/// * `title` - The alert title. +/// * `message` - The alert message. +/// * `buttons` - The buttons to show. +pub fn alert( + app: String, + alert_type: String, + title: String, + message: String, + buttons: Vec, +) -> ResultType { + let script = osascript::JavaScript::new(&format!( + " + var App = Application('{}'); + App.includeStandardAdditions = true; + return App.displayAlert($params.title, {{ + message: $params.message, + 'as': $params.alert_type, + buttons: $params.buttons, + }}); + ", + app + )); + + let result: AlertResult = script.execute_with_params(AlertParams { + title, + message, + alert_type, + buttons, + })?; + Ok(result.button) +} diff --git a/libs/hbb_common/src/platform/mod.rs b/libs/hbb_common/src/platform/mod.rs new file mode 100644 index 00000000..5dc004a8 --- /dev/null +++ b/libs/hbb_common/src/platform/mod.rs @@ -0,0 +1,81 @@ +#[cfg(target_os = "linux")] +pub mod linux; + +#[cfg(target_os = "macos")] +pub mod macos; + +#[cfg(target_os = "windows")] +pub mod windows; + +#[cfg(not(debug_assertions))] +use crate::{config::Config, log}; +#[cfg(not(debug_assertions))] +use std::process::exit; + +#[cfg(not(debug_assertions))] +static mut GLOBAL_CALLBACK: Option> = None; + +#[cfg(not(debug_assertions))] +extern "C" fn breakdown_signal_handler(sig: i32) { + let mut stack = vec![]; + backtrace::trace(|frame| { + backtrace::resolve_frame(frame, |symbol| { + if let Some(name) = symbol.name() { + stack.push(name.to_string()); + } + }); + true // keep going to the next frame + }); + let mut info = String::default(); + if stack.iter().any(|s| { + s.contains(&"nouveau_pushbuf_kick") + || s.to_lowercase().contains("nvidia") + || s.contains("gdk_window_end_draw_frame") + || s.contains("glGetString") + }) { + Config::set_option("allow-always-software-render".to_string(), "Y".to_string()); + info = "Always use software rendering will be set.".to_string(); + log::info!("{}", info); + } + if stack.iter().any(|s| { + s.to_lowercase().contains("nvidia") + || s.to_lowercase().contains("amf") + || s.to_lowercase().contains("mfx") + || s.contains("cuProfilerStop") + }) { + Config::set_option("enable-hwcodec".to_string(), "N".to_string()); + info = "Perhaps hwcodec causing the crash, disable it first".to_string(); + log::info!("{}", info); + } + log::error!( + "Got signal {} and exit. stack:\n{}", + sig, + stack.join("\n").to_string() + ); + if !info.is_empty() { + #[cfg(target_os = "linux")] + linux::system_message( + "RustDesk", + &format!("Got signal {} and exit.{}", sig, info), + true, + ) + .ok(); + } + unsafe { + if let Some(callback) = &GLOBAL_CALLBACK { + callback() + } + } + exit(0); +} + +#[cfg(not(debug_assertions))] +pub fn register_breakdown_handler(callback: T) +where + T: Fn() + 'static, +{ + unsafe { + GLOBAL_CALLBACK = Some(Box::new(callback)); + libc::signal(libc::SIGSEGV, breakdown_signal_handler as _); + } +} diff --git a/libs/hbb_common/src/platform/windows.rs b/libs/hbb_common/src/platform/windows.rs new file mode 100644 index 00000000..c36c384d --- /dev/null +++ b/libs/hbb_common/src/platform/windows.rs @@ -0,0 +1,199 @@ +use std::{ + collections::VecDeque, + os::windows::raw::HANDLE, + sync::{Arc, Mutex}, + time::Instant, +}; +use winapi::{ + shared::minwindef::{DWORD, FALSE, TRUE}, + um::{ + handleapi::CloseHandle, + pdh::{ + PdhAddEnglishCounterA, PdhCloseQuery, PdhCollectQueryData, PdhCollectQueryDataEx, + PdhGetFormattedCounterValue, PdhOpenQueryA, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, + PDH_HCOUNTER, PDH_HQUERY, + }, + synchapi::{CreateEventA, WaitForSingleObject}, + sysinfoapi::VerSetConditionMask, + winbase::{VerifyVersionInfoW, INFINITE, WAIT_OBJECT_0}, + winnt::{ + OSVERSIONINFOEXW, VER_BUILDNUMBER, VER_GREATER_EQUAL, VER_MAJORVERSION, + VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR, + }, + }, +}; + +lazy_static::lazy_static! { + static ref CPU_USAGE_ONE_MINUTE: Arc>> = Arc::new(Mutex::new(None)); +} + +// https://github.com/mgostIH/process_list/blob/master/src/windows/mod.rs +#[repr(transparent)] +pub struct RAIIHandle(pub HANDLE); + +impl Drop for RAIIHandle { + fn drop(&mut self) { + // This never gives problem except when running under a debugger. + unsafe { CloseHandle(self.0) }; + } +} + +#[repr(transparent)] +pub(self) struct RAIIPDHQuery(pub PDH_HQUERY); + +impl Drop for RAIIPDHQuery { + fn drop(&mut self) { + unsafe { PdhCloseQuery(self.0) }; + } +} + +pub fn start_cpu_performance_monitor() { + // Code from: + // https://learn.microsoft.com/en-us/windows/win32/perfctrs/collecting-performance-data + // https://learn.microsoft.com/en-us/windows/win32/api/pdh/nf-pdh-pdhcollectquerydataex + // Why value lower than taskManager: + // https://aaron-margosis.medium.com/task-managers-cpu-numbers-are-all-but-meaningless-2d165b421e43 + // Therefore we should compare with Precess Explorer rather than taskManager + + let f = || unsafe { + // load avg or cpu usage, test with prime95. + // Prefer cpu usage because we can get accurate value from Precess Explorer. + // const COUNTER_PATH: &'static str = "\\System\\Processor Queue Length\0"; + const COUNTER_PATH: &'static str = "\\Processor(_total)\\% Processor Time\0"; + const SAMPLE_INTERVAL: DWORD = 2; // 2 second + + let mut ret; + let mut query: PDH_HQUERY = std::mem::zeroed(); + ret = PdhOpenQueryA(std::ptr::null() as _, 0, &mut query); + if ret != 0 { + log::error!("PdhOpenQueryA failed: 0x{:X}", ret); + return; + } + let _query = RAIIPDHQuery(query); + let mut counter: PDH_HCOUNTER = std::mem::zeroed(); + ret = PdhAddEnglishCounterA(query, COUNTER_PATH.as_ptr() as _, 0, &mut counter); + if ret != 0 { + log::error!("PdhAddEnglishCounterA failed: 0x{:X}", ret); + return; + } + ret = PdhCollectQueryData(query); + if ret != 0 { + log::error!("PdhCollectQueryData failed: 0x{:X}", ret); + return; + } + let mut _counter_type: DWORD = 0; + let mut counter_value: PDH_FMT_COUNTERVALUE = std::mem::zeroed(); + let event = CreateEventA(std::ptr::null_mut(), FALSE, FALSE, std::ptr::null() as _); + if event.is_null() { + log::error!("CreateEventA failed"); + return; + } + let _event: RAIIHandle = RAIIHandle(event); + ret = PdhCollectQueryDataEx(query, SAMPLE_INTERVAL, event); + if ret != 0 { + log::error!("PdhCollectQueryDataEx failed: 0x{:X}", ret); + return; + } + + let mut queue: VecDeque = VecDeque::new(); + let mut recent_valid: VecDeque = VecDeque::new(); + loop { + // latest one minute + if queue.len() == 31 { + queue.pop_front(); + } + if recent_valid.len() == 31 { + recent_valid.pop_front(); + } + // allow get value within one minute + if queue.len() > 0 && recent_valid.iter().filter(|v| **v).count() > queue.len() / 2 { + let sum: f64 = queue.iter().map(|f| f.to_owned()).sum(); + let avg = sum / (queue.len() as f64); + *CPU_USAGE_ONE_MINUTE.lock().unwrap() = Some((avg, Instant::now())); + } else { + *CPU_USAGE_ONE_MINUTE.lock().unwrap() = None; + } + if WAIT_OBJECT_0 != WaitForSingleObject(event, INFINITE) { + recent_valid.push_back(false); + continue; + } + if PdhGetFormattedCounterValue( + counter, + PDH_FMT_DOUBLE, + &mut _counter_type, + &mut counter_value, + ) != 0 + || counter_value.CStatus != 0 + { + recent_valid.push_back(false); + continue; + } + queue.push_back(counter_value.u.doubleValue().clone()); + recent_valid.push_back(true); + } + }; + use std::sync::Once; + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + std::thread::spawn(f); + }); +} + +pub fn cpu_uage_one_minute() -> Option { + let v = CPU_USAGE_ONE_MINUTE.lock().unwrap().clone(); + if let Some((v, instant)) = v { + if instant.elapsed().as_secs() < 30 { + return Some(v); + } + } + None +} + +pub fn sync_cpu_usage(cpu_usage: Option) { + let v = match cpu_usage { + Some(cpu_usage) => Some((cpu_usage, Instant::now())), + None => None, + }; + *CPU_USAGE_ONE_MINUTE.lock().unwrap() = v; + log::info!("cpu usage synced: {:?}", cpu_usage); +} + +// https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 +// https://github.com/nodejs/node-convergence-archive/blob/e11fe0c2777561827cdb7207d46b0917ef3c42a7/deps/uv/src/win/util.c#L780 +pub fn is_windows_version_or_greater( + os_major: u32, + os_minor: u32, + build_number: u32, + service_pack_major: u32, + service_pack_minor: u32, +) -> bool { + let mut osvi: OSVERSIONINFOEXW = unsafe { std::mem::zeroed() }; + osvi.dwOSVersionInfoSize = std::mem::size_of::() as DWORD; + osvi.dwMajorVersion = os_major as _; + osvi.dwMinorVersion = os_minor as _; + osvi.dwBuildNumber = build_number as _; + osvi.wServicePackMajor = service_pack_major as _; + osvi.wServicePackMinor = service_pack_minor as _; + + let result = unsafe { + let mut condition_mask = 0; + let op = VER_GREATER_EQUAL; + condition_mask = VerSetConditionMask(condition_mask, VER_MAJORVERSION, op); + condition_mask = VerSetConditionMask(condition_mask, VER_MINORVERSION, op); + condition_mask = VerSetConditionMask(condition_mask, VER_BUILDNUMBER, op); + condition_mask = VerSetConditionMask(condition_mask, VER_SERVICEPACKMAJOR, op); + condition_mask = VerSetConditionMask(condition_mask, VER_SERVICEPACKMINOR, op); + + VerifyVersionInfoW( + &mut osvi as *mut OSVERSIONINFOEXW, + VER_MAJORVERSION + | VER_MINORVERSION + | VER_BUILDNUMBER + | VER_SERVICEPACKMAJOR + | VER_SERVICEPACKMINOR, + condition_mask, + ) + }; + + result == TRUE +} diff --git a/libs/hbb_common/src/tcp.rs b/libs/hbb_common/src/tcp.rs index f574e830..56fa29ca 100644 --- a/libs/hbb_common/src/tcp.rs +++ b/libs/hbb_common/src/tcp.rs @@ -3,7 +3,10 @@ use anyhow::Context as AnyhowCtx; use bytes::{BufMut, Bytes, BytesMut}; use futures::{SinkExt, StreamExt}; use protobuf::Message; -use sodiumoxide::crypto::secretbox::{self, Key, Nonce}; +use sodiumoxide::crypto::{ + box_, + secretbox::{self, Key, Nonce}, +}; use std::{ io::{self, Error, ErrorKind}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, @@ -21,10 +24,13 @@ use tokio_util::codec::Framed; pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {} pub struct DynTcpStream(Box); +#[derive(Clone)] +pub struct Encrypt(Key, u64, u64); + pub struct FramedStream( Framed, SocketAddr, - Option<(Key, u64, u64)>, + Option, u64, ); @@ -66,8 +72,8 @@ fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result) -> ResultType<()> { let mut msg = msg; if let Some(key) = self.2.as_mut() { - key.1 += 1; - let nonce = Self::get_nonce(key.1); - msg = secretbox::seal(&msg, &nonce, &key.0); + msg = key.enc(&msg); } self.send_bytes(bytes::Bytes::from(msg)).await?; Ok(()) @@ -206,18 +210,10 @@ impl FramedStream { #[inline] pub async fn next(&mut self) -> Option> { let mut res = self.0.next().await; - if let Some(key) = self.2.as_mut() { - if let Some(Ok(bytes)) = res.as_mut() { - key.2 += 1; - let nonce = Self::get_nonce(key.2); - match secretbox::open(bytes, &nonce, &key.0) { - Ok(res) => { - bytes.clear(); - bytes.put_slice(&res); - } - Err(()) => { - return Some(Err(Error::new(ErrorKind::Other, "decryption error"))); - } + if let Some(Ok(bytes)) = res.as_mut() { + if let Some(key) = self.2.as_mut() { + if let Err(err) = key.dec(bytes) { + return Some(Err(err)); } } } @@ -234,7 +230,7 @@ impl FramedStream { } pub fn set_key(&mut self, key: Key) { - self.2 = Some((key, 0, 0)); + self.2 = Some(Encrypt::new(key)); } fn get_nonce(seqnum: u64) -> Nonce { @@ -264,6 +260,8 @@ pub async fn listen_any(port: u16) -> ResultType { if let Ok(mut socket) = TcpSocket::new_v6() { #[cfg(unix)] { + socket.set_reuseport(true).ok(); + socket.set_reuseaddr(true).ok(); use std::os::unix::io::{FromRawFd, IntoRawFd}; let raw_fd = socket.into_raw_fd(); let sock2 = unsafe { socket2::Socket::from_raw_fd(raw_fd) }; @@ -287,9 +285,11 @@ pub async fn listen_any(port: u16) -> ResultType { } } } - let s = TcpSocket::new_v4()?; - s.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port))?; - Ok(s.listen(DEFAULT_BACKLOG)?) + Ok(new_socket( + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + true, + )? + .listen(DEFAULT_BACKLOG)?) } impl Unpin for DynTcpStream {} @@ -323,3 +323,53 @@ impl AsyncWrite for DynTcpStream { } impl TcpStreamTrait for R {} + +impl Encrypt { + pub fn new(key: Key) -> Self { + Self(key, 0, 0) + } + + pub fn dec(&mut self, bytes: &mut bytes::BytesMut) -> Result<(), Error> { + if bytes.len() <= 1 { + return Ok(()); + } + self.2 += 1; + let nonce = FramedStream::get_nonce(self.2); + match secretbox::open(bytes, &nonce, &self.0) { + Ok(res) => { + bytes.clear(); + bytes.put_slice(&res); + Ok(()) + } + Err(()) => Err(Error::new(ErrorKind::Other, "decryption error")), + } + } + + pub fn enc(&mut self, data: &[u8]) -> Vec { + self.1 += 1; + let nonce = FramedStream::get_nonce(self.1); + secretbox::seal(&data, &nonce, &self.0) + } + + pub fn decode( + symmetric_data: &[u8], + their_pk_b: &[u8], + our_sk_b: &box_::SecretKey, + ) -> ResultType { + if their_pk_b.len() != box_::PUBLICKEYBYTES { + anyhow::bail!("Handshake failed: pk length {}", their_pk_b.len()); + } + let nonce = box_::Nonce([0u8; box_::NONCEBYTES]); + let mut pk_ = [0u8; box_::PUBLICKEYBYTES]; + pk_[..].copy_from_slice(their_pk_b); + let their_pk_b = box_::PublicKey(pk_); + let symmetric_key = box_::open(symmetric_data, &nonce, &their_pk_b, &our_sk_b) + .map_err(|_| anyhow::anyhow!("Handshake failed: box decryption failure"))?; + if symmetric_key.len() != secretbox::KEYBYTES { + anyhow::bail!("Handshake failed: invalid secret key length from peer"); + } + let mut key = [0u8; secretbox::KEYBYTES]; + key[..].copy_from_slice(&symmetric_key); + Ok(Key(key)) + } +} diff --git a/libs/hbb_common/src/udp.rs b/libs/hbb_common/src/udp.rs index bb0d071a..68abd42d 100644 --- a/libs/hbb_common/src/udp.rs +++ b/libs/hbb_common/src/udp.rs @@ -24,15 +24,15 @@ fn new_socket(addr: SocketAddr, reuse: bool, buf_size: usize) -> Result 0 { socket.set_recv_buffer_size(buf_size).ok(); } - log::info!( + log::debug!( "Receive buf size of udp {}: {:?}", addr, socket.recv_buffer_size() diff --git a/src/rendezvous_server.rs b/src/rendezvous_server.rs index 0c0d1075..2d5d50dc 100644 --- a/src/rendezvous_server.rs +++ b/src/rendezvous_server.rs @@ -1,5 +1,6 @@ use crate::common::*; use crate::peer::*; +use hbb_common::bytes::BufMut; use hbb_common::{ allow_err, bail, bytes::{Bytes, BytesMut}, @@ -16,6 +17,10 @@ use hbb_common::{ register_pk_response::Result::{TOO_FREQUENT, UUID_MISMATCH}, *, }, + sodiumoxide::crypto::{ + box_, box_::PublicKey, box_::SecretKey, secretbox, secretbox::Key, secretbox::Nonce, sign, + }, + sodiumoxide::hex, tcp::{listen_any, FramedStream}, timeout, tokio::{ @@ -31,7 +36,7 @@ use hbb_common::{ AddrMangle, ResultType, }; use ipnetwork::Ipv4Network; -use sodiumoxide::crypto::sign; + use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, @@ -50,10 +55,44 @@ enum Data { const REG_TIMEOUT: i32 = 30_000; type TcpStreamSink = SplitSink, Bytes>; type WsSink = SplitSink, tungstenite::Message>; -enum Sink { +enum SinkType { TcpStream(TcpStreamSink), Ws(WsSink), } +struct Sink { + tx: SinkType, + key: Arc>>, +} +#[derive(Clone)] +struct Encrypt { + key: Key, + enc_seqnum: u64, + dec_seqnum: u64, +} + +// Light version of hbb_common::tcp::Encrypt +impl Encrypt { + pub fn dec(&mut self, bytes: &BytesMut) -> Result, ()> { + self.dec_seqnum += 1; + Ok(secretbox::open( + bytes, + &Self::get_nonce(self.dec_seqnum), + &self.key, + )?) + } + + pub fn enc(&mut self, data: &[u8]) -> Vec { + self.enc_seqnum += 1; + secretbox::seal(&data, &Self::get_nonce(self.enc_seqnum), &self.key) + } + + fn get_nonce(seqnum: u64) -> Nonce { + let mut nonce = Nonce([0u8; secretbox::NONCEBYTES]); + nonce.0[..std::mem::size_of_val(&seqnum)].copy_from_slice(&seqnum.to_le_bytes()); + nonce + } +} + type Sender = mpsc::UnboundedSender; type Receiver = mpsc::UnboundedReceiver; static ROTATION_RELAY_SERVER: AtomicUsize = AtomicUsize::new(0); @@ -69,6 +108,8 @@ struct Inner { mask: Option, local_ip: String, sk: Option, + secure_tcp_pk_b: PublicKey, + secure_tcp_sk_b: SecretKey, } #[derive(Clone)] @@ -119,6 +160,8 @@ impl RendezvousServer { .unwrap_or_default(), ) }; + // For privacy use per connection key pair + let (secure_tcp_pk_b, secure_tcp_sk_b) = box_::gen_keypair(); let mut rs = Self { tcp_punch: Arc::new(Mutex::new(HashMap::new())), pm, @@ -133,6 +176,8 @@ impl RendezvousServer { sk, mask, local_ip, + secure_tcp_pk_b, + secure_tcp_sk_b, }), }; log::info!("mask: {:?}", rs.inner.mask); @@ -554,6 +599,41 @@ impl RendezvousServer { }); Self::send_to_sink(sink, msg_out).await; } + Some(rendezvous_message::Union::KeyExchange(ex)) => { + log::trace!("KeyExchange {:?} <- bytes: {:?}", addr, hex::encode(&bytes)); + if ex.keys.len() != 2 { + log::error!("Handshake failed: invalid phase 2 key exchange message"); + return false; + } + log::trace!("KeyExchange their_pk: {:?}", hex::encode(&ex.keys[0])); + log::trace!("KeyExchange box: {:?}", hex::encode(&ex.keys[1])); + let their_pk: [u8; 32] = ex.keys[0].to_vec().try_into().unwrap(); + let cryptobox: [u8; 48] = ex.keys[1].to_vec().try_into().unwrap(); + let symetric_key = get_symetric_key_from_msg( + self.inner.secure_tcp_sk_b.0, + their_pk, + &cryptobox, + ); + log::debug!("KeyExchange symetric key: {:?}", hex::encode(&symetric_key)); + let key = secretbox::Key::from_slice(&symetric_key); + match key { + Some(key) => { + if let Some(sink) = sink.as_mut() { + sink.key.lock().await.replace(Encrypt { + key, + enc_seqnum: 0, + dec_seqnum: 0, + }); + } + log::debug!("KeyExchange symetric key set"); + return true; + } + None => { + log::error!("KeyExchange symetric key NOT set"); + return false; + } + } + } _ => {} } } @@ -803,12 +883,15 @@ impl RendezvousServer { #[inline] async fn send_to_sink(sink: &mut Option, msg: RendezvousMessage) { if let Some(sink) = sink.as_mut() { - if let Ok(bytes) = msg.write_to_bytes() { - match sink { - Sink::TcpStream(s) => { + if let Ok(mut bytes) = msg.write_to_bytes() { + if let Some(enc) = &mut sink.key.lock().await.as_mut() { + bytes = enc.enc(&bytes); + } + match &mut sink.tx { + SinkType::TcpStream(s) => { allow_err!(s.send(Bytes::from(bytes)).await); } - Sink::Ws(ws) => { + SinkType::Ws(ws) => { allow_err!(ws.send(tungstenite::Message::Binary(bytes)).await); } } @@ -1114,7 +1197,10 @@ impl RendezvousServer { if ws { let ws_stream = tokio_tungstenite::accept_async(stream).await?; let (a, mut b) = ws_stream.split(); - sink = Some(Sink::Ws(a)); + sink = Some(Sink { + tx: SinkType::Ws(a), + key: Arc::new(Mutex::new(None)), + }); while let Ok(Some(Ok(msg))) = timeout(30_000, b.next()).await { if let tungstenite::Message::Binary(bytes) = msg { if !self.handle_tcp(&bytes, &mut sink, addr, key, ws).await { @@ -1124,8 +1210,27 @@ impl RendezvousServer { } } else { let (a, mut b) = Framed::new(stream, BytesCodec::new()).split(); - sink = Some(Sink::TcpStream(a)); - while let Ok(Some(Ok(bytes))) = timeout(30_000, b.next()).await { + let enc = Arc::new(Mutex::new(None)); + sink = Some(Sink { + tx: SinkType::TcpStream(a), + key: enc.clone(), + }); + // Avoid key exchange if answering on nat helper port + if !key.is_empty() { + self.key_exchange_phase1(addr, &mut sink).await; + } + while let Ok(Some(Ok(mut bytes))) = timeout(30_000, b.next()).await { + let mut enc_lock = enc.lock().await; + if enc_lock.is_some() { + if let Ok(dec) = enc_lock.as_mut().unwrap().dec(&bytes) { + bytes.clear(); + bytes.put_slice(&dec); + } else { + log::warn!("Decryption error from {}", addr); + break; + } + } + drop(enc_lock); if !self.handle_tcp(&bytes, &mut sink, addr, key, ws).await { break; } @@ -1211,6 +1316,31 @@ impl RendezvousServer { } false } + + async fn key_exchange_phase1(&mut self, addr: SocketAddr, sink: &mut Option) { + let mut msg_out = RendezvousMessage::new(); + log::debug!("KeyExchange phase 1: send our pk for this tcp connection in a message signed with our server key"); + let sk = &self.inner.sk; + match sk { + Some(sk) => { + let our_pk_b = self.inner.secure_tcp_pk_b.clone(); + let sm = sign::sign(&our_pk_b.0, &sk); + + let bytes_sm = Bytes::from(sm); + msg_out.set_key_exchange(KeyExchange { + keys: vec![bytes_sm], + ..Default::default() + }); + log::trace!( + "KeyExchange {:?} -> bytes: {:?}", + addr, + hex::encode(Bytes::from(msg_out.write_to_bytes().unwrap())) + ); + Self::send_to_sink(sink, msg_out).await; + } + None => {} + } + } } async fn check_relay_servers(rs0: Arc, tx: Sender) { @@ -1310,3 +1440,22 @@ async fn create_tcp_listener(port: i32) -> ResultType { log::debug!("listen on tcp {:?}", s.local_addr()); Ok(s) } + +fn get_symetric_key_from_msg( + our_sk_b: [u8; 32], + their_pk_b: [u8; 32], + sealed_value: &[u8; 48], +) -> [u8; 32] { + let their_pk_b = box_::PublicKey(their_pk_b); + let nonce = box_::Nonce([0u8; box_::NONCEBYTES]); + let sk = box_::SecretKey(our_sk_b); + let key = box_::open(sealed_value, &nonce, &their_pk_b, &sk); + match key { + Ok(key) => { + let mut key_array = [0u8; 32]; + key_array.copy_from_slice(&key); + key_array + } + Err(e) => panic!("Error while opening the seal key{:?}", e), + } +} From 33ccf8eb242efc109e09142db2909672424f0b9c Mon Sep 17 00:00:00 2001 From: Ronan Date: Wed, 10 Apr 2024 20:19:21 +0200 Subject: [PATCH 2/4] common register_pk for WebSocket, TCP and UDP --- src/rendezvous_server.rs | 238 ++++++++++++++++++++++----------------- 1 file changed, 135 insertions(+), 103 deletions(-) diff --git a/src/rendezvous_server.rs b/src/rendezvous_server.rs index 2d5d50dc..76aef68e 100644 --- a/src/rendezvous_server.rs +++ b/src/rendezvous_server.rs @@ -14,7 +14,7 @@ use hbb_common::{ log, protobuf::{Message as _, MessageField}, rendezvous_proto::{ - register_pk_response::Result::{TOO_FREQUENT, UUID_MISMATCH}, + register_pk_response::Result::{INVALID_ID_FORMAT, TOO_FREQUENT, UUID_MISMATCH}, *, }, sodiumoxide::crypto::{ @@ -377,89 +377,25 @@ impl RendezvousServer { } } Some(rendezvous_message::Union::RegisterPk(rk)) => { - if rk.uuid.is_empty() || rk.pk.is_empty() { - return Ok(()); - } - let id = rk.id; - let ip = addr.ip().to_string(); - if id.len() < 6 { - return send_rk_res(socket, addr, UUID_MISMATCH).await; - } else if !self.check_ip_blocker(&ip, &id).await { - return send_rk_res(socket, addr, TOO_FREQUENT).await; - } - let peer = self.pm.get_or(&id).await; - let (changed, ip_changed) = { - let peer = peer.read().await; - if peer.uuid.is_empty() { - (true, false) - } else { - if peer.uuid == rk.uuid { - if peer.info.ip != ip && peer.pk != rk.pk { - log::warn!( - "Peer {} ip/pk mismatch: {}/{:?} vs {}/{:?}", - id, - ip, - rk.pk, - peer.info.ip, - peer.pk, - ); - drop(peer); - return send_rk_res(socket, addr, UUID_MISMATCH).await; - } - } else { - log::warn!( - "Peer {} uuid mismatch: {:?} vs {:?}", - id, - rk.uuid, - peer.uuid - ); - drop(peer); - return send_rk_res(socket, addr, UUID_MISMATCH).await; - } - let ip_changed = peer.info.ip != ip; - ( - peer.uuid != rk.uuid || peer.pk != rk.pk || ip_changed, - ip_changed, - ) + let response = self.handle_register_pk(rk, addr).await; + match response { + Err(err) => { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_register_pk_response(RegisterPkResponse { + result: err.into(), + ..Default::default() + }); + socket.send(&msg_out, addr).await?; } - }; - let mut req_pk = peer.read().await.reg_pk; - if req_pk.1.elapsed().as_secs() > 6 { - req_pk.0 = 0; - } else if req_pk.0 > 2 { - return send_rk_res(socket, addr, TOO_FREQUENT).await; - } - req_pk.0 += 1; - req_pk.1 = Instant::now(); - peer.write().await.reg_pk = req_pk; - if ip_changed { - let mut lock = IP_CHANGES.lock().await; - if let Some((tm, ips)) = lock.get_mut(&id) { - if tm.elapsed().as_secs() > IP_CHANGE_DUR { - *tm = Instant::now(); - ips.clear(); - ips.insert(ip.clone(), 1); - } else if let Some(v) = ips.get_mut(&ip) { - *v += 1; - } else { - ips.insert(ip.clone(), 1); - } - } else { - lock.insert( - id.clone(), - (Instant::now(), HashMap::from([(ip.clone(), 1)])), - ); + Ok(res) => { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_register_pk_response(RegisterPkResponse { + result: res.into(), + ..Default::default() + }); + socket.send(&msg_out, addr).await?; } } - if changed { - self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await; - } - let mut msg_out = RendezvousMessage::new(); - msg_out.set_register_pk_response(RegisterPkResponse { - result: register_pk_response::Result::OK.into(), - ..Default::default() - }); - socket.send(&msg_out, addr).await? } Some(rendezvous_message::Union::PunchHoleRequest(ph)) => { if self.pm.is_in_memory(&ph.id).await { @@ -590,14 +526,28 @@ impl RendezvousServer { msg_out.set_test_nat_response(res); Self::send_to_sink(sink, msg_out).await; } - Some(rendezvous_message::Union::RegisterPk(_)) => { - let res = register_pk_response::Result::NOT_SUPPORT; - let mut msg_out = RendezvousMessage::new(); - msg_out.set_register_pk_response(RegisterPkResponse { - result: res.into(), - ..Default::default() - }); - Self::send_to_sink(sink, msg_out).await; + Some(rendezvous_message::Union::RegisterPk(rk)) => { + let response = self.handle_register_pk(rk, addr).await; + match response { + Err(err) => { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_register_pk_response(RegisterPkResponse { + result: err.into(), + ..Default::default() + }); + Self::send_to_sink(sink, msg_out).await; + return false; + } + Ok(res) => { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_register_pk_response(RegisterPkResponse { + result: res.into(), + ..Default::default() + }); + Self::send_to_sink(sink, msg_out).await; + return true; + } + } } Some(rendezvous_message::Union::KeyExchange(ex)) => { log::trace!("KeyExchange {:?} <- bytes: {:?}", addr, hex::encode(&bytes)); @@ -640,6 +590,102 @@ impl RendezvousServer { false } + async fn handle_register_pk( + &mut self, + rk: RegisterPk, + addr: SocketAddr, + ) -> Result { + if rk.uuid.is_empty() || rk.pk.is_empty() { + return Err(INVALID_ID_FORMAT); + } + let id = rk.id; + let ip = addr.ip().to_string(); + if id.len() < 6 { + return Err(UUID_MISMATCH); + //return Err(send_rk_res(socket, addr, UUID_MISMATCH).await); + } else if !self.check_ip_blocker(&ip, &id).await { + return Err(TOO_FREQUENT); + //return Err(send_rk_res(socket, addr, TOO_FREQUENT).await); + } + let peer = self.pm.get_or(&id).await; + let (changed, ip_changed) = { + let peer = peer.read().await; + if peer.uuid.is_empty() { + (true, false) + } else { + if peer.uuid == rk.uuid { + if peer.info.ip != ip && peer.pk != rk.pk { + log::warn!( + "Peer {} ip/pk mismatch: {}/{:?} vs {}/{:?}", + id, + ip, + rk.pk, + peer.info.ip, + peer.pk, + ); + drop(peer); + return Err(UUID_MISMATCH); + //return Err(send_rk_res(socket, addr, UUID_MISMATCH).await); + } + } else { + log::warn!( + "Peer {} uuid mismatch: {:?} vs {:?}", + id, + rk.uuid, + peer.uuid + ); + drop(peer); + return Err(UUID_MISMATCH); + //return Err(send_rk_res(socket, addr, UUID_MISMATCH).await); + } + let ip_changed = peer.info.ip != ip; + ( + peer.uuid != rk.uuid || peer.pk != rk.pk || ip_changed, + ip_changed, + ) + } + }; + let mut req_pk = peer.read().await.reg_pk; + if req_pk.1.elapsed().as_secs() > 6 { + req_pk.0 = 0; + } else if req_pk.0 > 2 { + return Err(TOO_FREQUENT); + //return Err(send_rk_res(socket, addr, TOO_FREQUENT).await); + } + req_pk.0 += 1; + req_pk.1 = Instant::now(); + peer.write().await.reg_pk = req_pk; + if ip_changed { + let mut lock = IP_CHANGES.lock().await; + if let Some((tm, ips)) = lock.get_mut(&id) { + if tm.elapsed().as_secs() > IP_CHANGE_DUR { + *tm = Instant::now(); + ips.clear(); + ips.insert(ip.clone(), 1); + } else if let Some(v) = ips.get_mut(&ip) { + *v += 1; + } else { + ips.insert(ip.clone(), 1); + } + } else { + lock.insert( + id.clone(), + (Instant::now(), HashMap::from([(ip.clone(), 1)])), + ); + } + } + if changed { + self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await; + } + Ok(register_pk_response::Result::OK) + // let mut msg_out = RendezvousMessage::new(); + // msg_out.set_register_pk_response(RegisterPkResponse { + // result: register_pk_response::Result::OK.into(), + // ..Default::default() + // }); + // Ok(msg_out) + } + #[inline] async fn update_addr( &mut self, @@ -1408,20 +1454,6 @@ async fn test_hbbs(addr: SocketAddr) -> ResultType<()> { } } -#[inline] -async fn send_rk_res( - socket: &mut FramedSocket, - addr: SocketAddr, - res: register_pk_response::Result, -) -> ResultType<()> { - let mut msg_out = RendezvousMessage::new(); - msg_out.set_register_pk_response(RegisterPkResponse { - result: res.into(), - ..Default::default() - }); - socket.send(&msg_out, addr).await -} - async fn create_udp_listener(port: i32, rmem: usize) -> ResultType { let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port as _); if let Ok(s) = FramedSocket::new_reuse(&addr, false, rmem).await { From d4d032f9b32480f6e0cdf4de96143e53f633ca57 Mon Sep 17 00:00:00 2001 From: Ronan Date: Mon, 15 Apr 2024 16:05:04 +0200 Subject: [PATCH 3/4] makes the database compatible with rustdesk-server-pro:1.3.1 --- db_v2.sqlite3 | Bin 24576 -> 36864 bytes db_v2/migrations/20230720124454_peer.sql | 18 ++++++++++++++++++ src/database.rs | 20 ++++++++++++++------ 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 db_v2/migrations/20230720124454_peer.sql diff --git a/db_v2.sqlite3 b/db_v2.sqlite3 index 5fae8e35a3dec174fa2884bf17738be97e3a60e7..b271b284d921580c8566491e74582169ec451aa0 100644 GIT binary patch literal 36864 zcmeI*Z)h8390%~bW=)%PHR1@t&B)V$Yr}Fim*mnV8=GmHYT7jI+LUc7#x%LMN7JMx zm;PCuI2mKeL)0wApBb_(V zxiqn5cbhC+>vOI~!?#?WevE5)Yx5WSv4&3LE#v#fXXpY<5P$##An?Eo92>2#vzX2N z8=@K?PRWz9oMp42V;~ra21zu~9u5+=gtQpR8m7^_k|e{a%rKeEDiiVSEE$()NjjsF zbUu}GtSJVKFcr@x#^Tu)(do3WbL)J$1?#JIa=h5?^=*~N%5har#^{T5l0HzC3Ax-} zl9Wf{`IOp{$Y-;1T8*)_IW;~pX^k;SNsnYm_h4V7vWuO;uE0<@N}A^m zH#^9h#+fY@&#AFYI;Es#+bZ4H>~e~tz;0e5ihfD*i@s+2U8}H@X+<5&n^6XUu31ZfZVwQJVNGjbx&T2(lrm6~5_oYB6w zPH!0$c&-3Xq3D@RES^_0h37FXMoiQ$spmLd4SVVBuU&2Q>g(Al5XU{sN?G}7Z9iAW zI7=}7N+YLOjCJWsRLyWZGfni$+DL zLg3QCXEm&tRHebcbO#D4K+h0AZ?^JzZR?9Ez+#pXYV|Z&8ki;n4LeMaDyxX!t2S?> z$CkTk|5s`56g|YOD19%=+#o&Bte|{P!bUo-YfN8qrpxpW69gat0SG_<0uX=z1Rwwb z2tWV=8(3hQ&gxKlG)NvuE`;~eNM0B7Ja^(yZ(CI zc%j4oVe-?*_MN)^Px1W!B4@g8y10RN5KV&s1Rwwb2tWV=5P$##AOHafK%k1i79Ho% zz7xQfRX_hf!2e9qHbHa| zQS!UJey2-&Vqc}0#E|B}jT>0@?%KOLP5SH)Pe4i6y!0SG_<0uX=z1Rwwb2tWV=5ZHhM?EGK*m4IsR z39yU$^QMnEdcy<(2tWV=5P$##AOHafKmY;|fWZ9~*w5G1n%#z!?vat`<%R9+7aQpR z0C)taM-U|zjvmZ@qc`G&g5!|K&vZ z_SK)9M^AUWof>)P-G<|PG#A-pSI?WXDU&w8P4u~1Meoj5u~qcAZHHY>Q54wCOGMEx zNq*6Hq`A@2RB5L6oA{OAF)m#7o delta 458 zcmZozz|?Snae}m<2m=EH8xX?)>qH%6aS;Z+Xd_;r5GyYm1D`)16Ynh4?7c^xVk7~ z=45|9vB{_T)f5X-Q;QTdGE+30^%&X3MMW9g>`M}pa#BI!Aj~wmflXzy442O23G6JB z|FCIIwqjSAT*R(4c?G*CcWE(DcT!G%k`C8oH4Zr@AeF~qY?YapmakBfT2Z2qmk-3H zIXPSkNI+AeJhLPtzqCZ5D8D>2g$w8_CcZlie0TWsf&RI`$I~dtrK1?)V+@qAoSfF* z&eO=xr6UUy*x%R61J=WTjDi0c|C7yv3cL8VWSJFp42>-f%uJ09EiJ!T|B~Z6?yR{W zWt~jm(l>vknPqh{eG`-8GZK?0f3TNizQO-yvtYp)ehFb_1|4+OATgjSvCV7-%mQ$w Y8n~4HV5xq2Il!lKciiy@;iet008mSX6#xJL diff --git a/db_v2/migrations/20230720124454_peer.sql b/db_v2/migrations/20230720124454_peer.sql new file mode 100644 index 00000000..05d3d644 --- /dev/null +++ b/db_v2/migrations/20230720124454_peer.sql @@ -0,0 +1,18 @@ +ALTER TABLE peer RENAME TO peer_old; +UPDATE peer_old SET status = 1; +CREATE TABLE IF NOT EXISTS peer ( + guid blob primary key not null, + id varchar(100) not null, + uuid blob not null, + pk blob not null, + created_at datetime not null default(current_timestamp), + "user" blob, + status tinyint not null default(1), + note varchar(300), + region text null, + strategy blob, + info JSON not null DEFAULT '{}', + "last_online" datetime not null default('2011-11-16 11:55:19') + ) without rowid; +INSERT INTO peer(guid, id, uuid, pk, created_at, user, status, note, info) SELECT guid, id, uuid, pk, created_at, user, status, note, info FROM peer_old; +DROP TABLE peer_old; diff --git a/src/database.rs b/src/database.rs index fa1b6edc..bb156840 100644 --- a/src/database.rs +++ b/src/database.rs @@ -64,29 +64,37 @@ impl Database { ); let _ = pool.get().await?; // test let db = Database { pool }; + + // Load and run migrations + let migrator = sqlx::migrate!("./db_v2/migrations/"); + migrator.run(db.pool.get().await?.deref_mut()).await?; + db.create_tables().await?; Ok(db) } async fn create_tables(&self) -> ResultType<()> { sqlx::query!( - " - create table if not exists peer ( + r#" + CREATE TABLE IF NOT EXISTS peer ( guid blob primary key not null, id varchar(100) not null, uuid blob not null, pk blob not null, created_at datetime not null default(current_timestamp), - user blob, - status tinyint, + "user" blob, + status tinyint not null default(1), note varchar(300), - info text not null + region text null, + strategy blob, + info JSON not null DEFAULT '{}', + "last_online" datetime not null default('2011-11-16 11:55:19') ) without rowid; create unique index if not exists index_peer_id on peer (id); create index if not exists index_peer_user on peer (user); create index if not exists index_peer_created_at on peer (created_at); create index if not exists index_peer_status on peer (status); - " + "# ) .execute(self.pool.get().await?.deref_mut()) .await?; From 6584b91d941de2f46b26522fc56b23fedde4ba31 Mon Sep 17 00:00:00 2001 From: Ronan Date: Mon, 15 Apr 2024 17:50:29 +0200 Subject: [PATCH 4/4] Revert "makes the database compatible with rustdesk-server-pro:1.3.1" This reverts commit d4d032f9b32480f6e0cdf4de96143e53f633ca57. --- db_v2.sqlite3 | Bin 36864 -> 24576 bytes db_v2/migrations/20230720124454_peer.sql | 18 ------------------ src/database.rs | 20 ++++++-------------- 3 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 db_v2/migrations/20230720124454_peer.sql diff --git a/db_v2.sqlite3 b/db_v2.sqlite3 index b271b284d921580c8566491e74582169ec451aa0..5fae8e35a3dec174fa2884bf17738be97e3a60e7 100644 GIT binary patch delta 458 zcmZozz|?Snae}m<2m=EH8xX?)>qH%6aS;Z+Xd_;r5GyYm1D`)16Ynh4?7c^xVk7~ z=45|9vB{_T)f5X-Q;QTdGE+30^%&X3MMW9g>`M}pa#BI!Aj~wmflXzy442O23G6JB z|FCIIwqjSAT*R(4c?G*CcWE(DcT!G%k`C8oH4Zr@AeF~qY?YapmakBfT2Z2qmk-3H zIXPSkNI+AeJhLPtzqCZ5D8D>2g$w8_CcZlie0TWsf&RI`$I~dtrK1?)V+@qAoSfF* z&eO=xr6UUy*x%R61J=WTjDi0c|C7yv3cL8VWSJFp42>-f%uJ09EiJ!T|B~Z6?yR{W zWt~jm(l>vknPqh{eG`-8GZK?0f3TNizQO-yvtYp)ehFb_1|4+OATgjSvCV7-%mQ$w Y8n~4HV5xq2Il!lKciiy@;iet008mSX6#xJL literal 36864 zcmeI*Z)h8390%~bW=)%PHR1@t&B)V$Yr}Fim*mnV8=GmHYT7jI+LUc7#x%LMN7JMx zm;PCuI2mKeL)0wApBb_(V zxiqn5cbhC+>vOI~!?#?WevE5)Yx5WSv4&3LE#v#fXXpY<5P$##An?Eo92>2#vzX2N z8=@K?PRWz9oMp42V;~ra21zu~9u5+=gtQpR8m7^_k|e{a%rKeEDiiVSEE$()NjjsF zbUu}GtSJVKFcr@x#^Tu)(do3WbL)J$1?#JIa=h5?^=*~N%5har#^{T5l0HzC3Ax-} zl9Wf{`IOp{$Y-;1T8*)_IW;~pX^k;SNsnYm_h4V7vWuO;uE0<@N}A^m zH#^9h#+fY@&#AFYI;Es#+bZ4H>~e~tz;0e5ihfD*i@s+2U8}H@X+<5&n^6XUu31ZfZVwQJVNGjbx&T2(lrm6~5_oYB6w zPH!0$c&-3Xq3D@RES^_0h37FXMoiQ$spmLd4SVVBuU&2Q>g(Al5XU{sN?G}7Z9iAW zI7=}7N+YLOjCJWsRLyWZGfni$+DL zLg3QCXEm&tRHebcbO#D4K+h0AZ?^JzZR?9Ez+#pXYV|Z&8ki;n4LeMaDyxX!t2S?> z$CkTk|5s`56g|YOD19%=+#o&Bte|{P!bUo-YfN8qrpxpW69gat0SG_<0uX=z1Rwwb z2tWV=8(3hQ&gxKlG)NvuE`;~eNM0B7Ja^(yZ(CI zc%j4oVe-?*_MN)^Px1W!B4@g8y10RN5KV&s1Rwwb2tWV=5P$##AOHafK%k1i79Ho% zz7xQfRX_hf!2e9qHbHa| zQS!UJey2-&Vqc}0#E|B}jT>0@?%KOLP5SH)Pe4i6y!0SG_<0uX=z1Rwwb2tWV=5ZHhM?EGK*m4IsR z39yU$^QMnEdcy<(2tWV=5P$##AOHafKmY;|fWZ9~*w5G1n%#z!?vat`<%R9+7aQpR z0C)taM-U|zjvmZ@qc`G&g5!|K&vZ z_SK)9M^AUWof>)P-G<|PG#A-pSI?WXDU&w8P4u~1Meoj5u~qcAZHHY>Q54wCOGMEx zNq*6Hq`A@2RB5L6oA{OAF)m#7o diff --git a/db_v2/migrations/20230720124454_peer.sql b/db_v2/migrations/20230720124454_peer.sql deleted file mode 100644 index 05d3d644..00000000 --- a/db_v2/migrations/20230720124454_peer.sql +++ /dev/null @@ -1,18 +0,0 @@ -ALTER TABLE peer RENAME TO peer_old; -UPDATE peer_old SET status = 1; -CREATE TABLE IF NOT EXISTS peer ( - guid blob primary key not null, - id varchar(100) not null, - uuid blob not null, - pk blob not null, - created_at datetime not null default(current_timestamp), - "user" blob, - status tinyint not null default(1), - note varchar(300), - region text null, - strategy blob, - info JSON not null DEFAULT '{}', - "last_online" datetime not null default('2011-11-16 11:55:19') - ) without rowid; -INSERT INTO peer(guid, id, uuid, pk, created_at, user, status, note, info) SELECT guid, id, uuid, pk, created_at, user, status, note, info FROM peer_old; -DROP TABLE peer_old; diff --git a/src/database.rs b/src/database.rs index bb156840..fa1b6edc 100644 --- a/src/database.rs +++ b/src/database.rs @@ -64,37 +64,29 @@ impl Database { ); let _ = pool.get().await?; // test let db = Database { pool }; - - // Load and run migrations - let migrator = sqlx::migrate!("./db_v2/migrations/"); - migrator.run(db.pool.get().await?.deref_mut()).await?; - db.create_tables().await?; Ok(db) } async fn create_tables(&self) -> ResultType<()> { sqlx::query!( - r#" - CREATE TABLE IF NOT EXISTS peer ( + " + create table if not exists peer ( guid blob primary key not null, id varchar(100) not null, uuid blob not null, pk blob not null, created_at datetime not null default(current_timestamp), - "user" blob, - status tinyint not null default(1), + user blob, + status tinyint, note varchar(300), - region text null, - strategy blob, - info JSON not null DEFAULT '{}', - "last_online" datetime not null default('2011-11-16 11:55:19') + info text not null ) without rowid; create unique index if not exists index_peer_id on peer (id); create index if not exists index_peer_user on peer (user); create index if not exists index_peer_created_at on peer (created_at); create index if not exists index_peer_status on peer (status); - "# + " ) .execute(self.pool.get().await?.deref_mut()) .await?;