From 13f296d33fba6a0dacfa67909ca48d0b7b52c887 Mon Sep 17 00:00:00 2001 From: copygirl Date: Tue, 7 Jan 2025 00:14:23 +0100 Subject: [PATCH] WIP --- .vscode/launch.json | 56 +++- Cargo.lock | 531 +++++++++++++++++++++++++++++++++++ Cargo.toml | 16 +- client/Cargo.toml | 6 + client/src/connection_ui.rs | 144 ++++++++++ client/src/main.rs | 76 +---- client/src/server_address.rs | 236 ++++++++++++++++ common/Cargo.toml | 3 + server/Cargo.toml | 2 + server/src/main.rs | 6 +- 10 files changed, 997 insertions(+), 79 deletions(-) create mode 100644 client/src/connection_ui.rs create mode 100644 client/src/server_address.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index 79e700f..4683ef1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,13 +6,27 @@ "request": "launch", "name": "Debug client", "cargo": { - "args": [ "build", "--bin=gaemstone-client", "--package=gaemstone-client" ], - "filter": { "name": "gaemstone-client", "kind": "bin" }, + "args": [ + "build", + "--bin", "gaemstone-client", + "--package", "gaemstone-client", + // "--features", "bevy/dynamic_linking", + ], + "filter": { + "name": "gaemstone-client", + "kind": "bin", + }, + }, + "env": { + // When Bevy looks for assets, it checks BEVY_ASSET_ROOT, CARGO_MANIFEST_DIR, and + // then falls back to the executable directory. When debugging, the cargo manifest + // directory is not set, so we need to specify this environment variable. + "BEVY_ASSET_ROOT": "${workspaceFolder}", + // When debugging with the dynamic_linking feature enabled, we need to add + // the right directories to LD_LIBRARY_PATH, for the libraries to be found. + // NOTE: Currently disabled. Find a way to do this in a portable way. + // "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug/deps:${userHome}/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib:${env:LD_LIBRARY_PATH}", }, - // When Bevy looks for assets, it checks BEVY_ASSET_ROOT, CARGO_MANIFEST_DIR, and - // then falls back to the executable directory. When debugging, the cargo manifest - // directory is not set, so we need to specify this environment variable. - "env": { "BEVY_ASSET_ROOT": "${workspaceFolder}" }, "args": [], }, { @@ -20,10 +34,34 @@ "request": "launch", "name": "Debug server", "cargo": { - "args": [ "build", "--bin=gaemstone-server", "--package=gaemstone-server" ], - "filter": { "name": "gaemstone-server", "kind": "bin" }, + "args": [ + "build", + "--bin", "gaemstone-server", + "--package", "gaemstone-server", + // "--features", "bevy/dynamic_linking", + ], + "filter": { + "name": "gaemstone-server", + "kind": "bin", + }, + }, + "args": [], + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug client unit tests", + "cargo": { + "args": [ + "test", + "--no-run", + ], + "filter": { + "name": "gaemstone-client", + "kind": "bin", + }, }, "args": [], } - ] + ], } diff --git a/Cargo.lock b/Cargo.lock index 83045eb..5a2ed3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "accesskit" version = "0.17.1" @@ -105,6 +121,23 @@ dependencies = [ "web-time", ] +[[package]] +name = "aeronet_replicon" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27f508133f39a100fbf2192222790c875d0a77997952806582229095fc79893" +dependencies = [ + "aeronet_io", + "aeronet_transport", + "anyhow", + "bevy_app", + "bevy_ecs", + "bevy_hierarchy", + "bevy_reflect", + "bevy_replicon", + "tracing", +] + [[package]] name = "aeronet_transport" version = "0.11.0" @@ -282,6 +315,24 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "windows-sys 0.48.0", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -640,6 +691,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", + "serde", "uuid", ] @@ -732,6 +784,42 @@ dependencies = [ "syn", ] +[[package]] +name = "bevy_egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "954fbe8551af4b40767ea9390ec7d32fe1070a6ab55d524cf0868c17f8469a55" +dependencies = [ + "arboard", + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_input", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_time", + "bevy_utils", + "bevy_window", + "bevy_winit", + "bytemuck", + "crossbeam-channel", + "egui", + "encase", + "js-sys", + "log", + "thread_local", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webbrowser", + "wgpu-types", + "winit", +] + [[package]] name = "bevy_encase_derive" version = "0.15.0" @@ -875,6 +963,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "derive_more", + "serde", "smol_str", ] @@ -1150,6 +1239,21 @@ dependencies = [ "syn", ] +[[package]] +name = "bevy_replicon" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e0a980e9483807ef5cf4f7f9a4f0073dc1f2146a6cc0a2f17b9236ae6d7b4d" +dependencies = [ + "bevy", + "bincode", + "bitflags 2.6.0", + "bytes", + "integer-encoding", + "ordered-multimap", + "serde", +] + [[package]] name = "bevy_scene" version = "0.15.0" @@ -1198,6 +1302,7 @@ dependencies = [ "nonmax", "radsort", "rectangle-pack", + "serde", ] [[package]] @@ -1280,6 +1385,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "crossbeam-channel", + "serde", ] [[package]] @@ -1294,6 +1400,7 @@ dependencies = [ "bevy_math", "bevy_reflect", "derive_more", + "serde", ] [[package]] @@ -1325,6 +1432,7 @@ dependencies = [ "bytemuck", "derive_more", "nonmax", + "serde", "smallvec", "taffy", ] @@ -1370,6 +1478,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "raw-window-handle", + "serde", "smol_str", ] @@ -1400,12 +1509,22 @@ dependencies = [ "cfg-if", "crossbeam-channel", "raw-window-handle", + "serde", "wasm-bindgen", "web-sys", "wgpu-types", "winit", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -1591,6 +1710,18 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" version = "1.2.2" @@ -1646,6 +1777,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -2077,6 +2217,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "document-features" version = "0.2.10" @@ -2098,12 +2247,43 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "ecolor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" +dependencies = [ + "ahash", + "emath", + "epaint", + "nohash-hasher", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "emath" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" +dependencies = [ + "bytemuck", +] + [[package]] name = "encase" version = "0.10.0" @@ -2136,6 +2316,28 @@ dependencies = [ "syn", ] +[[package]] +name = "epaint" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "nohash-hasher", + "parking_lot", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" + [[package]] name = "equivalent" version = "1.0.1" @@ -2162,6 +2364,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + [[package]] name = "euclid" version = "0.22.11" @@ -2434,22 +2642,32 @@ name = "gaemstone-client" version = "0.1.0" dependencies = [ "aeronet", + "aeronet_replicon", "aeronet_webtransport", "bevy", + "bevy_egui", + "bevy_replicon", "gaemstone-common", + "hostname-validator", ] [[package]] name = "gaemstone-common" version = "0.1.0" +dependencies = [ + "bevy", + "disqualified", +] [[package]] name = "gaemstone-server" version = "0.1.0" dependencies = [ "aeronet", + "aeronet_replicon", "aeronet_webtransport", "bevy", + "bevy_replicon", "gaemstone-common", ] @@ -2732,6 +2950,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + [[package]] name = "httlib-huffman" version = "0.3.4" @@ -2887,6 +3120,7 @@ dependencies = [ "byteorder-lite", "num-traits", "png", + "tiff", ] [[package]] @@ -2934,6 +3168,12 @@ dependencies = [ "libc", ] +[[package]] +name = "integer-encoding" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d762194228a2f1c11063e46e32e5acb96e66e906382b9eb5441f2e0504bbd5a" + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -2990,6 +3230,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.74" @@ -3311,6 +3557,12 @@ dependencies = [ "libc", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -3721,12 +3973,31 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser 0.25.1", +] + [[package]] name = "parking" version = "2.2.1" @@ -3946,6 +4217,15 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.6" @@ -4399,12 +4679,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "security-framework" version = "3.0.1" @@ -4537,6 +4836,34 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.6.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] [[package]] name = "smol_str" @@ -4601,6 +4928,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "subtle" version = "2.6.1" @@ -4746,6 +5079,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.37" @@ -4786,6 +5130,31 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4975,6 +5344,12 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + [[package]] name = "twox-hash" version = "1.6.3" @@ -5220,6 +5595,115 @@ version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags 2.6.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.6.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.74" @@ -5265,6 +5749,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" +dependencies = [ + "block2", + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "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 = "23.0.1" @@ -5548,6 +6056,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -5750,6 +6267,7 @@ version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" dependencies = [ + "ahash", "android-activity", "atomic-waker", "bitflags 2.6.0", @@ -5764,6 +6282,7 @@ dependencies = [ "dpi", "js-sys", "libc", + "memmap2", "ndk 0.9.0", "objc2", "objc2-app-kit", @@ -5775,11 +6294,17 @@ dependencies = [ "raw-window-handle", "redox_syscall 0.4.1", "rustix", + "sctk-adwaita", + "smithay-client-toolkit", "smol_str", "tracing", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", "web-sys", "web-time", "windows-sys 0.52.0", @@ -5916,6 +6441,12 @@ dependencies = [ "time", ] +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + [[package]] name = "xkbcommon-dl" version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index ce53373..460731c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,20 @@ [workspace] resolver = "2" -members = [ - "client", - "common", - "server", -] +members = ["client", "common", "server"] + +# Enable a small amount of optimization in the dev profile. +[profile.dev] +opt-level = 1 + +# Enable a large amount of optimization in the dev profile for dependencies. +[profile.dev.package."*"] +opt-level = 3 [workspace.dependencies] # TODO: Disable default features and enable what's needed in specific crates. bevy = "0.15.*" +bevy_replicon = "0.29.*" aeronet = "0.11.*" aeronet_webtransport = "0.11.*" +aeronet_replicon = "0.11.*" diff --git a/client/Cargo.toml b/client/Cargo.toml index 0a12f14..80b97a8 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -7,6 +7,12 @@ edition = "2021" common = { package = "gaemstone-common", path = "../common" } bevy = { workspace = true } +bevy_replicon = { workspace = true } aeronet = { workspace = true } aeronet_webtransport = { workspace = true, features = ["client"] } +aeronet_replicon = { workspace = true, features = ["client"] } + +bevy_egui = "0.31.*" + +hostname-validator = "1.1.1" diff --git a/client/src/connection_ui.rs b/client/src/connection_ui.rs new file mode 100644 index 0000000..2c5f750 --- /dev/null +++ b/client/src/connection_ui.rs @@ -0,0 +1,144 @@ +use { + crate::server_address::ServerAddress, + aeronet_replicon::client::AeronetRepliconClient, + aeronet_webtransport::{ + cert::{self, CertificateHash}, + client::{ClientConfig, WebTransportClient}, + }, + bevy::prelude::*, + bevy_egui::{egui, EguiContexts}, + bevy_replicon::prelude::*, +}; + +pub struct ConnectionUiPlugin; + +impl Plugin for ConnectionUiPlugin { + fn build(&self, app: &mut App) { + app.init_resource::().add_systems( + Update, + display_connection_ui.run_if(not(client_connected.or(client_connecting))), + ); + } +} + +#[derive(Default, Resource)] +struct ConnectionUiState { + reveal: bool, + address: String, + cert_hash: String, +} + +fn display_connection_ui( + commands: Commands, + mut contexts: EguiContexts, + mut ui_state: ResMut, +) { + let default_address_str = format!("[::1]:{}", common::WEB_TRANSPORT_PORT); + let default_address = default_address_str.parse().unwrap(); + + egui::Window::new("Connect to Server") + .anchor(egui::Align2::CENTER_CENTER, [0., 0.]) + .collapsible(false) + .resizable(false) + .show(contexts.ctx_mut(), |ui| { + let password = !ui_state.reveal; + egui::Grid::new("connection_info_grid") + .num_columns(2) + .show(ui, |ui| { + ui.label("Server Address:"); + egui::TextEdit::singleline(&mut ui_state.address) + .hint_text(&default_address_str) + .password(password) + .show(ui); + ui.end_row(); + + ui.label("Certificate Hash:"); + egui::TextEdit::singleline(&mut ui_state.cert_hash) + .password(password) + .show(ui); + ui.end_row(); + }); + + ui.horizontal(|ui| { + ui.checkbox(&mut ui_state.reveal, "Reveal Address / Hash"); + + let address = if ui_state.address.is_empty() { + Some(default_address) + } else if let Ok(address) = ui_state.address.parse::() { + Some(address.with_default_port(common::WEB_TRANSPORT_PORT)) + } else { + None + }; + + let (cert_hash, cert_valid) = if ui_state.cert_hash.is_empty() { + (None, true) + } else if let Ok(cert_hash) = cert::hash_from_b64(&ui_state.cert_hash) { + (Some(cert_hash), true) + } else { + (None, false) + }; + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.horizontal(|ui| { + ui.add_enabled_ui(address.is_some() && cert_valid, |ui| { + if ui.button(" Connect ").clicked() { + connect_to_server(commands, &address.unwrap(), cert_hash); + } + }); + }); + }); + }); + }); +} + +fn connect_to_server( + mut commands: Commands, + address: &ServerAddress, + cert_hash: Option, +) { + // When using a self-signed cert from the server, its hash needs to be provided to the + // client for it to accept it. Works on native and WASM. On WASM we have the option to + // just accept the HTTPS server's certificate. On native we could theoretically disable + // cert checking entirely, but that kind of defeats the point. + + // TODO: Add support for SRV records to `ServerAddress`. + // TODO: (WASM) Auto-connect to address of the server hosting the webpage. + // FIXME: (WASM) `serverCertificateHashes` not accepted in Firefox. https://github.com/BiagioFesta/wtransport/issues/241 + let config = web_transport_config(cert_hash); + let target = format!("https://{address}"); + commands + .spawn((Name::new(address.to_string()), AeronetRepliconClient)) + .queue(WebTransportClient::connect(config, target)); +} + +#[cfg(not(target_family = "wasm"))] +fn web_transport_config(cert_hash: Option) -> ClientConfig { + use {aeronet_webtransport::wtransport::tls::Sha256Digest, core::time::Duration}; + let server_certificate_hashes = cert_hash + .map(Sha256Digest::new) + .into_iter() + .collect::>(); + ClientConfig::builder() + .with_bind_default() + .with_server_certificate_hashes(server_certificate_hashes) + .keep_alive_interval(Some(Duration::from_secs(1))) + .max_idle_timeout(Some(Duration::from_secs(5))) + .unwrap() + .build() +} + +#[cfg(target_family = "wasm")] +fn web_transport_config(cert_hash: Option) -> ClientConfig { + use aeronet_webtransport::xwt_web_sys::{CertificateHash, HashAlgorithm}; + let server_certificate_hashes = cert_hash + .map(|hash| CertificateHash { + algorithm: HashAlgorithm::Sha256, + value: Vec::from(hash), + }) + .into_iter() + .collect::>(); + ClientConfig { + server_certificate_hashes, + ..Default::default() + } +} diff --git a/client/src/main.rs b/client/src/main.rs index 05b5dbd..4fa8305 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -6,14 +6,16 @@ use { }, transport::TransportConfig, }, - aeronet_webtransport::{ - cert, - cert::CertificateHash, - client::{ClientConfig, WebTransportClient, WebTransportClientPlugin}, - }, + aeronet_replicon::client::AeronetRepliconClientPlugin, + aeronet_webtransport::client::WebTransportClientPlugin, bevy::{prelude::*, window::WindowResolution}, + bevy_egui::EguiPlugin, + bevy_replicon::RepliconPlugins, }; +mod connection_ui; +mod server_address; + fn main() -> AppExit { App::new() .add_plugins(( @@ -31,9 +33,13 @@ fn main() -> AppExit { }), ..default() }), + EguiPlugin, + RepliconPlugins, WebTransportClientPlugin, + AeronetRepliconClientPlugin, + connection_ui::ConnectionUiPlugin, )) - .add_systems(Startup, (setup, connect_to_server)) + .add_systems(Startup, setup) .add_observer(on_connecting) .add_observer(on_connected) .add_observer(on_disconnected) @@ -45,64 +51,6 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Sprite::from_image(asset_server.load("heck.png"))); } -fn connect_to_server(mut commands: Commands) { - // When using a self-signed cert, its hash needs to be provided to the - // client for it to accept it. Works on native and WASM. On WASM we have - // the option to just accept the HTTPS server's certificate. On native we - // could theoretically disable cert checking entirely, but that kind of - // defeats the point. - - // TODO: Do not hardcode the server's certificate hash. Enter in a UI of some sort? - // For now, just manually replace the `todo!` with the hash that gets - // logged in the server output. It looks something like this: - // ************************ - // SPKI FINGERPRINT - // 1YLqE3c3ZsRBos35nUrMETfZtCUVIxyIjcskEq0LFYE= - // CERTIFICATE HASH - // z3cWU+Pc209kffV440ksqcWxMcCTi9QO6qI7VjVOQfU= - // ************************ - - let target = format!("https://[::1]:{}", common::WEB_TRANSPORT_PORT); - let cert_hash = todo!(); - let cert_hash = cert::hash_from_b64(cert_hash).unwrap(); - let config = web_transport_config(Some(cert_hash)); - commands - .spawn(Name::new(target.clone())) - .queue(WebTransportClient::connect(config, target)); -} - -#[cfg(target_family = "wasm")] -fn web_transport_config(cert_hash: Option) -> ClientConfig { - use aeronet_webtransport::xwt_web_sys::{CertificateHash, HashAlgorithm}; - let server_certificate_hashes = cert_hash - .map(|hash| CertificateHash { - algorithm: HashAlgorithm::Sha256, - value: Vec::from(hash), - }) - .into_iter() - .collect::>(); - ClientConfig { - server_certificate_hashes, - ..Default::default() - } -} - -#[cfg(not(target_family = "wasm"))] -fn web_transport_config(cert_hash: Option) -> ClientConfig { - use {aeronet_webtransport::wtransport::tls::Sha256Digest, core::time::Duration}; - let server_certificate_hashes = cert_hash - .map(Sha256Digest::new) - .into_iter() - .collect::>(); - ClientConfig::builder() - .with_bind_default() - .with_server_certificate_hashes(server_certificate_hashes) - .keep_alive_interval(Some(Duration::from_secs(1))) - .max_idle_timeout(Some(Duration::from_secs(5))) - .unwrap() - .build() -} - fn on_connecting(trigger: Trigger, names: Query<&Name>) { let session = trigger.entity(); let name = names.get(session).unwrap(); diff --git a/client/src/server_address.rs b/client/src/server_address.rs new file mode 100644 index 0000000..b53dceb --- /dev/null +++ b/client/src/server_address.rs @@ -0,0 +1,236 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ServerAddress { + host: ServerHost, + port: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum ServerHost { + Hostname(String), + Ipv4Addr(Ipv4Addr), + Ipv6Addr(Ipv6Addr), +} + +impl ServerAddress { + pub fn host(&self) -> &ServerHost { + &self.host + } + + pub fn port(&self) -> Option { + self.port + } + + /// Returns a new `ServerAddress` with the specified port if + /// this instance did not already have an explicit port set. + pub fn with_default_port(self, default_port: u16) -> Self { + Self { + port: self.port.or(Some(default_port)), + ..self + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ParseError { + /// The given string (or its host or port) was empty. + Empty, + /// The given string was not a valid hostname. + Hostname, + /// The given string looked like an IPv4 address (`#.#.#.#`) but turned out to be invalid. + Ipv4, + /// The given string started with a '[' but did not contain a valid IPv6 address. + Ipv6, + /// The given port was invalid or out of range. + Port, +} + +impl std::str::FromStr for ServerAddress { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let colon = match (s.rfind(':'), s.rfind(']')) { + // This doesn't match colons within IPv6 brackets (`[::1]:80`). + (Some(colon), Some(bracket)) if colon > bracket => Some(colon), + (Some(colon), None) => Some(colon), + _ => None, + }; + + let host_slice = colon.map(|i| &s[..i]).unwrap_or(s); + let host = host_slice.parse()?; + + if colon == Some(s.len() - 1) { + return Err(ParseError::Empty); + } + + let port_slice = colon.map(|i| &s[(i + 1)..]); + let port_result = port_slice.map(str::parse).transpose(); + let port = port_result.map_err(|_| ParseError::Port)?; + + Ok(Self { host, port }) + } +} + +impl std::str::FromStr for ServerHost { + type Err = ParseError; + fn from_str(s: &str) -> Result { + if s.is_empty() { + Err(ParseError::Empty) + } else if s.chars().nth(0).unwrap() == '[' { + if s.chars().last().unwrap() == ']' { + let result = s[1..(s.len() - 1)].parse(); + result.map(Self::Ipv6Addr).map_err(|_| ParseError::Ipv6) + } else { + Err(ParseError::Ipv6) + } + } else if looks_like_ipv4(s) { + s.parse().map(Self::Ipv4Addr).map_err(|_| ParseError::Ipv4) + } else if hostname_validator::is_valid(s) { + Ok(Self::Hostname(s.to_string())) + } else { + Err(ParseError::Hostname) + } + } +} + +impl std::fmt::Display for ServerAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(port) = self.port { + write!(f, "{}:{}", self.host, port) + } else { + write!(f, "{}", self.host) + } + } +} + +impl std::fmt::Display for ServerHost { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServerHost::Hostname(host) => write!(f, "{}", host), + ServerHost::Ipv4Addr(ipv4) => write!(f, "{}", ipv4), + ServerHost::Ipv6Addr(ipv6) => write!(f, "[{}]", ipv6), + } + } +} + +/// Returns whether the supplied string is in the dotted-decimal (`#.#.#.#`) +/// form that IPv4 addresses are (has 4 digit-only sections seperated by dots). +/// Does not verify the individual values are within valid ranges (0-255). +fn looks_like_ipv4(s: &str) -> bool { + let mut num_dots = 0; + let mut previous_char = '.'; + for c in s.chars() { + match c { + // Dot at beginning or after another dot is invalid. + '.' if previous_char == '.' => return false, + // More than 3 dots aren't valid, let's stop iterating. + '.' if num_dots == 3 => return false, + // More dots! + '.' => num_dots += 1, + // Digits are valid. + '0'..'9' => {} + // Anything else isn't. + _ => return false, + } + previous_char = c; + } + // Must contain 3 dots and not end with one. + (num_dots == 3) && (previous_char != '.') +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn valid_addresses() { + // No port. + assert!(parse("localhost").is_ok()); + assert!(parse("0.1-example.net2").is_ok()); + assert!(parse("127.0.0.1").is_ok()); + assert!(parse("255.255.255.255").is_ok()); + assert!(parse("[::]").is_ok()); + assert!(parse("[2001:db8:85a3::8a2e:370:7334]").is_ok()); + assert!(parse("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]").is_ok()); + + // Yes port. + assert!(parse("localhost:0").is_ok()); + assert!(parse("0.1-example.net2:80").is_ok()); + assert!(parse("127.0.0.1:420").is_ok()); + assert!(parse("[::]:8080").is_ok()); + assert!(parse("[2001:db8:85a3::8a2e:370:7334]:25565").is_ok()); + assert!(parse("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:65535").is_ok()); + + assert_host_and_port("localhost:80", "localhost", 80); + assert_host_and_port("0.0.0.0:69", "0.0.0.0", 69); + assert_host_and_port("[::1]:1337", "[::1]", 1337); + + // Sussy but technically valid hostnames?? + assert_is_hostname("12.34.56"); + assert_is_hostname("12.34.56.78.90:123"); + } + + #[test] + fn invalid_addresses() { + assert_eq!(parse(""), Err(ParseError::Empty)); + assert_eq!(parse(":"), Err(ParseError::Empty)); + assert_eq!(parse(":80"), Err(ParseError::Empty)); + assert_eq!(parse("abc:"), Err(ParseError::Empty)); + + assert_eq!(parse("[::]:a"), Err(ParseError::Port)); + assert_eq!(parse("[::]:20a"), Err(ParseError::Port)); + assert_eq!(parse("[::]:65536"), Err(ParseError::Port)); + assert_eq!(parse("example.net:42.0"), Err(ParseError::Port)); + + // Not necessary to verify many of these, as we don't parse them ourselves. + assert_eq!(parse("["), Err(ParseError::Ipv6)); + assert_eq!(parse("[]"), Err(ParseError::Ipv6)); + assert_eq!(parse("[localhost]"), Err(ParseError::Ipv6)); + + // Bare IPv6 values are not supported, they must be wrapped in brackets. + assert_eq!(parse("::"), Err(ParseError::Hostname)); + assert_eq!(parse("::1:1337"), Err(ParseError::Hostname)); + assert_eq!(parse("2001:4860:4860::8888"), Err(ParseError::Hostname)); + + // Anything that "looks like an IPv4 address" will attempt to parse as such. + assert_eq!(parse("253.254.255.256"), Err(ParseError::Ipv4)); + assert_eq!(parse("1.22.333.4444"), Err(ParseError::Ipv4)); + assert_eq!(parse("01.02.03.04"), Err(ParseError::Ipv4)); + + assert_eq!(parse("..."), Err(ParseError::Hostname)); + assert_eq!(parse("a..b"), Err(ParseError::Hostname)); + assert_eq!(parse("example.net]"), Err(ParseError::Hostname)); + assert_eq!(parse("foo.-bar.example"), Err(ParseError::Hostname)); + assert_eq!(parse("admin@example.net"), Err(ParseError::Hostname)); + } + + #[test] + fn address_with_default_port() { + assert_default_port("localhost", 12345, 12345); + assert_default_port("localhost:80", 12345, 80); + assert_default_port("[::1]", 12345, 12345); + assert_default_port("[::1]:80", 12345, 80); + assert_default_port("1.2.3.4", 12345, 12345); + assert_default_port("1.2.3.4:80", 12345, 80); + } + + fn parse(s: &str) -> Result { + s.parse::() + } + + fn assert_host_and_port(s: &str, host: &str, port: u16) { + let address = parse(s).unwrap(); + assert_eq!(address.host().to_string(), host); + assert_eq!(address.port(), Some(port)); + } + + fn assert_is_hostname(s: &str) { + let host = parse(s).unwrap().host().clone(); + assert!(matches!(host, ServerHost::Hostname(_))); + } + + fn assert_default_port(s: &str, default_port: u16, expected_port: u16) { + let address = parse(s).unwrap().with_default_port(default_port); + assert_eq!(address.port(), Some(expected_port)); + } +} diff --git a/common/Cargo.toml b/common/Cargo.toml index a371a93..cec6516 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] +disqualified = "1.0.0" + +bevy = { workspace = true } diff --git a/server/Cargo.toml b/server/Cargo.toml index 0a5fbe3..a8d4752 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" common = { package = "gaemstone-common", path = "../common" } bevy = { workspace = true } +bevy_replicon = { workspace = true } aeronet = { workspace = true } aeronet_webtransport = { workspace = true, features = ["server"] } +aeronet_replicon = { workspace = true, features = ["server"] } diff --git a/server/src/main.rs b/server/src/main.rs index ef8dc93..2b4e5d8 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,12 +4,14 @@ use { server::Server, Session, }, + aeronet_replicon::server::{AeronetRepliconServer, AeronetRepliconServerPlugin}, aeronet_webtransport::{ cert, server::{SessionRequest, SessionResponse, WebTransportServer, WebTransportServerPlugin}, wtransport::{Identity, ServerConfig}, }, bevy::{log::LogPlugin, prelude::*}, + bevy_replicon::RepliconPlugins, std::time::Duration, }; @@ -18,7 +20,9 @@ fn main() -> AppExit { .add_plugins(( LogPlugin::default(), MinimalPlugins, + RepliconPlugins, WebTransportServerPlugin, + AeronetRepliconServerPlugin, )) .add_systems(Startup, (setup, open_web_transport_server)) .add_observer(on_server_opened) @@ -53,7 +57,7 @@ fn open_web_transport_server(mut commands: Commands) { .build(); commands - .spawn_empty() + .spawn(AeronetRepliconServer) .queue(WebTransportServer::open(config)); info!("Starting WebTransport server ..."); }