diff --git a/Cargo.lock b/Cargo.lock index f2f4879..72ce1a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,12 +126,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned-vec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" - [[package]] name = "allocator-api2" version = "0.2.16" @@ -165,6 +159,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[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" @@ -174,24 +174,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anyhow" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" - -[[package]] -name = "arbitrary" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" - [[package]] name = "arboard" version = "3.3.2" @@ -208,17 +190,6 @@ dependencies = [ "x11rb", ] -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "arrayref" version = "0.3.7" @@ -230,9 +201,6 @@ name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -dependencies = [ - "serde", -] [[package]] name = "as-raw-xcb-connection" @@ -283,6 +251,20 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" +dependencies = [ + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + [[package]] name = "async-executor" version = "1.5.1" @@ -450,30 +432,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "av1-grain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "serde", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" -dependencies = [ - "arrayvec", -] - [[package]] name = "backtrace" version = "0.3.69" @@ -495,6 +453,12 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "bit-set" version = "0.5.3" @@ -510,12 +474,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -528,18 +486,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" -[[package]] -name = "bitstream-io" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e445576659fd04a57b44cbd00aa37aaa815ebefa0aa3cb677a6b5e63d883074f" - -[[package]] -name = "bitstream-io" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" - [[package]] name = "block" version = "0.1.6" @@ -608,21 +554,6 @@ dependencies = [ "log", ] -[[package]] -name = "built" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe" -dependencies = [ - "cargo-lock", -] - -[[package]] -name = "built" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d17f4d6e4dc36d1a02fbedc2753a096848e7c1b0772f7654eab8e2c927dd53" - [[package]] name = "bumpalo" version = "3.13.0" @@ -687,18 +618,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "cargo-lock" -version = "8.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" -dependencies = [ - "semver", - "serde", - "toml 0.5.11", - "url", -] - [[package]] name = "cc" version = "1.0.83" @@ -715,16 +634,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -746,6 +655,21 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.4", +] + [[package]] name = "clipboard-win" version = "5.2.0" @@ -854,9 +778,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", "time", @@ -865,12 +789,12 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" +checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" dependencies = [ "cookie", - "idna 0.2.3", + "idna 0.3.0", "log", "publicsuffix", "serde", @@ -939,75 +863,73 @@ dependencies = [ ] [[package]] -name = "crossbeam-channel" -version = "0.5.8" +name = "crossbeam-utils" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", - "crossbeam-utils", ] [[package]] -name = "crossbeam-deque" -version = "0.8.3" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", + "generic-array", + "typenum", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.15" +name = "cursor-icon" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", -] +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "darling" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "cfg-if", + "darling_core", + "darling_macro", ] [[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" +name = "darling_core" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ - "generic-array", - "typenum", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.52", ] [[package]] -name = "cursor-icon" -version = "1.1.0" +name = "darling_macro" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.52", +] [[package]] name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +dependencies = [ + "serde", +] [[package]] name = "derivative" @@ -1062,18 +984,18 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfe80b1890e1a8cdbffc6044d6872e814aaf6011835a2a5e2db0e5c5c4ef4e" +checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" dependencies = [ "bytemuck", ] [[package]] name = "eframe" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c456c1bb6d13bf68b780257484703d750c70a23ff891ba35f4d6e23a4dbdf26f" +checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2" dependencies = [ "bytemuck", "cocoa", @@ -1105,9 +1027,9 @@ dependencies = [ [[package]] name = "egui" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180f595432a5b615fc6b74afef3955249b86cfea72607b40740a4cd60d5297d0" +checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a" dependencies = [ "accesskit", "ahash", @@ -1118,9 +1040,9 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f2d75e1e70228e7126f828bac05f9fe0e7ea88e9660c8cebe609bb114c61d4" +checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37" dependencies = [ "bytemuck", "document-features", @@ -1136,9 +1058,9 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4d44f8d89f70d4480545eb2346b76ea88c3022e9f4706cebc799dbe8b004a2" +checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609" dependencies = [ "accesskit_winit", "arboard", @@ -1153,9 +1075,9 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e3be8728b4c59493dbfec041c657e6725bdeafdbd49aef3f1dbb9e551fa01" +checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca" dependencies = [ "bytemuck", "egui", @@ -1167,17 +1089,11 @@ dependencies = [ "winit", ] -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - [[package]] name = "emath" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6916301ecf80448f786cdf3eb51d9dbdd831538732229d49119e2d4312eaaf09" +checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" dependencies = [ "bytemuck", ] @@ -1193,16 +1109,7 @@ dependencies = [ "rustc_version", "toml 0.8.2", "vswhom", - "winreg 0.52.0", -] - -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if", + "winreg", ] [[package]] @@ -1228,9 +1135,9 @@ dependencies = [ [[package]] name = "epaint" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b9fdf617dd7f58b0c8e6e9e4a1281f730cde0831d40547da446b2bb76a47af" +checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176" dependencies = [ "ab_glyph", "ahash", @@ -1291,22 +1198,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "exr" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" -dependencies = [ - "bit_field", - "flume 0.10.14", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1342,16 +1233,12 @@ dependencies = [ ] [[package]] -name = "flume" -version = "0.10.14" +name = "float-cmp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin", + "num-traits", ] [[package]] @@ -1360,9 +1247,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "futures-core", - "futures-sink", - "nanorand", "spin", ] @@ -1430,6 +1314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1520,16 +1405,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gimli" version = "0.28.0" @@ -1664,7 +1539,7 @@ checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.4.0", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.14.0", ] [[package]] @@ -1677,32 +1552,20 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.24" +name = "halfbrown" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "8588661a8607108a5ca69cab034063441a0413a0b041c13618a7dd348021ef6f" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "hashbrown 0.14.0", + "serde", ] [[package]] -name = "half" -version = "2.2.1" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" @@ -1729,12 +1592,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.3.2" @@ -1764,9 +1621,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1775,12 +1632,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", + "futures-core", "http", + "http-body", "pin-project-lite", ] @@ -1790,47 +1659,83 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" -version = "0.14.27" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", "hyper", - "native-tls", + "pin-project-lite", + "socket2 0.5.3", "tokio", - "tokio-native-tls", + "tower", + "tower-service", + "tracing", +] + +[[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", +] + +[[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]] @@ -1845,15 +1750,10 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.2.3" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" @@ -1897,37 +1797,21 @@ checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645" dependencies = [ "bytemuck", "byteorder", - "color_quant", - "exr", - "gif", - "image-webp", "num-traits", "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", ] [[package]] -name = "image-webp" -version = "0.1.0" +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6107a25f04af48ceeb4093eebc9b405ee5a1813a0bab5ecf1805d3eabb3337" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "byteorder", - "thiserror", + "autocfg", + "hashbrown 0.12.3", + "serde", ] -[[package]] -name = "imgref" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" - [[package]] name = "indexmap" version = "2.0.0" @@ -1935,7 +1819,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.0", + "serde", ] [[package]] @@ -1947,17 +1832,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1975,24 +1849,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" @@ -2030,12 +1886,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" - [[package]] name = "js-sys" version = "0.3.69" @@ -2063,44 +1913,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] -name = "lazy_static" -version = "1.4.0" +name = "lexical-core" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] [[package]] -name = "lebe" -version = "0.5.2" +name = "lexical-parse-float" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] [[package]] -name = "libc" -version = "0.2.153" +name = "lexical-parse-integer" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] [[package]] -name = "libfuzzer-sys" -version = "0.3.5" +name = "lexical-util" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf184a4b6b274f82a5df6b357da6055d3e82272327bba281c28bbba6f1664ef" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" dependencies = [ - "arbitrary 0.4.7", - "cc", + "static_assertions", ] [[package]] -name = "libfuzzer-sys" -version = "0.4.7" +name = "lexical-write-float" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" dependencies = [ - "arbitrary 1.3.2", - "cc", - "once_cell", + "lexical-util", + "lexical-write-integer", + "static_assertions", ] +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "libloading" version = "0.7.4" @@ -2155,15 +2036,6 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -2173,22 +2045,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - [[package]] name = "memchr" version = "2.7.1" @@ -2280,7 +2136,7 @@ dependencies = [ "bitflags 2.4.0", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 2.0.0", "log", "num-traits", "rustc-hash", @@ -2290,33 +2146,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndk" version = "0.8.0" @@ -2348,12 +2177,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "nix" version = "0.26.4" @@ -2382,45 +2205,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -2438,7 +2222,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint", "num-integer", "num-traits", ] @@ -2576,40 +2359,14 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "openssl" -version = "0.10.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" -dependencies = [ - "bitflags 2.4.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "memchr", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl-probe" @@ -2617,18 +2374,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "orbclient" version = "0.3.46" @@ -2700,18 +2445,18 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", @@ -2815,19 +2560,6 @@ name = "profiling" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" -dependencies = [ - "quote", - "syn 2.0.52", -] [[package]] name = "psl-types" @@ -2845,21 +2577,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quick-xml" version = "0.31.0" @@ -2908,92 +2625,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rav1e" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c383692a5e7abd9f6d1eddb1a5e0269f859392387883361bb09e5555852ec1" -dependencies = [ - "arbitrary 0.4.7", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io 1.10.0", - "built 0.5.2", - "cfg-if", - "interpolate_name", - "itertools 0.10.5", - "libc", - "libfuzzer-sys 0.3.5", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive 0.3.3", - "num-traits", - "once_cell", - "paste", - "rand", - "rand_chacha", - "rust_hawktracer", - "rustc_version", - "simd_helpers", - "system-deps", - "thiserror", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "rav1e" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" -dependencies = [ - "arbitrary 1.3.2", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io 2.2.0", - "built 0.7.1", - "cfg-if", - "interpolate_name", - "itertools 0.12.1", - "libc", - "libfuzzer-sys 0.4.7", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive 0.4.2", - "num-traits", - "once_cell", - "paste", - "profiling", - "rand", - "rand_chacha", - "simd_helpers", - "system-deps", - "thiserror", - "v_frame", -] - -[[package]] -name = "ravif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44feba0b8a381a5efa2c0baf8dace8418904403260233f4a614503b018fc288" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e 0.6.6", - "rav1e 0.7.1", - "rayon", - "rgb", -] - [[package]] name = "raw-window-handle" version = "0.5.2" @@ -3007,34 +2638,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" [[package]] -name = "rayon" -version = "1.7.0" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "either", - "rayon-core", + "bitflags 1.3.2", ] [[package]] -name = "rayon-core" -version = "1.11.0" +name = "ref-cast" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", + "ref-cast-impl", ] [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "ref-cast-impl" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ - "bitflags 1.3.2", + "proc-macro2", + "quote", + "syn 2.0.52", ] [[package]] @@ -3091,74 +2720,65 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64", + "async-compression", + "base64 0.22.0", "bytes", "cookie", "cookie_store", - "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", + "http-body-util", "hyper", - "hyper-tls", + "hyper-rustls", + "hyper-util", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.50.0", + "winreg", ] [[package]] -name = "rgb" -version = "0.8.37" +name = "ring" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ - "bytemuck", -] - -[[package]] -name = "rust_hawktracer" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3480a29b927f66c6e06527be7f49ef4d291a01d694ec1fe85b0de71d6b02ac1" -dependencies = [ - "rust_hawktracer_normal_macro", - "rust_hawktracer_proc_macro", + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] -[[package]] -name = "rust_hawktracer_normal_macro" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a570059949e1dcdc6f35228fa389f54c2c84dfe0c94c05022baacd56eacd2e9" - -[[package]] -name = "rust_hawktracer_proc_macro" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb626abdbed5e93f031baae60d72032f56bc964e11ac2ff65f2ba3ed98d6d3e1" - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3207,6 +2827,60 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" + +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" @@ -3284,9 +2958,6 @@ name = "semver" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" -dependencies = [ - "serde", -] [[package]] name = "serde" @@ -3351,6 +3022,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +dependencies = [ + "base64 0.21.3", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "sha1" version = "0.10.5" @@ -3388,14 +3089,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] -name = "simd_helpers" -version = "0.1.0" +name = "simd-json" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +checksum = "2faf8f101b9bc484337a6a6b0409cf76c139f2fb70a9e3aee6b6774be7bfbf76" dependencies = [ - "quote", + "getrandom", + "halfbrown", + "lexical-core", + "ref-cast", + "serde", + "serde_json", + "simdutf8", + "value-trait", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "slab" version = "0.4.9" @@ -3416,9 +3130,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" @@ -3515,6 +3229,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -3538,23 +3264,10 @@ dependencies = [ ] [[package]] -name = "system-deps" -version = "6.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.14" +name = "sync_wrapper" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "tempfile" @@ -3598,17 +3311,6 @@ dependencies = [ "syn 2.0.52", ] -[[package]] -name = "tiff" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "time" version = "0.3.28" @@ -3694,12 +3396,13 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "native-tls", + "rustls", + "rustls-pki-types", "tokio", ] @@ -3753,7 +3456,7 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "toml_datetime", "winnow", ] @@ -3764,13 +3467,35 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -3784,6 +3509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3887,22 +3613,33 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unifi-search-tool" -version = "2.1.5" +version = "2.2.0" dependencies = [ + "chrono", "eframe", "egui", "embed-resource", - "flume 0.11.0", + "flume", "image 0.25.0", "once_cell", "regex-automata 0.4.6", "reqwest", "serde", "serde_json", + "serde_repr", + "serde_with", + "simd-json", + "thiserror", "winres", "zeroize", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -3915,28 +3652,17 @@ dependencies = [ ] [[package]] -name = "v_frame" -version = "0.3.8" +name = "value-trait" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", + "float-cmp", + "halfbrown", + "itoa", + "ryu", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" - [[package]] name = "version_check" version = "0.9.4" @@ -4206,12 +3932,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - [[package]] name = "wgpu" version = "0.19.3" @@ -4247,7 +3967,7 @@ dependencies = [ "bitflags 2.4.0", "cfg_aliases", "codespan-reporting", - "indexmap", + "indexmap 2.0.0", "log", "naga", "once_cell", @@ -4659,16 +4379,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winreg" version = "0.52.0" @@ -4857,27 +4567,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" [[package]] -name = "zune-core" -version = "0.4.12" +name = "zstd" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] [[package]] -name = "zune-inflate" -version = "0.2.54" +name = "zstd-safe" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ - "simd-adler32", + "zstd-sys", ] [[package]] -name = "zune-jpeg" -version = "0.4.11" +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ - "zune-core", + "cc", + "pkg-config", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 247aa7d..b2f9e2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,25 @@ [package] name = "unifi-search-tool" -version = "2.1.5" +version = "2.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -egui = "0.26" -eframe = "0.26" -flume = "0.11" -image = "0.25" +chrono = { version = "0.4", features=["serde"] } +egui = { version = "0.27", features = ["extra_debug_asserts"] } +eframe = "0.27" +flume = { version = "0.11", default-features = false } +image = { version = "0.25", default-features = false, features=["ico"] } once_cell = "1.19" regex-automata = { version = "0.4", default-features = false, features=["std", "perf", "dfa"] } -reqwest = {version = "0.11", features = ["blocking", "cookies", "json"]} +reqwest = {version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "blocking", "cookies", "json", "zstd"]} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_repr = "0.1" +serde_with = "3.7" +simd-json = "0.13" +thiserror = "1.0" zeroize = "1.6" [target.'cfg(windows)'.build-dependencies] diff --git a/build.rs b/build.rs index 8829340..0b6b8ed 100644 --- a/build.rs +++ b/build.rs @@ -15,7 +15,7 @@ fn main() -> io::Result<()> { embed_resource::compile("icon.rc", embed_resource::NONE); } - let mac_addr_validation_dir = "src/mac_addr_validation"; + let mac_addr_validation_dir = "src/mac_address/validation"; fs::create_dir_all(mac_addr_validation_dir).unwrap(); let nfa_config = thompson::NFA::config() @@ -55,11 +55,13 @@ fn main() -> io::Result<()> { let autogenerated_mod_string = "\ // DO NOT EDIT THIS FILE. IT WAS AUTOMATICALLY GENERATED BY THE BUILD SCRIPT\n\n\ + use once_cell::sync::Lazy;\n\ use regex_automata::{\n\ - \tdfa::dense,\n\ + \tdfa::{dense, Automaton},\n\ \tutil::wire::AlignAs,\n\ + \tAnchored, Input,\n\ };\n\ - use once_cell::sync::Lazy;\n\n\ + \n\ pub static MAC_ADDR_REGEX: Lazy> = Lazy::new(|| {\n\ \tstatic ALIGNED: &AlignAs<[u8], u32> = &AlignAs {\n\ \t\t_align: [],\n\ @@ -71,7 +73,13 @@ fn main() -> io::Result<()> { \tlet (dense_dfa, _) = dense::DFA::from_bytes(&ALIGNED.bytes)\ .expect(\"serialized dense::DFA should be valid\");\n\ \tdense_dfa\n\ - });"; + });\n\ + \n\ + pub(crate) fn text_is_valid_mac>(text: S) -> bool {\n\ + \tMAC_ADDR_REGEX\n\ + \t\t.try_search_fwd(&Input::new(&text).anchored(Anchored::Yes).earliest(true))\n\ + \t\t.map_or(false, |x| x.is_some())\n\ + }"; fs::write( format!("{mac_addr_validation_dir}/mod.rs"), diff --git a/src/gui.rs b/src/gui.rs deleted file mode 100644 index a7023d5..0000000 --- a/src/gui.rs +++ /dev/null @@ -1,530 +0,0 @@ -use crate::unifi::{ - run_unifi_search, DeviceLabel, UnifiDevice, UnifiErrorKind, UnifiSearchInfo, UnifiSearchResult, - UnifiSearchStatus, -}; -use flume::{Receiver, Sender}; -use regex_automata::{ - dfa::{dense, Automaton}, - Anchored, Input, -}; -use std::thread; - -use crate::mac_addr_validation::MAC_ADDR_REGEX; - -fn regex_is_match, S: AsRef<[u8]>>(regex: &dense::DFA, text: S) -> bool { - (®ex) - .try_search_fwd(&Input::new(&text).anchored(Anchored::Yes).earliest(true)) - .map_or(false, |x| x.is_some()) -} - -#[derive(Debug, Clone, PartialEq)] -enum FontSize { - Small, - Medium, - Large, - ExtraLarge, -} - -#[derive(Debug, Clone, PartialEq)] -enum GuiErrorLevel { - Info, - Standard, - Critical, -} - -#[derive(Debug, Clone, PartialEq)] -struct GuiError { - title: Box, - desc: Box, - err_lvl: GuiErrorLevel, -} - -impl GuiError { - fn new_info(title: Box, desc: Box) -> Self { - Self { - title, - desc, - err_lvl: GuiErrorLevel::Info, - } - } - fn new_standard(title: Box, desc: Box) -> Self { - Self { - title, - desc, - err_lvl: GuiErrorLevel::Standard, - } - } - fn new_standard_with_code(title: &str, desc: Box, code: usize) -> Self { - Self { - title: format!("Error {}: {}", code, title).into_boxed_str(), - desc, - err_lvl: GuiErrorLevel::Standard, - } - } - fn new_critical_with_code(title: &str, desc: Box, code: usize) -> Self { - Self { - title: format!("Critical Error {}: {}", code, title).into_boxed_str(), - desc, - err_lvl: GuiErrorLevel::Critical, - } - } -} - -#[derive(Debug, PartialEq)] -pub struct CancelSignal; - -#[derive(Debug, Clone, PartialEq)] -enum PopupWindow { - SearchProgress(f32), - SearchResult(UnifiDevice), - Error(GuiError), - DisplayCancel, -} - -struct ChannelsForGuiThread { - search_info_tx: Sender, - signal_tx: Sender, - percentage_rx: Receiver, - device_rx: Receiver, -} - -pub struct ChannelsForUnifiThread { - pub search_info_rx: Receiver, - pub signal_rx: Receiver, - pub percentage_tx: Sender, - pub device_tx: Sender, -} - -pub struct GuiApp { - font_size_enum: FontSize, - unifi_search_info: UnifiSearchInfo, - channels_for_gui: ChannelsForGuiThread, - popup_window_option: Option, -} - -impl Default for GuiApp { - fn default() -> Self { - let font_size_enum = FontSize::Medium; - - // create flume channels to communicate with the background thread - let (search_info_tx, search_info_rx) = flume::bounded(1); - let (signal_tx, signal_rx) = flume::bounded(1); - let (percentage_tx, percentage_rx) = flume::bounded(1); - let (device_tx, device_rx) = flume::bounded(1); - - // all of the channel pieces for the GUI thread - let channels_for_gui = ChannelsForGuiThread { - search_info_tx, - signal_tx, - percentage_rx, - device_rx, - }; - - // all of the channel pieces for the background thread - let mut channels_for_unifi = ChannelsForUnifiThread { - search_info_rx, - signal_rx, - percentage_tx, - device_tx, - }; - - let _ = thread::spawn(move || loop { - let mut search_info = channels_for_unifi.search_info_rx.recv().unwrap(); - let unifi_search_status = run_unifi_search(&mut search_info, &mut channels_for_unifi); - channels_for_unifi - .device_tx - .send(unifi_search_status) - .unwrap(); - }); - - Self { - font_size_enum, - unifi_search_info: UnifiSearchInfo::default(), - channels_for_gui, - popup_window_option: None, - } - } -} - -impl GuiApp { - /// Called once before the first frame. - pub fn new(cc: &eframe::CreationContext<'_>) -> Self { - // This is also where you can customized the look at feel of egui using - // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. - cc.egui_ctx.set_visuals(egui::Visuals::dark()); - cc.egui_ctx.set_pixels_per_point(1.5); - - Default::default() - } -} - -impl eframe::App for GuiApp { - /// Called each time the UI needs repainting, which may be many times per second. - /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - let Self { - font_size_enum, - unifi_search_info, - channels_for_gui, - popup_window_option, - } = self; - let UnifiSearchInfo { - username, - password, - server_url, - mac_address, - accept_invalid_certs, - } = unifi_search_info; - - egui::CentralPanel::default().show(ctx, |ui| { - - let ui_scale_num = { - match font_size_enum { - FontSize::Small => 1.25, - FontSize::Medium => 1.5, - FontSize::Large => 1.75, - FontSize::ExtraLarge => 2. - } - }; - if ctx.pixels_per_point() > ui_scale_num || ctx.pixels_per_point() < ui_scale_num { - ctx.set_pixels_per_point(ui_scale_num); - } - ui.shrink_width_to_current(); - ui.shrink_height_to_current(); - - - let main_window_size = ui.available_size(); - - // create top menu bar with light/dark buttons & hyperlinks - egui::menu::bar(ui, |ui| { - ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { - egui::widgets::global_dark_light_mode_buttons(ui); - ui.label(" | "); - egui::ComboBox::from_id_source("ComboBox #1") - .selected_text("Gui Scaling") - .show_ui(ui, |ui| { - ui.selectable_value(font_size_enum, FontSize::Small, "Small"); - ui.selectable_value(font_size_enum, FontSize::Medium, "Medium"); - ui.selectable_value(font_size_enum, FontSize::Large, "Large"); - ui.selectable_value(font_size_enum, FontSize::ExtraLarge, "Extra Large"); - }); - }); - ui.add_space(150.); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.hyperlink_to("Source Code", "https://github.com/Crypto-Spartan/unifi-search-tool"); - ui.label(" | "); - ui.hyperlink_to("License", "https://github.com/Crypto-Spartan/unifi-search-tool/blob/master/LICENSE"); - }); - - }); - - // title in main window - ui.vertical_centered(|ui| { - ui.strong("Enter Unifi Controller Credentials"); - }); - - // use of grid for the input fields for formatting/spacing - egui::Grid::new("some_unique_id #1").num_columns(2).show(ui, |ui| { - ui.label("Username"); - ui.add(egui::TextEdit::singleline(username).desired_width(f32::INFINITY)); - ui.end_row(); - - ui.label("Password"); - ui.add(egui::TextEdit::singleline(password).password(true).desired_width(f32::INFINITY)); - ui.end_row(); - - ui.label("Server URL"); - ui.add(egui::TextEdit::singleline(server_url).desired_width(f32::INFINITY)); - ui.end_row(); - - ui.label("MAC Address"); - ui.add(egui::TextEdit::singleline(mac_address).desired_width(f32::INFINITY)); - ui.end_row(); - }); - - ui.checkbox(accept_invalid_certs, "Accept Invalid HTTPS Certificate"); - - // add "Search Unifi" button - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - if ui.button("Search Unifi").clicked() { - - if username.is_empty() - || password.is_empty() - || server_url.is_empty() - || mac_address.is_empty() { - *popup_window_option = Some(PopupWindow::Error( - GuiError::new_standard( - Box::from("Required Fields"), - Box::from("Username, Password, Server URL, & MAC Address are all required fields.") - ) - )); - } else if !regex_is_match(&*MAC_ADDR_REGEX, &*mac_address) { - *popup_window_option = Some(PopupWindow::Error( - GuiError::new_standard( - Box::from("Invalid MAC Address"), - Box::from("MAC Address must be formatted like XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX with hexadecimal characters only.") - ) - )); - } else { - *popup_window_option = Some(PopupWindow::SearchProgress(0.)); - channels_for_gui.search_info_tx.send( - UnifiSearchInfo { - username: username.to_string(), - password: password.to_string(), - server_url: server_url.to_string(), - mac_address: mac_address.replace("-", ":").to_lowercase(), - accept_invalid_certs: *accept_invalid_certs - } - ).unwrap(); - } - } - }); - }); - - // render popup window - if let Some(popup_window) = popup_window_option.clone() { - let width = main_window_size.x*0.7; - let default_x_pos = (main_window_size.x/2.) - (width/2.); - //let default_y_pos = main_window_size.y*0.25; - let default_y_pos = main_window_size.y*0.15; - - match popup_window { - PopupWindow::SearchProgress(percentage) => { - // create progress bar - let progress_bar = egui::widgets::ProgressBar::new(percentage) - .show_percentage() - .animate(true); - - egui::Window::new("Running Unifi Search") - .resizable(false) - .collapsible(false) - .default_width(width) - .default_pos((default_x_pos, default_y_pos)) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label(format!("Searching for device with MAC Address: {}", mac_address)); - }); - }); - // get percentage value from channel to update the progress bar - if let Ok(new_percentage) = channels_for_gui.percentage_rx.try_recv() { - *popup_window_option = Some(PopupWindow::SearchProgress(new_percentage)); - } - // check channel to see if we have a search result - if let Ok(unifi_search_result) = channels_for_gui.device_rx.try_recv() { - match unifi_search_result { - Ok(unifi_search_status) => { - match unifi_search_status { - UnifiSearchStatus::DeviceFound(unifi_device) => { - *popup_window_option = Some(PopupWindow::SearchResult(unifi_device)); - }, - UnifiSearchStatus::DeviceNotFound => { - *popup_window_option = Some(PopupWindow::Error( - GuiError::new_info( - Box::from("Device Not Found"), - format!("Unable to find device with MAC Address {}", mac_address).into_boxed_str() - ) - )); - }, - UnifiSearchStatus::Cancelled => { - *popup_window_option = None; - }, - } - }, - Err(unifi_search_error) => { - *popup_window_option = match unifi_search_error.kind { - UnifiErrorKind::Login => { - Some(PopupWindow::Error( - GuiError::new_standard_with_code( - "Login Failed", - format!("Unable to login to {}", server_url).into_boxed_str(), - unifi_search_error.code - ) - )) - }, - UnifiErrorKind::Network => { - Some(PopupWindow::Error( - GuiError::new_standard_with_code( - "Network Error", - format!("Unable to reach {}", server_url).into_boxed_str(), - unifi_search_error.code - ) - )) - }, - UnifiErrorKind::APIParsing => { - Some(PopupWindow::Error( - GuiError::new_critical_with_code( - "API Parsing Error", - Box::from("Error parsing API data"), - unifi_search_error.code - ) - )) - } - } - } - } - } - - ui.add(progress_bar); - - // cancel button - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - if ui.button("Cancel").clicked() { - channels_for_gui.signal_tx.send(CancelSignal).unwrap(); - *popup_window_option = Some(PopupWindow::DisplayCancel); - } - }); - }); - }); - }, - PopupWindow::SearchResult(unifi_device) => { - let UnifiDevice { mac_found, device_label, site, state, adopted } = unifi_device.clone(); - - // set the name/label of the device if a name wasn't found in the controller - let gui_label; - let device_name; - match device_label { - DeviceLabel::Name(s) => { - gui_label = "Device Name:"; - device_name = s; - }, - DeviceLabel::Model(s) => { - gui_label = "Device Type / Model:"; - device_name = s; - } - } - - egui::Window::new("Unifi Search Result") - .resizable(false) - .collapsible(false) - .default_width(width) - .default_pos((default_x_pos, default_y_pos)) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label("Successfully found device!"); - }); - }); - - // grid of results, grid allows for spacing/formatting - egui::Grid::new("some_unique_id #2").num_columns(2).show(ui, |ui| { - - // apply device name/label to the GUI - ui.label(gui_label); - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - ui.label(&*device_name); - }); - ui.end_row(); - - // display the name of the Unifi site - ui.label("Unifi Site:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - ui.label(&*site); - }); - ui.end_row(); - - // display the MAC address of the device found - ui.label("MAC Address:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - ui.label(&*mac_found); - }); - ui.end_row(); - - // show if the device is connected, offline, or unknown - ui.label("Device Status:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - ui.label(state); - }); - ui.end_row(); - - // show if the device is adopted to the controller - ui.label("Adopted:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - if adopted { - ui.label("True"); - } else { - ui.label("False"); - } - }); - ui.end_row(); - }); - - // close button for Unifi Search Result window - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - if ui.button("Close").clicked() { - *popup_window_option = None; - } - }); - }); - }); - }, - PopupWindow::Error(error) => { - egui::Window::new(&*error.title) - .resizable(false) - .collapsible(false) - .default_width(width) - .default_pos((default_x_pos, default_y_pos)) - .show(ctx, |ui| { - ui.vertical(|ui| { - - // error message - ui.horizontal(|ui| { - if error.err_lvl == GuiErrorLevel::Critical { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label(&*error.desc); - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("Please report this bug to the "); - ui.hyperlink_to("Github Issues Page", "https://github.com/Crypto-Spartan/unifi-search-tool/issues"); - ui.label(" and include as much information as possible.") - }); - }); - } else { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label(&*error.desc); - }); - } - }); - - // close button - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::BottomUp), |ui| { - if ui.button("Close").clicked() { - *popup_window_option = None; - } - }); - }); - }); - }); - }, - PopupWindow::DisplayCancel => { - egui::Window::new("Cancel") - .resizable(false) - .collapsible(false) - .default_width(width) - .default_pos((default_x_pos, default_y_pos)) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label("Cancel in progress, please wait..."); - }); - }); - }); - - if let Ok(Ok(UnifiSearchStatus::Cancelled)) = channels_for_gui.device_rx.recv() { - *popup_window_option = None; - } - } - } - } - - // displays a small warning message in the bottom right corner if not built in release mode - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - egui::warn_if_debug_build(ui); - }); - }); - } -} diff --git a/src/gui/app.rs b/src/gui/app.rs new file mode 100644 index 0000000..3358001 --- /dev/null +++ b/src/gui/app.rs @@ -0,0 +1,270 @@ +use crate::{ + gui::{ + popup::{PopupWindow, WindowMeta, GuiError}, + {ChannelsGuiThread, ChannelsSearchThread} + }, + mac_address::validation::text_is_valid_mac, + unifi::search::{UnifiSearchInfo, find_unifi_device} +}; +use std::thread; + + +#[derive(Debug, Clone, PartialEq)] +enum FontSize { + Small, + Medium, + Large, + ExtraLarge, +} + + +pub(crate) struct GuiApp<'a> { + font_size_enum: FontSize, + unifi_search_info: UnifiSearchInfo, + gui_channels: ChannelsGuiThread, + popup_window_option: Option>, +} + + +impl<'a> Default for GuiApp<'a> { + fn default() -> Self { + let font_size_enum = FontSize::Medium; + + // create flume channels to communicate with the background thread + let (search_info_tx, search_info_rx) = flume::bounded(1); + let (signal_tx, signal_rx) = flume::bounded(1); + let (percentage_tx, percentage_rx) = flume::bounded(1); + let (device_tx, device_rx) = flume::bounded(1); + + // all of the channel pieces for the GUI thread + let gui_channels = ChannelsGuiThread { + search_info_tx, + signal_tx, + percentage_rx, + device_rx, + }; + + // all of the channel pieces for the background thread + let mut search_thread_channels = ChannelsSearchThread { + search_info_rx, + signal_rx, + percentage_tx, + device_tx, + }; + + // spawn background thread to do the API calls, parsing, & searching + // multiple flume channels used for communication between the gui thread and search thread + let _ = thread::spawn(move || loop { + let mut search_info = search_thread_channels.search_info_rx.recv().unwrap(); + let unifi_search_result = find_unifi_device(&mut search_info, &mut search_thread_channels); + search_thread_channels + .device_tx + .send(unifi_search_result) + .unwrap(); + }); + + Self { + font_size_enum, + unifi_search_info: UnifiSearchInfo::default(), + gui_channels, + popup_window_option: None, + } + } +} + +impl<'a> GuiApp<'a> { + /// Called once before the first frame. + pub(crate) fn new(cc: &eframe::CreationContext<'_>) -> Self { + // This is also where you can customized the look at feel of egui using + // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. + cc.egui_ctx.set_visuals(egui::Visuals::dark()); + cc.egui_ctx.set_pixels_per_point(1.5); + + Default::default() + } + + + fn create_menu_bar(ui: &mut egui::Ui, font_size_enum: &mut FontSize) { + // create top menu bar with light/dark buttons & hyperlinks + egui::menu::bar(ui, |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { + egui::widgets::global_dark_light_mode_buttons(ui); + ui.label(" | "); + egui::ComboBox::from_id_source("ComboBox #1") + .selected_text("Gui Scaling") + .show_ui(ui, |ui| { + ui.selectable_value(font_size_enum, FontSize::Small, "Small"); + ui.selectable_value(font_size_enum, FontSize::Medium, "Medium"); + ui.selectable_value(font_size_enum, FontSize::Large, "Large"); + ui.selectable_value(font_size_enum, FontSize::ExtraLarge, "Extra Large"); + }); + }); + ui.add_space(150.); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.hyperlink_to("Source Code", "https://github.com/Crypto-Spartan/unifi-search-tool"); + ui.label(" | "); + ui.hyperlink_to("License", "https://github.com/Crypto-Spartan/unifi-search-tool/blob/master/LICENSE"); + }); + + }); + } + + + fn create_main_window( + ui: &mut egui::Ui, + unifi_search_info: &mut UnifiSearchInfo, + popup_window_option: &mut Option, + search_info_tx: &mut flume::Sender + ) { + let UnifiSearchInfo { + username, + password, + server_url, + mac_to_search, + accept_invalid_certs, + } = unifi_search_info; + + // title in main window + ui.vertical_centered(|ui| { + ui.strong("Enter Unifi Controller Credentials"); + }); + + // use of grid for the input fields for formatting/spacing + egui::Grid::new("some_unique_id #1").num_columns(2).show(ui, |ui| { + ui.label("Username"); + ui.add(egui::TextEdit::singleline(username).desired_width(f32::INFINITY)); + ui.end_row(); + + ui.label("Password"); + ui.add(egui::TextEdit::singleline(password).password(true).desired_width(f32::INFINITY)); + ui.end_row(); + + ui.label("Server URL"); + ui.add(egui::TextEdit::singleline(server_url).desired_width(f32::INFINITY)); + ui.end_row(); + + ui.label("MAC Address"); + ui.add(egui::TextEdit::singleline(mac_to_search).desired_width(f32::INFINITY)); + ui.end_row(); + }); + + ui.checkbox(accept_invalid_certs, "Accept Invalid HTTPS Certificate"); + + // add "Search Unifi" button + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + if ui.button("Search Unifi").clicked() { + + if username.is_empty() + || password.is_empty() + || server_url.is_empty() + || mac_to_search.is_empty() { + *popup_window_option = Some(PopupWindow::Error( + GuiError::new_standard( + "Required Fields", + Box::from("Username, Password, Server URL, & MAC Address are all required fields.") + ) + )); + } else if !text_is_valid_mac(mac_to_search.as_bytes()) { + *popup_window_option = Some(PopupWindow::Error( + GuiError::new_standard( + "Invalid MAC Address", + Box::from("MAC Address must be formatted like XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX with hexadecimal characters only.") + ) + )); + } else { + *popup_window_option = Some(PopupWindow::SearchProgress(0.)); + search_info_tx.send( + UnifiSearchInfo { + username: username.to_string(), + password: password.to_string(), + server_url: server_url.to_string(), + mac_to_search: mac_to_search.replace("-", ":").to_lowercase(), + accept_invalid_certs: *accept_invalid_certs + } + ).unwrap(); + } + } + }); + }); + } + + + fn handle_popup_window( + ctx: &egui::Context, + popup_window_option: &mut Option, + main_window_size: egui::Vec2, + mac_to_search: &str, + gui_channels: &mut ChannelsGuiThread + ) { + if let Some(popup_window) = popup_window_option.clone() { + let popup_metadata = { + let width = main_window_size.x * 0.7; + WindowMeta { + ctx, + width, + default_x_pos: (main_window_size.x / 2.) - (width / 2.), + default_y_pos: main_window_size.y * 0.15 + } + }; + + match popup_window { + PopupWindow::SearchProgress(percentage) => { + PopupWindow::render_search_progress(popup_metadata, popup_window_option, percentage, mac_to_search, gui_channels); + }, + PopupWindow::SearchResult(unifi_device) => { + PopupWindow::render_search_result(popup_metadata, popup_window_option, unifi_device); + }, + PopupWindow::Error(error) => { + PopupWindow::render_error(popup_metadata, popup_window_option, error); + }, + PopupWindow::DisplayCancel => { + PopupWindow::render_cancel(popup_metadata, popup_window_option, &mut gui_channels.device_rx); + } + } + } + } +} + + +impl<'a> eframe::App for GuiApp<'a> { + /// Called each time the UI needs repainting, which may be many times per second. + /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + let Self { + font_size_enum, + unifi_search_info, + gui_channels, + popup_window_option, + } = self; + + egui::CentralPanel::default().show(ctx, |ui| { + + let ui_scale_num = { + match font_size_enum { + FontSize::Small => 1.25, + FontSize::Medium => 1.5, + FontSize::Large => 1.75, + FontSize::ExtraLarge => 2. + } + }; + if ctx.pixels_per_point() > ui_scale_num || ctx.pixels_per_point() < ui_scale_num { + ctx.set_pixels_per_point(ui_scale_num); + } + ui.shrink_width_to_current(); + ui.shrink_height_to_current(); + + GuiApp::create_menu_bar(ui, font_size_enum); + GuiApp::create_main_window(ui, unifi_search_info, popup_window_option, &mut gui_channels.search_info_tx); + let main_window_size = ui.available_size(); + GuiApp::handle_popup_window(ctx, popup_window_option, main_window_size, &unifi_search_info.mac_to_search, gui_channels); + + // displays a small warning message in the bottom right corner if not built in release mode + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + egui::warn_if_debug_build(ui); + }); + }); + } + + +} \ No newline at end of file diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000..05ad422 --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,24 @@ +pub(crate) mod app; +mod popup; + +use crate::unifi::search::{UnifiSearchInfo, UnifiSearchResult}; +use flume::{Receiver, Sender}; + + +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct CancelSignal; + + +struct ChannelsGuiThread { + search_info_tx: Sender, + signal_tx: Sender, + percentage_rx: Receiver, + device_rx: Receiver, +} + +pub(crate) struct ChannelsSearchThread { + pub(crate) search_info_rx: Receiver, + pub(crate) signal_rx: Receiver, + pub(crate) percentage_tx: Sender, + pub(crate) device_tx: Sender, +} \ No newline at end of file diff --git a/src/gui/popup.rs b/src/gui/popup.rs new file mode 100644 index 0000000..4752cd2 --- /dev/null +++ b/src/gui/popup.rs @@ -0,0 +1,334 @@ +use crate::{ + gui::{ChannelsGuiThread, CancelSignal}, + unifi::{ + api::UnifiAPIError, + devices::UnifiDeviceBasic, + search::UnifiSearchResult, + } +}; +use std::borrow::Cow; + +#[derive(Debug, Clone, PartialEq)] +pub(super) enum GuiErrorLevel { + Info, + Standard, + Critical, +} + +#[derive(Debug, Clone, PartialEq)] +pub(super) struct GuiError<'a> { + title: Cow<'a, str>, + desc: Box, + err_lvl: GuiErrorLevel, +} + +impl<'a> GuiError<'a> { + pub(super) fn new_info(title: &'static str, desc: Box) -> Self { + Self { + title: Cow::Borrowed(title), + desc, + err_lvl: GuiErrorLevel::Info, + } + } + pub(super) fn new_standard(title: &'static str, desc: Box) -> Self { + Self { + title: Cow::Owned(format!("Error: {}", title)), + desc, + err_lvl: GuiErrorLevel::Standard, + } + } + pub(super) fn new_critical(title: &'static str, desc: Box) -> Self { + Self { + title: Cow::Owned(format!("Critical Error: {}", title)), + desc, + err_lvl: GuiErrorLevel::Critical, + } + } +} + + +#[derive(Debug, Clone, PartialEq)] +pub(super) enum PopupWindow<'a> { + SearchProgress(f32), + SearchResult(UnifiDeviceBasic), + Error(GuiError<'a>), + DisplayCancel +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub(super) struct WindowMeta<'b> { + pub(super) ctx: &'b egui::Context, + pub(super) width: f32, + pub(super) default_x_pos: f32, + pub(super) default_y_pos: f32 +} + + +impl<'a> PopupWindow<'a> { + + pub(super) fn render_search_progress(popup_metadata: WindowMeta, popup_window_option: &mut Option, percentage: f32, mac_address: &str, gui_channels: &mut ChannelsGuiThread) { + // create progress bar + let progress_bar = egui::widgets::ProgressBar::new(percentage) + .show_percentage() + .animate(true); + + egui::Window::new("Running Unifi Search") + .resizable(false) + .collapsible(false) + .default_width(popup_metadata.width) + .default_pos((popup_metadata.default_x_pos, popup_metadata.default_y_pos)) + .show(popup_metadata.ctx, |ui| { + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + ui.label(format!("Searching for Unifi device with MAC Address: {}", mac_address)); + }); + }); + // get percentage value from channel to update the progress bar + if let Ok(new_percentage) = gui_channels.percentage_rx.try_recv() { + *popup_window_option = Some(PopupWindow::SearchProgress(new_percentage)); + } + // check channel to see if we have a search result + if let Ok(unifi_search_result) = gui_channels.device_rx.try_recv() { + match unifi_search_result { + Ok(unifi_search_option) => { + match unifi_search_option { + Some(unifi_device) => { + *popup_window_option = Some(PopupWindow::SearchResult(unifi_device)); + }, + None => { + *popup_window_option = Some(PopupWindow::Error( + GuiError::new_info( + "Device Not Found", + format!("Unable to find device with MAC Address {}", mac_address).into_boxed_str() + ) + )); + } + } + }, + Err(ref unifi_api_error) => { + *popup_window_option = match unifi_api_error { + UnifiAPIError::ClientError(source) => { + debug_assert!(source.is_builder()); + Some(PopupWindow::Error( + GuiError::new_critical( + "Reqwest Client Error", + format!("Unable to Build Unifi Client\n{}\n{}", unifi_api_error, source).into_boxed_str() + ) + )) + }, + UnifiAPIError::LoginAuthenticationError{url} => { + Some(PopupWindow::Error( + GuiError::new_standard( + "Login Failed", + format!("Unable to login to {}\n{}", url, unifi_api_error).into_boxed_str() + ) + )) + }, + UnifiAPIError::ReqwestError {source, ..} => { + Some(PopupWindow::Error( + GuiError::new_standard( + "Reqwest Error", + format!("{}\n{}", unifi_api_error, source).into_boxed_str() + ) + )) + }, + UnifiAPIError::JsonError {source, ..} => { + Some(PopupWindow::Error( + GuiError::new_critical( + "Json Parsing Error", + format!("{}\n{}", unifi_api_error, source).into_boxed_str() + ) + )) + } + } + } + } + } + + ui.add(progress_bar); + + // cancel button + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + if ui.button("Cancel").clicked() { + gui_channels.signal_tx.send(CancelSignal).unwrap(); + *popup_window_option = Some(PopupWindow::DisplayCancel); + } + }); + }); + }); + } + + + pub(super) fn render_search_result(popup_metadata: WindowMeta, popup_window_option: &mut Option, unifi_device: UnifiDeviceBasic) { + let UnifiDeviceBasic { + mac, + state, + adopted, + device_type, + device_model, + gateway_mode, + name_option, + device_label_option, + site + } = unifi_device; + + egui::Window::new("Unifi Search Result") + .resizable(false) + .collapsible(false) + .default_width(popup_metadata.width) + .default_pos((popup_metadata.default_x_pos, popup_metadata.default_y_pos)) + .show(popup_metadata.ctx, |ui| { + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + ui.label("Successfully found device!"); + }); + }); + + // grid of results, grid allows for spacing/formatting + egui::Grid::new("some_unique_id #2").num_columns(2).show(ui, |ui| { + + // add device name to the popup, if it's available + if let Some(device_name) = name_option { + ui.label("Device Name:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label(device_name.as_ref()); + }); + ui.end_row(); + } + + // add device label to the popup, if it's available + // else add the device type & model + if let Some(device_label) = device_label_option { + ui.label("Model / SKU / Product:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label(format!("{} / {}", device_model.to_uppercase(), device_label)); + }); + } else { + ui.label("Device Type / Model:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label(format!("{} / {}", device_type.to_uppercase(), device_model.to_uppercase())); + }); + } + ui.end_row(); + + // add the name of the Unifi site + ui.label("Unifi Site:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label(site.as_ref()); + }); + ui.end_row(); + + // add the MAC address of the device found + ui.label("MAC Address:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label(mac.as_ref()); + }); + ui.end_row(); + + // add device status; ie if the device is connected, offline, or unknown + ui.label("Device Status:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label(state.as_str()); // custom as_str implementation + }); + ui.end_row(); + + // add adoption status if false + // it's weird that the controller has info on a device that's not adopted + // device status will most likely be `unknown` + if !adopted { + ui.label("Adopted"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label("False"); + }); + ui.end_row(); + } + + // add gateway mode if true + if gateway_mode { + ui.label("Gateway Mode:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + ui.label("True"); + }); + ui.end_row(); + } + }); + + // close button + PopupWindow::render_close_button(ui, popup_window_option); + }); + } + + + pub(super) fn render_error(popup_metadata: WindowMeta, popup_window_option: &mut Option, error: GuiError) { + egui::Window::new(error.title) + .resizable(false) + .collapsible(false) + .default_width(popup_metadata.width) + .default_pos((popup_metadata.default_x_pos, popup_metadata.default_y_pos)) + .show(popup_metadata.ctx, |ui| { + ui.vertical(|ui| { + + // error message + ui.horizontal(|ui| { + if error.err_lvl == GuiErrorLevel::Critical { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + ui.label(&*error.desc); + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("Please report this bug to the "); + ui.hyperlink_to("Github Issues Page", "https://github.com/Crypto-Spartan/unifi-search-tool/issues"); + ui.label(" and include as much information as possible.") + }); + }); + } else { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + ui.label(error.desc.as_ref()); + }); + } + }); + + // close button + PopupWindow::render_close_button(ui, popup_window_option); + }); + }); + } + + + pub(super) fn render_cancel(popup_metadata: WindowMeta, popup_window_option: &mut Option, device_rx: &mut flume::Receiver) { + let WindowMeta { + ctx, + width, + default_x_pos, + default_y_pos + } = popup_metadata; + + egui::Window::new("Cancel") + .resizable(false) + .collapsible(false) + .default_width(width) + .default_pos((default_x_pos, default_y_pos)) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { + ui.label("Cancel in progress, please wait..."); + }); + }); + }); + + if let Ok(Ok(None)) = device_rx.recv() { + *popup_window_option = None; + } + } + + + fn render_close_button(ui: &mut egui::Ui, popup_window_option: &mut Option) { + ui.horizontal(|ui| { + ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::BottomUp), |ui| { + if ui.button("Close").clicked() { + *popup_window_option = None; + } + }); + }); + } +} diff --git a/src/mac_address/mod.rs b/src/mac_address/mod.rs new file mode 100644 index 0000000..657d8e2 --- /dev/null +++ b/src/mac_address/mod.rs @@ -0,0 +1,88 @@ +pub mod validation; + +/*pub(crate) struct MACAddress{ + bytes: [u8; 6] +} + +impl MACAddress { + pub fn new(bytes: [u8; 6]) -> MACAddress { + MACAddress{ bytes } + } + + pub fn bytes(self) -> [u8; 6] { + self.bytes + } +} + +impl From<[u8; 6]> for MACAddress { + fn from(v: [u8; 6]) -> Self { + MACAddress::new(v) + } +} + +impl std::str::FromStr for MACAddress { + type Err = MACParseError; + + fn from_str(input: &str) -> Result { + let mut array = [0u8; 6]; + + let mut nth = 0; + for byte in input.split(|c| c == ':' || c == '-') { + if nth == 6 { + return Err(MACParseError::InvalidLength); + } + + array[nth] = u8::from_str_radix(byte, 16).map_err(|_| MACParseError::InvalidDigit)?; + + nth += 1; + } + + if nth != 6 { + return Err(MACParseError::InvalidLength); + } + + Ok(MACAddress::new(array)) + } +} + +impl std::convert::TryFrom<&'_ str> for MACAddress { + type Error = MACParseError; + + fn try_from(value: &str) -> Result { + value.parse() + } +} + +impl std::convert::TryFrom> for MACAddress { + type Error = MACParseError; + + fn try_from(value: std::borrow::Cow<'_, str>) -> Result { + value.parse() + } +} + +impl std::fmt::Display for MACAddress { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let _ = write!( + f, + "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}", + self.bytes[0], + self.bytes[1], + self.bytes[2], + self.bytes[3], + self.bytes[4], + self.bytes[5] + ); + + Ok(()) + } +}*/ + +// impl serde::Serialize for MACAddress { +// fn serialize(&self, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// serializer.collect_str(self) +// } +// } \ No newline at end of file diff --git a/src/mac_addr_validation/mac_addr_regex.bigendian.dfa b/src/mac_address/validation/mac_addr_regex.bigendian.dfa similarity index 100% rename from src/mac_addr_validation/mac_addr_regex.bigendian.dfa rename to src/mac_address/validation/mac_addr_regex.bigendian.dfa diff --git a/src/mac_addr_validation/mac_addr_regex.littleendian.dfa b/src/mac_address/validation/mac_addr_regex.littleendian.dfa similarity index 100% rename from src/mac_addr_validation/mac_addr_regex.littleendian.dfa rename to src/mac_address/validation/mac_addr_regex.littleendian.dfa diff --git a/src/mac_addr_validation/mod.rs b/src/mac_address/validation/mod.rs similarity index 71% rename from src/mac_addr_validation/mod.rs rename to src/mac_address/validation/mod.rs index 521c92e..b07a195 100644 --- a/src/mac_addr_validation/mod.rs +++ b/src/mac_address/validation/mod.rs @@ -1,10 +1,11 @@ // DO NOT EDIT THIS FILE. IT WAS AUTOMATICALLY GENERATED BY THE BUILD SCRIPT +use once_cell::sync::Lazy; use regex_automata::{ - dfa::dense, + dfa::{dense, Automaton}, util::wire::AlignAs, + Anchored, Input, }; -use once_cell::sync::Lazy; pub static MAC_ADDR_REGEX: Lazy> = Lazy::new(|| { static ALIGNED: &AlignAs<[u8], u32> = &AlignAs { @@ -16,4 +17,10 @@ pub static MAC_ADDR_REGEX: Lazy> = Lazy::new(|| { }; let (dense_dfa, _) = dense::DFA::from_bytes(&ALIGNED.bytes).expect("serialized dense::DFA should be valid"); dense_dfa -}); \ No newline at end of file +}); + +pub(crate) fn text_is_valid_mac>(text: S) -> bool { + MAC_ADDR_REGEX + .try_search_fwd(&Input::new(&text).anchored(Anchored::Yes).earliest(true)) + .map_or(false, |x| x.is_some()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9d64050..9df4c6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release mod gui; -mod mac_addr_validation; +mod mac_address; mod unifi; -use gui::GuiApp; +use gui::app::GuiApp; fn main() { const IMAGE: &[u8] = include_bytes!("unifi-search.ico"); diff --git a/src/unifi.rs b/src/unifi.rs deleted file mode 100644 index 133b846..0000000 --- a/src/unifi.rs +++ /dev/null @@ -1,268 +0,0 @@ -//https://ubntwiki.com/products/software/unifi-controller/api - -use crate::gui::{CancelSignal, ChannelsForUnifiThread}; -use reqwest::blocking::Client; -use reqwest::header::REFERER; -use serde::Deserialize; -use std::collections::HashMap; -use std::time::Duration; -use zeroize::Zeroize; - -#[derive(Default, Debug, Clone)] -pub struct UnifiSearchInfo { - pub username: String, - pub password: String, - pub server_url: String, - pub mac_address: String, - pub accept_invalid_certs: bool, -} - -pub type UnifiSearchResult = Result; -type UnifiLoginResult = Result; -type ErrorCode = usize; - -#[derive(Debug, Clone)] -pub enum UnifiSearchStatus { - DeviceFound(UnifiDevice), - DeviceNotFound, - Cancelled, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct UnifiDevice { - pub mac_found: Box, - pub device_label: DeviceLabel, - pub site: Box, - pub state: &'static str, - pub adopted: bool, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum DeviceLabel { - Name(Box), - Model(Box), -} - -#[derive(Debug, Clone)] -pub struct UnifiSearchError { - pub code: usize, - pub kind: UnifiErrorKind, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum UnifiErrorKind { - Login, - Network, - APIParsing, -} - -impl UnifiSearchError { - fn new_login(code: usize) -> Self { - Self { - code, - kind: UnifiErrorKind::Login, - } - } - - fn new_network(code: usize) -> Self { - Self { - code, - kind: UnifiErrorKind::Network, - } - } - - fn new_api_parsing(code: usize) -> Self { - Self { - code, - kind: UnifiErrorKind::APIParsing, - } - } -} - -#[derive(Debug, Clone, Deserialize)] -struct UnifiAllSitesJson { - data: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -struct UnifiSiteJson { - #[serde(rename = "name")] - code: Box, - desc: Box, -} - -#[derive(Debug, Clone, Deserialize)] -struct UnifiAllSiteDevicesJson { - data: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -struct UnifiSiteDeviceJson { - mac: Box, - state: usize, - adopted: bool, - //disabled: bool, - #[serde(rename = "type")] - device_type: Box, - model: Box, - name: Option>, -} - -pub fn run_unifi_search( - search_info: &mut UnifiSearchInfo, - channels_for_unifi: &mut ChannelsForUnifiThread, -) -> UnifiSearchResult { - let UnifiSearchInfo { - username, - password, - ref server_url, - ref mac_address, - ref accept_invalid_certs, - } = search_info; - - match login_with_client(username, password, server_url, *accept_invalid_certs) { - Ok(client) => find_unifi_device(client, server_url, mac_address, channels_for_unifi), - Err(code) => Err(UnifiSearchError::new_login(code)), - } -} - -fn login_with_client( - username: &mut String, - password: &mut String, - base_url: &str, - accept_invalid_certs: bool, -) -> UnifiLoginResult { - let mut login_data: HashMap<&str, &str> = HashMap::new(); - login_data.insert("username", username); - login_data.insert("password", password); - - let Ok(client) = Client::builder() - .timeout(Duration::from_secs(15)) - .danger_accept_invalid_certs(accept_invalid_certs) - .cookie_store(true) - .build() - else { - return Err(101); - }; - - let login = client - .post(format!("{}/api/login", base_url)) - .header(REFERER, "/login") - .json(&login_data) - .send() - .ok(); - - // zeroize the user entered data for security - password.zeroize(); - username.zeroize(); - - if let Some(login_status) = login { - if login_status.status().is_success() { - Ok(client) - } else { - Err(103) - } - } else { - Err(102) - } -} - -fn find_unifi_device( - client: Client, - base_url: &str, - mac_to_search: &str, - channels_for_unifi: &mut ChannelsForUnifiThread, -) -> UnifiSearchResult { - // check for cancel signal - if let Ok(v) = channels_for_unifi.signal_rx.try_recv() { - if v == CancelSignal { - return Ok(UnifiSearchStatus::Cancelled); - } - } - - let Ok(sites_get) = client.get(format!("{}/api/self/sites", base_url)).send() else { - return Err(UnifiSearchError::new_network(201)); - }; - let Ok(sites_raw) = sites_get.text() else { - return Err(UnifiSearchError::new_api_parsing(301)); - }; - let Ok::(sites_parsed) = serde_json::from_str(&sites_raw) else { - return Err(UnifiSearchError::new_api_parsing(302)); - }; - let unifi_sites = sites_parsed.data; - let unifi_sites_len = unifi_sites.len() as f32; - - for (iter_num, site) in unifi_sites.iter().enumerate() { - // check for cancel signal - if let Ok(v) = channels_for_unifi.signal_rx.try_recv() { - if v == CancelSignal { - return Ok(UnifiSearchStatus::Cancelled); - } - } - { - // send percentage of search completion to GUI thread - let _ = channels_for_unifi - .percentage_tx - .try_send(iter_num as f32 / unifi_sites_len); - } - - // hit the controller's API to get device info for a specific site - let Ok(devices_get) = client - .get(format!( - "{}/api/s/{}/stat/device-basic", - base_url, site.code - )) - .send() - else { - return Err(UnifiSearchError::new_network(202)); - }; - // get the string of the API response - let Ok(devices_raw) = devices_get.text() else { - return Err(UnifiSearchError::new_api_parsing(303)); - }; - // parse the API response with serde - let Ok::(devices_parsed) = serde_json::from_str(&devices_raw) - else { - return Err(UnifiSearchError::new_api_parsing(304)); - }; - - let site_devices = devices_parsed.data; - // loop through the devices found in the site to see if the MAC address matches what we're searching for - for device in site_devices.into_iter() { - if mac_to_search == device.mac.to_lowercase() { - // set percentage to 100% - { - let _ = channels_for_unifi.percentage_tx.try_send(1f32); - } - - let state = match device.state { - 0 => "Offline", - 1 => "Connected", - _ => "Unknown", - }; - let device_label = { - match device.name { - Some(device_name) => DeviceLabel::Name(device_name), - None => DeviceLabel::Model( - format!( - "{} / {}", - device.device_type.to_uppercase(), - device.model.to_uppercase() - ) - .into_boxed_str(), - ), - } - }; - - return Ok(UnifiSearchStatus::DeviceFound(UnifiDevice { - mac_found: device.mac.to_lowercase().into_boxed_str(), - device_label, - site: site.desc.clone(), - state, - adopted: device.adopted, - })); - } - } - } - return Ok(UnifiSearchStatus::DeviceNotFound); -} diff --git a/src/unifi/api.rs b/src/unifi/api.rs new file mode 100644 index 0000000..aa900f7 --- /dev/null +++ b/src/unifi/api.rs @@ -0,0 +1,204 @@ +use super::devices::{UnifiSite, UnifiDeviceBasic, UnifiDeviceFull, ClientDevice, ClientDeviceActive}; +use reqwest::blocking::Client; +use reqwest::header::REFERER; +use serde::Deserialize; +use std::collections::HashMap; +use std::time::Duration; +use thiserror::Error; +use zeroize::Zeroize; + + +#[derive(Debug, Clone, Deserialize)] +struct RespMeta { + #[serde(rename(deserialize = "rc"))] + result: RespResult, + msg: Option> +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "lowercase")] +enum RespResult { + Ok, + Error +} + +#[derive(Debug, Clone, Deserialize)] +struct UnifiSitesResp { + meta: RespMeta, + data: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct UnifiDevicesBasicResp { + meta: RespMeta, + data: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct UnifiDevicesFullResp { + meta: RespMeta, + data: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct UnifiClientsAllResp { + meta: RespMeta, + data: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct UnifiClientsActiveResp { + meta: RespMeta, + data: Vec, +} + + +#[derive(Error, Debug)] +pub(crate) enum UnifiAPIError { + #[error("error building reqwest client")] + ClientError(#[from] reqwest::Error), + #[error("invalid credentials")] + LoginAuthenticationError { + url: Box + }, + #[error("error communicating with\n{url}")] + ReqwestError { + url: Box, + source: reqwest::Error + }, + #[error("error parsing json from\n{url}")] + JsonError { + url: Box, + source: simd_json::Error + } +} + +pub(crate) struct UnifiClient<'a> { + client: Client, + server_url: &'a str, + is_logged_in: bool, +} + +impl<'a> UnifiClient<'a> { + pub(crate) fn new(server_url: &'a str, accept_invalid_certs: bool) -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(15)) + .danger_accept_invalid_certs(accept_invalid_certs) + .cookie_store(true) + .build() + .map_err(UnifiAPIError::ClientError)?; + + Ok(Self { + client, + server_url, + is_logged_in: false, + }) + } + + pub(crate) fn is_logged_in(&self) -> bool { + self.is_logged_in + } + + pub(crate) fn login(&mut self, username: &mut str, password: &mut str) -> Result<(), UnifiAPIError> { + let mut login_data: HashMap<&str, &mut str> = HashMap::new(); + login_data.insert("username", username); + login_data.insert("password", password); + + let mut url = format!("{}/api/login", self.server_url).into_boxed_str(); + let login_result = self + .client + .post(url.as_ref()) + .header(REFERER, "/login") + .json(&login_data) + .send(); + + { // zeroize the user entered data for security + login_data.iter_mut().for_each(|(_, v)| { + v.zeroize(); + }); + std::mem::drop(login_data); + password.zeroize(); + username.zeroize(); + }; + + let login = login_result + .map_err(|source| UnifiAPIError::ReqwestError{ + url: std::mem::take(&mut url), + source + })? + .error_for_status() + .map_err(|source| UnifiAPIError::ReqwestError{ + url: std::mem::take(&mut url), + source + })?; + + if login.status().is_success() { + self.is_logged_in = true; + Ok(()) + } else { + Err(UnifiAPIError::LoginAuthenticationError{ url }) + } + } + + fn api_call(&mut self, url: &mut Box) -> Result { + let resp = self.client.get(url.as_ref()).send() + .map_err(|source| UnifiAPIError::ReqwestError{ + url: std::mem::take(url), + source + })? + .error_for_status() + .map_err(|source| UnifiAPIError::ReqwestError{ + url: std::mem::take(url), + source + })?; + Ok(resp) + } + + pub(crate) fn get_sites(&mut self) -> Result, UnifiAPIError> { + let mut url = format!("{}/api/self/sites", self.server_url).into_boxed_str(); + let resp = self.api_call(&mut url)?; + let sites: UnifiSitesResp = simd_json::serde::from_reader(resp) + .map_err(|source| UnifiAPIError::JsonError{ url, source })?; + Ok(sites.data) + } + + pub(crate) fn get_site_devices_basic(&mut self, site_code: &str) -> Result, UnifiAPIError> { + let mut url = format!("{}/api/s/{}/stat/device-basic", self.server_url, site_code).into_boxed_str(); + let resp = self.api_call(&mut url)?; + let site_unifi_devices_basic: UnifiDevicesBasicResp = simd_json::serde::from_reader(resp) + .map_err(|source| UnifiAPIError::JsonError{ url, source })?; + Ok(site_unifi_devices_basic.data) + } + + pub(crate) fn get_site_devices_full(&mut self, site_code: &str) -> Result, UnifiAPIError> { + let mut url = format!("{}/api/s/{}/stat/device", self.server_url, site_code).into_boxed_str(); + let resp = self.api_call(&mut url)?; + let site_unifi_devices_full: UnifiDevicesFullResp = simd_json::serde::from_reader(resp) + .map_err(|source| UnifiAPIError::JsonError{ url, source })?; + Ok(site_unifi_devices_full.data) + } + + pub(crate) fn get_site_device_mac(&mut self, site_code: &str, mac: &str) -> Result, UnifiAPIError> { + let mut url = format!("{}/api/s/{}/stat/device/{}", self.server_url, site_code, mac).into_boxed_str(); + let resp = self.api_call(&mut url)?; + let site_unifi_device_mac: UnifiDevicesFullResp = simd_json::serde::from_reader(resp) + .map_err(|source| UnifiAPIError::JsonError{ url, source })?; + Ok(site_unifi_device_mac.data) + } + + pub(crate) fn get_site_clients_all(&mut self, site_code: &str) -> Result, UnifiAPIError> { + let mut url = format!("{}/api/s/{}/rest/user", self.server_url, site_code).into_boxed_str(); + let resp = self.api_call(&mut url)?; + let site_client_devices_all: UnifiClientsAllResp = simd_json::serde::from_reader(resp) + .map_err(|source| UnifiAPIError::JsonError{ url, source })?; + Ok(site_client_devices_all.data) + } + + pub(crate) fn get_site_clients_active(&mut self, site_code: &str) -> Result, UnifiAPIError> { + let mut url = format!("{}/api/s/{}/stat/sta", self.server_url, site_code).into_boxed_str(); + let resp = self.api_call(&mut url)?; + let site_client_devices_active: UnifiClientsActiveResp = simd_json::serde::from_reader(resp) + .map_err(|source| UnifiAPIError::JsonError{ url, source })?; + Ok(site_client_devices_active.data) + } +} diff --git a/src/unifi/devices.rs b/src/unifi/devices.rs new file mode 100644 index 0000000..82c6397 --- /dev/null +++ b/src/unifi/devices.rs @@ -0,0 +1,280 @@ +use chrono::serde::ts_seconds; +use chrono::{DateTime, Utc}; +use serde::Deserialize; +use serde_repr::Deserialize_repr; + + +#[derive(Default, Debug, Clone, Deserialize)] +pub(crate) struct UnifiSite { + #[serde(rename(deserialize = "name"))] + pub(crate) code: Box, + pub(crate) desc: Box, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize_repr)] +#[repr(u8)] +pub(crate) enum DeviceState { + Offline = 0, + Connected = 1, + Unknown = 2, + Isolated = 11 +} + +impl DeviceState { + + #[inline] + pub(crate) fn as_str(&self) -> &'static str { + match self { + DeviceState::Offline => "Offline", + DeviceState::Connected => "Connected", + DeviceState::Unknown => "Unknown", + DeviceState::Isolated => "Isolated" + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +pub(crate) struct UnifiDeviceBasic { + pub(crate) mac: Box, + pub(crate) state: DeviceState, + pub(crate) adopted: bool, + #[serde(rename(deserialize = "type"))] + pub(crate) device_type: Box, + #[serde(rename(deserialize = "model"))] + pub(crate) device_model: Box, + #[serde(rename(deserialize = "in_gateway_mode"))] + pub(crate) gateway_mode: bool, + #[serde(rename(deserialize = "name"))] + pub(crate) name_option: Option>, + #[serde(skip_deserializing)] + pub(crate) device_label_option: Option<&'static str>, + #[serde(skip_deserializing)] + pub(crate) site: Box +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +pub(crate) struct UnifiDeviceFull { + #[serde(flatten)] + device: UnifiDeviceBasic, + port_table: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +pub(crate) struct Port { + name: Box, + ifname: Box, + mac: Box, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +pub(crate) struct ClientDevice { + last_ip: Box, + oui: Box, + #[serde(with = "ts_seconds")] + first_seen: DateTime, + #[serde(with = "ts_seconds")] + last_seen: DateTime, + is_wired: bool, + #[serde(rename(deserialize = "last_connection_network_name"))] + network_name: Box, + mac: Box, + hostname: Box +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +pub(crate) struct ClientDeviceActive { + #[serde(rename(deserialize = "assoc_time"), with = "ts_seconds")] + session_start: DateTime, + #[serde(rename(deserialize = "latest_assoc_time"), with = "ts_seconds")] + session_latest: DateTime, + oui: Box, + last_ip: Box, + #[serde(with = "ts_seconds")] + first_seen: DateTime, + #[serde(with = "ts_seconds")] + last_seen: DateTime, + is_wired: bool, + #[serde(rename(deserialize = "last_connection_network_name"))] + network_name: Box, + mac: Box, + hostname: Box, + uptime: usize, +} + + +pub(crate) fn get_device_label(device_type: &str, device_model: &str) -> Option<&'static str> { + match device_type { + "uap" => { + match device_model { + "BZ2" => Some("UAP / Access Point"), + "BZ2LR" => Some("UAP-LR / Access Point Long-Range"), + "U2HSR" => Some("UAP-Outdoor+ / Access Point Outdoor+"), + "U2IW" => Some("UAP-IW / Access Point In-Wall"), + "U2L48" => Some("UAP-LR / Access Point Long-Range"), + "U2Lv2" => Some("UAP-LRv2 / Access Point Long-Range"), + "U2M" => Some("UAP-Mini / Access Point Mini"), + "U2O" => Some("UAP-Outdoor / Access Point Outdoor"), + "U2S48" => Some("UAP / Access Point"), + "U2Sv2" => Some("UAPv2 / Access Point"), + "U5O" => Some("UAP-Outdoor5 / Access Point Outdoor 5"), + "U6ENT" => Some("U6-Enterprise / Access Point WiFi 6 Enterprise"), + "U6EXT" => Some("U6-Extender / Access Point WiFi 6 Extender"), + "U6IW" => Some("U6-IW / Access Point WiFi 6 In-Wall"), + "U6M" => Some("U6-Mesh / Access Point WiFi 6 Mesh"), + "U7E" => Some("UAP-AC / Access Point AC"), + "U7EDU" => Some("UAP-AC-EDU / Access Point AC EDU"), + "U7Ev2" => Some("UAP-AC / Access Point AC"), + "U7HD" => Some("UAP-AC-HD / Access Point AC HD"), + "U7IW" => Some("UAP-AC-IW / Access Point AC In-Wall"), + "U7IWP" => Some("UAP-AC-IW-Pro / Access Point AC In-Wall Pro"), + "U7LR" => Some("UAP-AC-LR / Access Point AC Long-Range"), + "U7LT" => Some("UAP-AC-Lite / Access Point AC Lite"), + "U7MP" => Some("UAP-AC-M-Pro / Access Point AC Mesh Pro"), + "U7MSH" => Some("UAP-AC-M / Access Point AC Mesh"), + "U7NHD" => Some("UAP-nanoHD / Access Point nanoHD"), + "U7O" => Some("UAP-AC-Outdoor / Access Point AC Outdoor"), + "U7P" => Some("UAP-AC-Pro / Access Point AC Pro"), + "U7PG2" => Some("UAP-AC-Pro / Access Point AC Pro"), + "U7SHD" => Some("UAP-AC-SHD / Access Point AC SHD"), + "UAE6" => Some("U6-Extender-EA / Access Point WiFi 6 Extender"), + "UAIW6" => Some("U6-IW-EA / Access Point WiFi 6 In-Wall"), + "UAL6" => Some("U6-Lite / Access Point WiFi 6 Lite"), + "UALR6" => Some("U6-LR-EA / Access Point WiFi 6 Long-Range"), + "UALR6v2" => Some("U6-LR / Access Point WiFi 6 Long-Range"), + "UALR6v3" => Some("U6-LR / Access Point WiFi 6 Long-Range"), + "UAM6" => Some("U6-Mesh-EA / Access Point WiFi 6 Mesh"), + "UAP6" => Some("U6-LR / Access Point WiFi 6 Long-Range"), + "UAP6MP" => Some("U6-Pro / Access Point WiFi 6 Pro"), + "UCMSH" => Some("UAP-XG-Mesh / Access Point Mesh XG"), + "UCXG" => Some("UAP-XG / Access Point XG"), + "UDMB" => Some("UAP-BeaconHD / Access Point BeaconHD"), + "UFLHD" => Some("UAP-FlexHD / Access Point FlexHD"), + "UHDIW" => Some("UAP-IW-HD / Access Point In-Wall HD"), + "ULTE" => Some("U-LTE / UniFi LTE"), + "ULTEPEU" => Some("U-LTE-Pro / UniFi LTE Pro"), + "ULTEPUS" => Some("U-LTE-Pro / UniFi LTE Pro"), + "UP1" => Some("USP-Plug / SmartPower Plug"), + "UP6" => Some("USP-Strip / SmartPower Strip (6 ports)"), + "UXBSDM" => Some("UWB-XG-BK / WiFi BaseStation XG"), + "UXSDM" => Some("UWB-XG / WiFi BaseStation XG"), + "p2N" => Some("PICOM2HP / PicoStation M2 HP"), + _ => None + } + }, + "usw" => { + match device_model { + "S216150" => Some("US-16-150W / Switch 16 PoE (150 W)"), + "S224250" => Some("US-24-250W / Switch 24 PoE (250 W)"), + "S224500" => Some("US-24-500W / Switch 24 PoE (500 W)"), + "S248500" => Some("US-48-500W / Switch 48 PoE (500 W)"), + "S248750" => Some("US-48-750W / Switch 48 PoE (750 W)"), + "S28150" => Some("US-8-150W / Switch 8 PoE (150 W)"), + "UDC48X6" => Some("USW-Leaf / Switch Leaf"), + "US16P150" => Some("US-16-150W / Switch 16 PoE (150 W)"), + "US24" => Some("USW-24-G1 / Switch 24"), + "US24P250" => Some("US-24-250W / Switch 24 PoE (250 W)"), + "US24P500" => Some("US-24-500W / Switch 24 PoE (500 W)"), + "US24PL2" => Some("US-L2-24-PoE / Switch 24 PoE"), + "US24PRO" => Some("USW-Pro-24-PoE / Switch Pro 24 PoE"), + "US24PRO2" => Some("USW-Pro-24 / Switch Pro 24"), + "US48" => Some("US-48-G1 / Switch 48"), + "US48P500" => Some("US-48-500W / Switch 48 PoE (500 W)"), + "US48P750" => Some("US-48-750W / Switch 48 PoE (750 W)"), + "US48PL2" => Some("US-L2-48-PoE / Switch 48 PoE"), + "US48PRO" => Some("USW-Pro-48-PoE / Switch Pro 48 PoE"), + "US48PRO2" => Some("USW-Pro-48 / Switch Pro 48"), + "US624P" => Some("USW-Enterprise-24-PoE / Switch Enterprise 24 PoE"), + "US648P" => Some("USW-Enterprise-48-PoE / Switch Enterprise 48 PoE"), + "US68P" => Some("USW-Enterprise-8-PoE / Switch Enterprise 8 PoE"), + "US6XG150" => Some("US-XG-6PoE / Switch 6 XG PoE"), + "US8" => Some("US-8 / Switch 8"), + "US8P150" => Some("US-8-150W / Switch 8 PoE (150 W)"), + "US8P60" => Some("US-8-60W / Switch 8 (60 W)"), + "USAGGPRO" => Some("USW-Pro-Aggregation / Switch Aggregation Pro"), + "USC8" => Some("US-8 / Switch 8"), + "USC8P150" => Some("US-8-150W / Switch 8 PoE (150 W)"), + "USC8P450" => Some("USW-Industrial / Switch Industrial"), + "USC8P60" => Some("US-8-60W / Switch 8 (60 W)"), + "USF5P" => Some("USW-Flex / Switch Flex"), + "USFXG" => Some("USW-Flex-XG / Switch Flex XG"), + "USL16LP" => Some("USW-Lite-16-PoE / Switch Lite 16 PoE"), + "USL16P" => Some("USW-16-PoE / Switch 16 PoE"), + "USL24" => Some("USW-24-G2 / Switch 24"), + "USL24P" => Some("USW-24-PoE / Switch 24 PoE"), + "USL48" => Some("USW-48-G2 / Switch 48"), + "USL48P" => Some("USW-48-PoE / Switch 48 PoE"), + "USL8A" => Some("USW-Aggregation / Switch Aggregation"), + "USL8LP" => Some("USW-Lite-8-PoE / Switch Lite 8 PoE"), + "USL8MP" => Some("USW-Mission-Critical / Switch Mission Critical"), + "USMINI" => Some("USW-Flex-Mini / Switch Flex Mini"), + "USPPDUP" => Some("USP-PDU-Pro / SmartPower PDU Pro"), + "USPRPS" => Some("USP-RPS / SmartPower Redundant Power System"), + "USXG" => Some("US-16-XG / Switch XG 16"), + "USXG24" => Some("USW-EnterpriseXG-24 / Switch Enterprise XG 24"), + _ => None + } + }, + "ugw" => { + match device_model { + "UGW3" => Some("USG-3P / Security Gateway"), + "UGW4" => Some("USG-Pro-4 / Security Gateway Pro"), + "UGWHD4" => Some("USG / Security Gateway"), + "UGWXG" => Some("USG-XG-8 / Security Gateway XG"), + _ => None + } + }, + "uxg" => { + match device_model { + "UXGPRO" => Some("UXG-Pro / Next-Generation Gateway Pro"), + _ => None + } + }, + "ubb" => { + match device_model { + "UBB" => Some("UBB / Building-to-Building Bridge"), + "UBBXG" => Some("UBB-XG / Building-to-Building Bridge XG"), + _ => None + } + }, + "uas" => { + match device_model { + "UASXG" => Some("UAS-XG / Application Server XG"), + _ => None + } + }, + "udm" => { + match device_model { + "UDM" => Some("UDM / Dream Machine"), + "UDMPRO" => Some("UDM-Pro / Dream Machine Pro"), + "UDMPROSE" => Some("UDM-SE / Dream Machine Special Edition"), + "UDR" => Some("UDR / Dream Router"), + "UDW" => Some("UDW / Dream Wall"), + "UDWPRO" => Some("UDWPRO / Dream Wall Pro"), + _ => None + } + }, + "uck" => { + match device_model { + "UCK" => Some("UCK / Cloud Key"), + "UCK-v2" => Some("UCK / Cloud Key"), + "UCK-v3" => Some("UCK / Cloud Key"), + "UCKG2" => Some("UCK-G2 / Cloud Key Gen2"), + "UCKP" => Some("UCK-G2-Plus / Cloud Key Gen2 Plus"), + _ => None + } + }, + "uph" => { + match device_model { + "UP4" => Some("UVP-X / Phone"), + "UP5" => Some("UVP / Phone"), + "UP5c" => Some("UVP / Phone"), + "UP5t" => Some("UVP-Pro / Phone Professional"), + "UP5tc" => Some("UVP-Pro / Phone Professional"), + "UP7" => Some("UVP-Executive / Phone Executive"), + "UP7c" => Some("UVP-Executive / Phone Executive"), + _ => None + } + }, + _ => None + } +} diff --git a/src/unifi/mod.rs b/src/unifi/mod.rs new file mode 100644 index 0000000..512a13e --- /dev/null +++ b/src/unifi/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod api; +pub(crate) mod devices; +pub(crate) mod search; \ No newline at end of file diff --git a/src/unifi/search.rs b/src/unifi/search.rs new file mode 100644 index 0000000..8744083 --- /dev/null +++ b/src/unifi/search.rs @@ -0,0 +1,109 @@ +use crate::{ + gui::{CancelSignal, ChannelsSearchThread}, + mac_address::validation::text_is_valid_mac, + unifi::{ + api::{UnifiClient, UnifiAPIError}, + devices::{UnifiDeviceBasic, get_device_label} + } +}; +//use std::time::Duration; +use zeroize::Zeroize; + + +#[derive(Default, Debug, Clone)] +pub struct UnifiSearchInfo { + pub username: String, + pub password: String, + pub server_url: String, + pub mac_to_search: String, + pub accept_invalid_certs: bool, +} + +pub type UnifiSearchResult = Result, UnifiAPIError>; + +// #[derive(Debug, Clone)] +// pub(crate) enum UnifiSearchType { +// NetworkDevice, +// ClientDevice +// } + +fn get_client_and_login<'a>(username: &mut str, password: &mut str, server_url: &'a str, accept_invalid_certs: bool) -> Result, UnifiAPIError> { + let mut client = UnifiClient::new(server_url, accept_invalid_certs)?; + let login_result = client.login(username, password); + + // zeroize the user entered data for security + password.zeroize(); + username.zeroize(); + + // return any errors with the login + login_result?; + // if we make it here, we should be logged in + debug_assert!(client.is_logged_in()); + Ok(client) +} + + +pub fn find_unifi_device( + search_info: &mut UnifiSearchInfo, + search_thread_channels: &mut ChannelsSearchThread, +) -> UnifiSearchResult { + let UnifiSearchInfo { + username, + password, + ref server_url, + ref mac_to_search, + ref accept_invalid_certs + } = search_info; + + let mut client = get_client_and_login(username, password, server_url, *accept_invalid_certs)?; + + // check for cancel signal; if channel empty, move on + if let Ok(v) = search_thread_channels.signal_rx.try_recv() { + if v == CancelSignal { + return Ok(None); + } + } + + let mac_str = mac_to_search.as_str(); + let mut unifi_sites = client.get_sites()?; + let unifi_sites_len = unifi_sites.len() as f32; + //dbg!(&unifi_sites); + + for (iter_num, site) in unifi_sites.iter_mut().enumerate() { + // check for cancel signal each iteration + if let Ok(v) = search_thread_channels.signal_rx.try_recv() { + if v == CancelSignal { + return Ok(None); + } + } + + { + // send percentage of search completion to GUI thread + let _ = search_thread_channels + .percentage_tx + .try_send(iter_num as f32 / unifi_sites_len); + } + + // get devices from a specific site + let site_devices = client.get_site_devices_basic(&site.code)?; + /*if let Some(device) = site_devices.iter().find(|device| !device.adopted) { + dbg!(&site); + dbg!(device); + }*/ + let unifi_device_option = site_devices.into_iter() + .filter(|device| text_is_valid_mac(device.mac.as_bytes())) + .find(|device| mac_str == device.mac.to_lowercase().as_str()); + + if let Some(mut unifi_device) = unifi_device_option { + { + // set percentage to 100% since we got a match + let _ = search_thread_channels.percentage_tx.try_send(1f32); + } + + unifi_device.device_label_option = get_device_label(&unifi_device.device_type, &unifi_device.device_model); + unifi_device.site = std::mem::take(&mut site.desc); + return Ok(Some(unifi_device)); + } + } + Ok(None) +} \ No newline at end of file