diff --git a/.gitignore b/.gitignore index 6985cf1..ead180b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,15 @@ debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +# dotenv environment variable files +.env + +# A bunch of local certificates files for testing +crates/longboy-server-cli/test/data diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..06bebb1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,125 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(Windows) Launch longboy-server-cli", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/longboy-server-cli.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "externalConsole": true + }, + { + "name": "(Windows) Launch longboy-client-cli", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/longboy-client-cli.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "externalConsole": true + }, + { + "type": "cppdbg", + "request": "launch", + "name": "Launch longboy-client-cli", + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/target/debug/longboy-client-cli", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "args": [], + "sourceFileMap": {}, + "osx": { + "MIMode": "lldb" + } + }, + { + "type": "cppdbg", + "request": "launch", + "name": "Launch longboy-server-cli", + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/target/debug/longboy-server-cli", + "envFile": "${workspaceFolder}/.env", + "environment": [ + { + "name": "RUST_BACKTRACE", + "value": "short" + }, + { + "name": "RUSTC_TOOLCHAIN", + "value": "~/.rustup/toolchains/nightly-aarch64-apple-darwin" + } + ], + "args": [], + "sourceFileMap": {}, + "osx": { + "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "program": "${workspaceFolder}/target/debug/longboy-server-cli.exe" + } + }, + { + "type": "cppdbg", // Use "type": "cppdbg" for Windows C/C++ extension + "request": "attach", + "name": "longboy-server-cli by PID", + "program": "${workspaceFolder}/target/debug/longboy-server-cli", + "processId": "${command:pickProcess}", // Provides a filterable list of running processes + "osx": { + "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "program": "${workspaceFolder}/target/debug/longboy-server-cli.exe" + } + }, + { + "type": "cppdbg", // Use "type": "cppdbg" for Windows C/C++ extension + "request": "attach", + "name": "longboy-client-cli by PID", + "program": "${workspaceFolder}/target/debug/longboy-client-cli", + "processId": "${command:pickProcess}", // Provides a filterable list of running processes + "osx": { + "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "program": "${workspaceFolder}/target/debug/longboy-client-cli.exe" + } + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c648f1d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2449 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + +[[package]] +name = "cipher" +version = "0.5.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71c893d5a1e8257048dbb29954d2e1f85f091a150304f1defe4ca2806da5d3f" +dependencies = [ + "crypto-common 0.2.0-rc.10", + "inout", + "zeroize", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "config" +version = "0.15.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde-untagged", + "serde_core", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fc0003068acd7e9cb6659fd956dc4d671f102a06cc115990b9e7bb5745c25" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common 0.1.7", +] + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "typenum", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "longboy" +version = "1.0.0" +dependencies = [ + "anyhow", + "cipher", + "derive_builder", + "enum-map", + "flume 0.11.1", + "fnv", + "parking_lot", + "quinn", + "rc5", + "rcgen", + "thunderdome", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "longboy-client-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "crossterm", + "flume 0.12.0", + "longboy", + "longboy_schema", + "quinn", + "rand 0.9.2", + "rustls", + "rustls-native-certs", + "serde", + "tokio", + "tokio-util", +] + +[[package]] +name = "longboy-server-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "enum-map", + "flume 0.12.0", + "longboy", + "longboy_schema", + "quinn", + "rand 0.10.0", + "rand-unique", + "rustls", + "serde", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "longboy_schema" +version = "1.0.0" +dependencies = [ + "longboy", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + +[[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 = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "fastbloom", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand-unique" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f45448a2f51627504e8592dc11fe0e46828de85a29b412671e726843d738fe1" +dependencies = [ + "num-traits", + "rand 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rc5" +version = "0.1.0-pre" +source = "git+https://github.com/RustCrypto/block-ciphers.git?rev=36b34efddc5da2340c4e08e09f590f2200c715ba#36b34efddc5da2340c4e08e09f590f2200c715ba" +dependencies = [ + "cipher", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "thunderdome" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e170f93360bf9ae6fe3c31116bbf27adb1d054cedd6bc3d7857e34f2d98d0b" + +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/crates/longboy-client-cli/Cargo.toml b/crates/longboy-client-cli/Cargo.toml new file mode 100644 index 0000000..3d59f2e --- /dev/null +++ b/crates/longboy-client-cli/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "longboy-client-cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +config = "0.15.19" +crossterm = "0.29.0" +flume = "0.12.0" +longboy = { version = "1.0.0", path = "../longboy" } +longboy_schema = { version = "1.0.0", path = "../longboy-schema" } +quinn = "0.11.9" +rand = "0.9.2" +rustls = { version = "0.23.35", features = ["aws-lc-rs"] } +rustls-native-certs = "0.8.2" +serde = { version = "1.0.228", features = ["derive"] } +tokio = "1.48.0" +tokio-util = "0.7.17" diff --git a/crates/longboy-client-cli/src/main.rs b/crates/longboy-client-cli/src/main.rs new file mode 100644 index 0000000..707e6cc --- /dev/null +++ b/crates/longboy-client-cli/src/main.rs @@ -0,0 +1,215 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] +#![feature(generic_const_items)] +#![feature(unboxed_closures)] + +use std::{net::SocketAddr, sync::Arc, time::Duration}; + +use anyhow::{Context, Result}; +use config::Config; +use longboy::{Client, ClientSession, Sink, Source}; +use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; +use quinn::{ClientConfig, Endpoint}; +use rustls::{ + crypto::{CryptoProvider, aws_lc_rs}, + pki_types::{CertificateDer, pem::PemObject}, +}; +use rustls_native_certs::load_native_certs; +use tokio::time::sleep; +use tokio_util::sync::CancellationToken; + +#[derive(serde::Deserialize)] +struct LongboyClientConfig +{ + certificate_trust_store: Option, + server_address: SocketAddr, + server_name: String, +} + +struct ServerToClientSink +{ + channel: flume::Sender<(u64, u64, u64, u8)>, +} + +struct ClientToServerSource +{ + channel: flume::Receiver<(u64, u64, u64)>, +} + +impl Source<32> for ClientToServerSource +{ + fn poll(&mut self, buffer: &mut [u8; 32]) -> bool + { + let msg = self.channel.recv(); + match msg + { + Ok((input0, input1, input2)) => + { + print!("Sending {}, {}, {}", input0, input1, input2); + *(<&mut [u8; 8]>::try_from(&mut buffer[0..8]).unwrap()) = input0.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[8..16]).unwrap()) = input1.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[16..24]).unwrap()) = input2.to_le_bytes(); + true + } + _ => false, + } + } +} + +impl Sink<32> for ServerToClientSink +{ + fn handle(&mut self, buffer: &[u8; 32]) + { + let input0 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[0..8]).unwrap())); + let input1 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[8..16]).unwrap())); + let input2 = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[16..24]).unwrap())); + let player_id = u8::from_le_bytes(*(<&[u8; 1]>::try_from(&buffer[24..25]).unwrap())); + self.channel.send((input0, input1, input2, player_id)).unwrap(); + } +} + +fn main() +{ + let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); + // Setup the configuration builder for the server. Let environment variables take the highest precedence. + let settings = Config::builder() + .add_source(config::File::with_name(&format!("{}/longboy", base_config_dir)).required(false)) + .add_source(config::Environment::with_prefix("LONGBOY_CLIENT")) + .build() + .unwrap(); + let config: LongboyClientConfig = settings + .try_deserialize::() + .expect("Failed to deserialize configuration"); + if let Err(err) = run_client_from_config(config) + { + eprintln!("Error running longboy client: {err:#}"); + } +} + +#[tokio::main] +async fn run_client_from_config(config: LongboyClientConfig) -> anyhow::Result<()> +{ + // load rustls default crypto provider with aws-lc-rs backend + let provider = aws_lc_rs::default_provider(); + CryptoProvider::install_default(provider).expect("Failed to install default crypto provider"); + + // load the default trust store if provided + let mut root_store = rustls::RootCertStore::empty(); + for cert in load_native_certs().certs + { + root_store + .add(cert) + .context("failed to add native certificate to root store")?; + } + + if let Some(trust_store_path) = config.certificate_trust_store + { + let cert_path = std::path::Path::new(&trust_store_path); + let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") + { + vec![CertificateDer::from( + std::fs::read(cert_path).context("failed to read certificate chain file")?, + )] + } + else + { + CertificateDer::pem_file_iter(cert_path) + .context("failed to read PEM from certificate chain file")? + .collect::>() + .context("invalid PEM-encoded certificate")? + }; + for cert in cert_chain + { + root_store + .add(cert) + .context("failed to add certificate to root store")?; + } + } + + // Create client endpoint with the given server address and tls configuration + let client_config = ClientConfig::with_root_certificates(Arc::new(root_store))?; + let socket = SocketAddr::from(([0, 0, 0, 0], 0)); + let client_endpoint = Endpoint::client(socket).unwrap(); + + // Connect to the server + let connection = client_endpoint + .connect_with(client_config, config.server_address, config.server_name.as_str()) + .context("failed to connect to server")? + .await + .context("failed to establish connection to server")?; + + println!("Connected to server at {}", config.server_address); + + // Create longboy client sessions + let client_session = ClientSession::new(connection).await?; + + // Bind the cancellation token to sigterm handler + let cancellation_token = CancellationToken::new(); + + // Session established, create the longboy client using tokyo runtime. + let runtime = longboy::TokioRuntime::new(cancellation_token.child_token()); + let client_to_server_schema = new_client_to_server_schema(); + let server_to_client_schema = new_server_to_client_schema(); + let receiver_channel = flume::unbounded(); + let sender_channel = flume::unbounded(); + let _longboy_client = Client::builder(client_session, Box::new(runtime)) + .receiver::<_, 32, 3>( + &server_to_client_schema, + ServerToClientSink { + channel: receiver_channel.0, + }, + )? + .sender::<_, 32, 3>( + &client_to_server_schema, + ClientToServerSource { + channel: sender_channel.1, + }, + )? + .build(); + + // Client was created, for now just log and exit. + println!("Longboy client session established."); + + // dump server events + tokio::spawn(async move { + let recv = receiver_channel.1.clone(); + loop + { + let incoming = recv.recv_async().await; + + match incoming + { + Ok((input0, input1, input2, player_id)) => + { + println!( + "Recv'd Input0({}) Input1({}) Input2({}) PlayerID({})", + input0, input1, input2, player_id + ); + } + Err(e) => + { + println!("{}", e); + break; + } + } + } + }); + + loop + { + if cancellation_token.is_cancelled() + { + break; + } + // send keys + sleep(Duration::from_millis(5000)).await; + // random inputs for testing + let input0 = rand::random::(); + let input1 = rand::random::(); + let input2 = rand::random::(); + println!("Sending Input0({}) Input1({}) Input2({})", input0, input1, input2); + sender_channel.0.send((input0, input1, input2)).unwrap(); + } + + Ok(()) +} diff --git a/crates/longboy-schema/Cargo.toml b/crates/longboy-schema/Cargo.toml new file mode 100644 index 0000000..43a100e --- /dev/null +++ b/crates/longboy-schema/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "longboy_schema" +version = "1.0.0" +edition = "2024" + +[dependencies] +longboy = { version = "1.0.0", path = "../longboy" } \ No newline at end of file diff --git a/crates/longboy-schema/src/lib.rs b/crates/longboy-schema/src/lib.rs new file mode 100644 index 0000000..861eb6b --- /dev/null +++ b/crates/longboy-schema/src/lib.rs @@ -0,0 +1,20 @@ +use longboy::{ClientToServerSchema, ServerToClientSchema}; + +pub fn new_client_to_server_schema() -> ClientToServerSchema +{ + ClientToServerSchema { + name: "Input", + mapper_port: 8081, + heartbeat_period: 100, + port: 8082, + } +} + +pub fn new_server_to_client_schema() -> ServerToClientSchema +{ + ServerToClientSchema { + name: "State", + mapper_port: 8080, + heartbeat_period: 100, + } +} diff --git a/crates/longboy-server-cli/Cargo.toml b/crates/longboy-server-cli/Cargo.toml new file mode 100644 index 0000000..97f8f65 --- /dev/null +++ b/crates/longboy-server-cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "longboy-server-cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +config = "0.15.19" +enum-map = "2.7.3" +flume = "0.12.0" +longboy = { version = "1.0.0", path = "../longboy" } +longboy_schema = { version = "1.0.0", path = "../longboy-schema" } +quinn = "0.11.9" +rand = "0.10.0" +rand-unique = "0.3.0" +rustls = { version = "0.23.35", features = ["aws-lc-rs"] } +serde = { version = "1.0.228", features = ["derive"] } +tokio = "1.48.0" +tokio-util = "0.7.17" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" diff --git a/crates/longboy-server-cli/src/README.md b/crates/longboy-server-cli/src/README.md new file mode 100644 index 0000000..18540bd --- /dev/null +++ b/crates/longboy-server-cli/src/README.md @@ -0,0 +1,16 @@ + +# Longboy Server CLI Architecture + +## Broker Module Overview + +```mermaid +graph TD + A[SessionBroker] -->|Manages| B[ClientBroker] + A -->|Manages| C[ServerBroker] + B -->|Sends| D[ClientToServerSink] + C -->|Receives| E[ServerToClientSource] + A -->|Tracks| F[PlayerSessions] + A -->|Tracks| G[PlayerIndexFreeList] +``` + +This diagram illustrates how the broker module manages client connections, routes messages through a queue system, and maintains client state through a registry. diff --git a/crates/longboy-server-cli/src/broker.rs b/crates/longboy-server-cli/src/broker.rs new file mode 100644 index 0000000..cd9f9bf --- /dev/null +++ b/crates/longboy-server-cli/src/broker.rs @@ -0,0 +1,175 @@ +use anyhow::Result; +use flume::{Receiver, Sender}; +use longboy::{Factory, Sink, Source}; +use std::{ + collections::HashMap, + sync::{ + Arc, RwLock, + atomic::{AtomicU64, Ordering::SeqCst}, + }, +}; +use tracing::info; + +pub struct SessionBroker +{ + receiver_channels: [Receiver<(u64, u64, u64, u8)>; MAX_PLAYERS], + broadcast_channel: Sender<(u64, u64, u64, u8)>, + // thread safe atomic always incrementing session id + next_session_id: AtomicU64, + // key is the session id, value is the player index + player_sessions: Arc>>, + // thread safe atomic free pointer to the next player index + player_index_free_list: Arc>>, +} + +impl SessionBroker +{ + pub fn new() -> Self + { + let pipe = flume::unbounded(); + let receiver_channels: [Receiver<(u64, u64, u64, u8)>; MAX_PLAYERS] = [(); MAX_PLAYERS].map(|_| { + let (_s, r) = pipe.clone(); + r + }); + + let broadcast_channel = pipe.0; + + Self { + receiver_channels, + broadcast_channel, + next_session_id: AtomicU64::new(1), + player_sessions: Arc::new(RwLock::new(HashMap::new())), + player_index_free_list: Arc::new(RwLock::new((0..MAX_PLAYERS).map(|_| true).collect())), + } + } + + pub fn allocate_session_id(&self, player_index: usize) -> u64 + { + let session_id = self.next_session_id.fetch_add(1, SeqCst); + let insert = self.player_sessions.write().unwrap().insert(session_id, player_index); + match insert + { + Some(old_player_index) => info!( + "Warning: Overwriting existing session {} for player index {} with {}", + old_player_index, player_index, session_id + ), + None => info!("Allocated session id {} for player index {}", session_id, player_index), + } + session_id + } + + pub fn next_player_index(&self) -> Result + { + let mut free_list = self.player_index_free_list.write().unwrap(); + let player_index = free_list.iter().position(|&is_free| is_free); + match player_index + { + Some(index) => + { + free_list[index] = false; + Ok(index) + } + None => anyhow::bail!("Maximum number of players reached"), + } + } +} + +pub struct ClientToServerSink +{ + player_index: u8, + channel: Sender<(u64, u64, u64, u8)>, +} + +pub struct ServerToClientSource +{ + channel: Receiver<(u64, u64, u64, u8)>, +} + +pub struct ServerBroker +{ + inner: Arc>, +} + +impl ServerBroker +{ + pub fn new(session_broker: Arc>) -> Self + { + Self { inner: session_broker } + } +} + +impl Factory for ServerBroker +{ + type Type = ServerToClientSource; + + fn invoke(&mut self, session_id: u64) -> Self::Type + { + let binding = self.inner.player_sessions.read().unwrap(); + let player_index = binding.get(&session_id).expect("Unknown Session ID"); + + ServerToClientSource { + channel: self.inner.receiver_channels[*player_index].clone(), + } + } +} + +pub struct ClientBroker +{ + inner: Arc>, +} + +impl ClientBroker +{ + pub fn new(session_broker: Arc>) -> Self + { + Self { inner: session_broker } + } +} + +impl Factory for ClientBroker +{ + type Type = ClientToServerSink; + + fn invoke(&mut self, session_id: u64) -> Self::Type + { + let binding = self.inner.player_sessions.read().unwrap(); + let player_index = binding.get(&session_id).expect("Unknown Session ID"); + + ClientToServerSink { + player_index: *player_index as u8, + channel: self.inner.broadcast_channel.clone(), + } + } +} + +impl Sink<28> for ClientToServerSink +{ + #[tracing::instrument(skip(self))] + fn handle(&mut self, buffer: &[u8; 28]) + { + let a = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[0..8]).unwrap())); + let b = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[8..16]).unwrap())); + let c = u64::from_le_bytes(*(<&[u8; 8]>::try_from(&buffer[16..24]).unwrap())); + self.channel.send((a, b, c, self.player_index)).unwrap(); + } +} + +impl Source<28> for ServerToClientSource +{ + #[tracing::instrument(skip(self))] + fn poll(&mut self, buffer: &mut [u8; 28]) -> bool + { + match self.channel.try_recv() + { + Ok((a, b, c, player_index)) => + { + *(<&mut [u8; 8]>::try_from(&mut buffer[0..8]).unwrap()) = a.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[8..16]).unwrap()) = b.to_le_bytes(); + *(<&mut [u8; 8]>::try_from(&mut buffer[16..24]).unwrap()) = c.to_le_bytes(); + *(<&mut [u8; 1]>::try_from(&mut buffer[24..25]).unwrap()) = player_index.to_le_bytes(); + true + } + Err(_) => false, + } + } +} diff --git a/crates/longboy-server-cli/src/main.rs b/crates/longboy-server-cli/src/main.rs new file mode 100644 index 0000000..38ae5e8 --- /dev/null +++ b/crates/longboy-server-cli/src/main.rs @@ -0,0 +1,233 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] +#![feature(generic_const_items)] + +mod broker; + +use anyhow::{Context, Result}; +use config::Config; +use longboy::{Server, ServerSession, ThreadRuntime, TokioRuntime}; +use longboy_schema::{new_client_to_server_schema, new_server_to_client_schema}; +use quinn::{Connection, Endpoint, crypto::rustls::QuicServerConfig}; +use rand_unique::RandomSequence; +use rustls::{ + crypto::{CryptoProvider, aws_lc_rs}, + pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, +}; +use std::{ + net::SocketAddr, + sync::{Arc, Mutex}, +}; +use tokio_util::sync::CancellationToken; +use tracing::{error, info, warn}; +use tracing_subscriber::fmt::format::FmtSpan; + +use crate::broker::{ClientBroker, ServerBroker, SessionBroker}; + +// This really could be a feature in the toml configuration crate. +#[derive(serde::Deserialize)] +enum LongboyRuntimeType +{ + Tokio, + Thread, +} + +#[derive(serde::Deserialize)] +struct LongboyServerConfig +{ + // Define configuration fields here + // e.g., session_capacity: usize, + // runtime: enum of tokio or async-std, + session_capacity: usize, + runtime_type: LongboyRuntimeType, + public_certificate_path: String, + private_key_path: String, + listen_address: Option, +} + +/// Entry point of the application. +/// +/// This function collects configuration settings and starts the server. +fn main() +{ + //Setup a simple tracer subscriber for logging. + tracing_subscriber::fmt() + .with_span_events(FmtSpan::FULL) + .with_max_level(tracing::Level::TRACE) + .init(); + + let base_config_dir = std::env::var("LONGBOY_CONFIG_DIR").unwrap_or_else(|_| ".".to_string()); + // Setup the configuration builder for the server. Let environment variables take the highest precedence. + let settings = Config::builder() + .add_source(config::File::with_name(&format!("{}/longboy", base_config_dir)).required(false)) + .add_source(config::Environment::with_prefix("LONGBOY_SERVER")) + .build() + .unwrap(); + + let config: LongboyServerConfig = settings + .try_deserialize::() + .expect("Failed to deserialize configuration"); + + // Create a cancellation token for the runtime. + let cancellation_token = CancellationToken::new(); + let _ = run_server_from_config(config, cancellation_token); +} + +// Runs the server based on the provided configuration. It uses the tokio runtime by default. +#[tokio::main] +async fn run_server_from_config(config: LongboyServerConfig, cancellation_token: CancellationToken) -> Result<()> +{ + let server_runtime = match config.runtime_type + { + LongboyRuntimeType::Tokio => + { + Box::new(TokioRuntime::new(cancellation_token.clone())) as Box + } + LongboyRuntimeType::Thread => + { + Box::new(ThreadRuntime::new(cancellation_token.clone())) as Box + } + }; + + let broker = std::sync::Arc::new(SessionBroker::<32>::new()); + let client_broker = ClientBroker::new(broker.clone()); + let server_broker = ServerBroker::new(broker.clone()); + + // Setup the receivers for client-server communication. + let server_to_client_schema = new_server_to_client_schema(); + let client_to_server_schema = new_client_to_server_schema(); + + let server_builder = Server::builder(config.session_capacity, server_runtime) + .sender::<_, 28, 1>(&server_to_client_schema, server_broker) + .unwrap() + .receiver::<_, 28, 1>(&client_to_server_schema, client_broker) + .unwrap(); + + // Load TLS certificates. TLS is required. + let provider = aws_lc_rs::default_provider(); + CryptoProvider::install_default(provider).expect("Failed to install default crypto provider"); + + let (cert_chain, key) = { + let cert_path = std::path::Path::new(&config.public_certificate_path); + let key_path = std::path::Path::new(&config.private_key_path); + let key = if key_path.extension().is_some_and(|x| x == "der") + { + PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( + std::fs::read(key_path) + .context("failed to read private key file") + .unwrap(), + )) + } + else + { + PrivateKeyDer::from_pem_file(key_path) + .context("failed to read PEM from private key file") + .unwrap() + }; + + let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") + { + vec![CertificateDer::from( + std::fs::read(cert_path) + .context("failed to read certificate chain file") + .unwrap(), + )] + } + else + { + CertificateDer::pem_file_iter(cert_path) + .context("failed to read PEM from certificate chain file")? + .collect::>() + .context("invalid PEM-encoded certificate") + .unwrap() + }; + + (cert_chain, key) + }; + + // setup listen address + let listen_addr = config.listen_address.unwrap_or_else(|| "[::1]:4433".parse().unwrap()); + + // Now use Quinn (HTTP/3 (QUIP)) to start a listening endpoint. + let mut server_crypto = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key) + .context("failed to build server crypto configuration")?; + server_crypto.alpn_protocols = vec![b"longboyquic".to_vec()]; + let server_config = quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(server_crypto)?)); + let server_endpoint = Endpoint::server(server_config, listen_addr).context("failed to build endpoint")?; + + // create a new logical server instance + let mut server_instance = server_builder.build(); + + // weak key generator for session encryption. This is not ideal but it is important that the server can generate keys for sessions without blocking on a mutex or something. We can replace this with a more robust solution later if needed. + let keygenerator = Arc::new(Mutex::new(RandomSequence::::rand( + &mut rand::rngs::ThreadRng::default(), + ))); + + // log out some info on server startup + info!("Longboy server listening on {}", listen_addr); + // print stats from server endpoint + info!("Server endpoint stats: {:?}", server_endpoint.stats()); + + // start accepting connections + while let Some(conn) = server_endpoint.accept().await + { + // print it + info!("Incoming connection: {:?}", conn.remote_address()); + + // try to accept and handle errors gracefully + match conn.accept() + { + Err(e) => + { + error!("Failed to accept connection: {:?}", e); + continue; + } + Ok(connecting) => + { + match connecting.await + { + Err(e) => + { + warn!("Failed to establish connection: {:?}", e); + continue; + } + Ok(connection) => + { + // handle the connection + if let Err(e) = + server_handle_connection(connection, &mut server_instance, &broker, &keygenerator).await + { + warn!("Error handling connection: {:?}", e); + } + } + } + } + } + } + + Ok(()) +} + +async fn server_handle_connection( + conn: Connection, + server: &mut Server, + broker: &std::sync::Arc>, + keygenerator: &std::sync::Arc>>, +) -> Result<()> +{ + let remote_address = conn.remote_address(); + info!("New connection from {}", remote_address); + + let player_index = broker.next_player_index()?; + let session_id = broker.allocate_session_id(player_index); + let cipher_key = keygenerator + .lock() + .unwrap() + .next() + .expect("key generator should never exhaust"); + let server_session = ServerSession::new(session_id, cipher_key, conn).await?; + server.register(server_session); // Perhaps this should handle errors in some way? Client ditches mid stream? + Ok(()) +} diff --git a/crates/longboy/Cargo.toml b/crates/longboy/Cargo.toml index cd14b02..9ccd050 100644 --- a/crates/longboy/Cargo.toml +++ b/crates/longboy/Cargo.toml @@ -23,6 +23,7 @@ rc5 = { git = "https://github.com/RustCrypto/block-ciphers.git", rev = "36b34efd thunderdome = "0.6.1" tokio = { version = "1.48", features = ["full"] } tokio-util = "0.7.11" +tracing = "0.1.44" [dev-dependencies] parking_lot = "0.12.3" diff --git a/crates/longboy/src/client/client_session.rs b/crates/longboy/src/client/client_session.rs index 92af6ce..e4c29df 100644 --- a/crates/longboy/src/client/client_session.rs +++ b/crates/longboy/src/client/client_session.rs @@ -20,6 +20,9 @@ impl ClientSession let cipher_key = receive.read_u64_le().await?; receive.stop(Default::default())?; + // debug print the session id and cipher key + println!("Received session id: {}, cipher key: {}", session_id, cipher_key); + Ok(Self { connection, session_id, diff --git a/crates/longboy/src/client/client_to_server_sender.rs b/crates/longboy/src/client/client_to_server_sender.rs index 00f2f72..13f8961 100644 --- a/crates/longboy/src/client/client_to_server_sender.rs +++ b/crates/longboy/src/client/client_to_server_sender.rs @@ -86,6 +86,8 @@ where for (mirroring, socket) in self.sockets.iter() { buffer[8] = Mirroring::into_usize(mirroring) as u8; + // debug sent buffer + print!("Sending heartbeat buffer: {:?}", buffer); socket .send_to(&buffer, self.mapper_socket_addr) .expect("send_to failure"); @@ -97,8 +99,10 @@ where // Poll Session if let Some(datagram) = self.sender.poll_datagram(timestamp) { + print!("Sending datagram: {:?}", datagram); for socket in self.sockets.values() { + // debug sent datagram socket.send_to(datagram, self.socket_addr).expect("send_to failure"); } } diff --git a/crates/longboy/src/lib.rs b/crates/longboy/src/lib.rs index 4df8094..82e618f 100644 --- a/crates/longboy/src/lib.rs +++ b/crates/longboy/src/lib.rs @@ -2,7 +2,6 @@ #![feature(generic_const_exprs)] #![feature(generic_const_items)] #![feature(map_try_insert)] -#![feature(try_blocks)] // API mod client; diff --git a/crates/longboy/src/proto/receiver.rs b/crates/longboy/src/proto/receiver.rs index c56876f..b8822dd 100644 --- a/crates/longboy/src/proto/receiver.rs +++ b/crates/longboy/src/proto/receiver.rs @@ -42,6 +42,7 @@ where self.cycle } + #[tracing::instrument(skip(self))] pub fn handle_datagram( &mut self, timestamp: u16, @@ -54,6 +55,12 @@ where #[allow(non_snake_case)] let MAX_BUFFERED: usize = Constants::::MAX_BUFFERED; + tracing::trace!( + "Handling datagram with timestamp {timestamp} at local cycle {}", + self.cycle + ); + tracing::trace!("Flags: {:?}", self.flags); + // Grab cycle and timestamp. self.cipher .decrypt_header(<&mut [u8; 4]>::try_from(&mut datagram[0..4]).unwrap()); @@ -62,7 +69,17 @@ where // Calculate diff for cycle and timestamp. let cycle_diff = ((datagram_cycle + MAX_CYCLE) - self.cycle) % MAX_CYCLE; - let timestamp_diff = ((datagram_timestamp + u16::MAX) - timestamp) % u16::MAX; + let timestamp_diff = timestamp.wrapping_sub(datagram_timestamp); + + // trace cycle and timestamp info. + tracing::trace!( + datagram_cycle, + datagram_timestamp, + cycle_diff, + timestamp_diff, + local_cycle = self.cycle, + local_timestamp = timestamp + ); // Check for bad datagrams or late datagrams that are already processed. Because // we ensure only a positive diff, this is done by checking for any values greater @@ -70,6 +87,7 @@ where if cycle_diff > 256 || timestamp_diff > 2048 { // Bad datagram or already received. + tracing::warn!("Dropping datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff}"); return; } @@ -78,10 +96,14 @@ where if cycle_diff > std::cmp::min(8, WINDOW_SIZE + 1) { // soft warning + tracing::warn!("Late datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff}"); } if cycle_diff > MAX_BUFFERED { // hard warning + tracing::error!( + "Datagram with cycle diff {cycle_diff} and timestamp diff {timestamp_diff} is too far ahead" + ); for _ in 0..(cycle_diff - MAX_BUFFERED) { @@ -101,6 +123,11 @@ where // already adanced the local cycle to catch up, if applicable. if ((cycle_i + MAX_CYCLE) - self.cycle) % MAX_CYCLE > MAX_BUFFERED { + tracing::trace!( + "Stopping sink input at cycle {} because it's too far ahead of local cycle {}", + cycle_i, + self.cycle + ); break; } @@ -125,13 +152,16 @@ where // Advance cycles. loop { + tracing::trace!("Advancing cycle from {}", self.cycle); let index = self.cycle % MAX_BUFFERED; if !self.flags[index] { + tracing::trace!("Stopping cycle advance at cycle {} because flag is not set", self.cycle); break; } self.flags[index] = false; self.cycle = (self.cycle + 1) % MAX_CYCLE; + tracing::trace!("Advanced cycle to {}", self.cycle); } } } diff --git a/crates/longboy/src/server/client_to_server_receiver.rs b/crates/longboy/src/server/client_to_server_receiver.rs index 06147e6..7c2824f 100644 --- a/crates/longboy/src/server/client_to_server_receiver.rs +++ b/crates/longboy/src/server/client_to_server_receiver.rs @@ -5,6 +5,7 @@ use enum_map::{Enum, EnumMap}; use flume::Receiver as FlumeReceiver; use fnv::FnvHashMap; use thunderdome::{Arena, Index}; +use tracing::{info, warn}; use crate::{Constants, Factory, Mirroring, Receiver, RuntimeTask, ServerSessionEvent, Sink}; @@ -37,6 +38,33 @@ where receiver: Receiver, } +impl std::fmt::Debug + for ClientToServerReceiver +where + SinkFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, + [(); >::MAX_BUFFERED]:, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.debug_struct("ClientToServerReceiver") + .field("name", &self.name) + .field("mapper_socket", &self.mapper_socket) + .field("socket", &self.socket) + .field("session_receiver", &"FlumeReceiver") + .field( + "sessions", + &format!( + "Arena>", + "SinkFactoryType::Type", SIZE, WINDOW_SIZE + ), + ) + .field("session_id_to_session_map", &self.session_id_to_session_map) + .field("socket_addr_to_session_map", &self.socket_addr_to_session_map) + .finish() + } +} + impl ClientToServerReceiver where @@ -74,26 +102,9 @@ where sink_factory, }) } -} - -impl RuntimeTask - for ClientToServerReceiver -where - SinkFactoryType: Factory>, - [(); >::DATAGRAM_SIZE]:, - [(); >::MAX_BUFFERED]:, -{ - fn name(&self) -> &str - { - &self.name - } - fn poll(&mut self, timestamp: u16) + fn poll_server_session_events(&mut self, _timestamp: u16) { - // Alias constants so they're less painful to read. - #[allow(non_snake_case)] - let DATAGRAM_SIZE: usize = Constants::::DATAGRAM_SIZE; - // Handle Session changes. for event in self.session_receiver.try_iter() { @@ -123,19 +134,32 @@ where } } } + } + fn poll_for_client_socket_addresses(&mut self) + { // Update Client socket addresses. let mut buffer = [0; 64]; + // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) { - if len != std::mem::size_of::() + std::mem::size_of::() + if len < std::mem::size_of::() + std::mem::size_of::() + // disqualify not enough data? { + warn!( + "Received mapping of invalid length {} from socket_addr {:?}", + len, socket_addr + ); continue; } let session_id = u64::from_le_bytes(*<&[u8; 8]>::try_from(&buffer[0..8]).unwrap()); - + info!( + "Received mapping from session_id {} to socket_addr {:?}", + session_id, socket_addr + ); let mirroring = buffer[8] as usize; + if mirroring >= Mirroring::LENGTH { continue; @@ -148,23 +172,46 @@ where if let Some(socket_addr) = session.socket_addrs[mirroring] { + info!( + "Removing old mapping from session_id {} to socket_addr {:?} for mirroring {:?}", + session_id, socket_addr, mirroring + ); self.socket_addr_to_session_map.remove(&socket_addr); } + info!( + "Adding mapping from session_id {} to socket_addr {:?} for mirroring {:?}", + session_id, socket_addr, mirroring + ); session.socket_addrs[mirroring] = Some(socket_addr); self.socket_addr_to_session_map.insert(socket_addr, *index); } } + } + + fn poll_for_client_datagrams(&mut self, timestamp: u16) + { + // Alias constants so they're less painful to read. + #[allow(non_snake_case)] + let DATAGRAM_SIZE: usize = Constants::::DATAGRAM_SIZE; - // Process datagrams. + // TODO: This accepts anything, even those which are not mapped, no session is established, and there is no signed header. let mut buffer = [0; 512]; while let Ok((len, socket_addr)) = self.socket.recv_from(&mut buffer) { if len != DATAGRAM_SIZE { + warn!( + "Received datagram of invalid length {} from socket_addr {:?}", + len, socket_addr + ); continue; } let datagram = (&mut buffer[0..DATAGRAM_SIZE]).try_into().unwrap(); + info!( + "Received datagram from socket_addr {:?} - datagram {:?}", + socket_addr, datagram + ); if let Some(index) = self.socket_addr_to_session_map.get(&socket_addr) { @@ -177,3 +224,23 @@ where } } } + +impl RuntimeTask + for ClientToServerReceiver +where + SinkFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, + [(); >::MAX_BUFFERED]:, +{ + fn name(&self) -> &str + { + &self.name + } + + fn poll(&mut self, timestamp: u16) + { + self.poll_server_session_events(timestamp); + self.poll_for_client_socket_addresses(); + self.poll_for_client_datagrams(timestamp); + } +} diff --git a/crates/longboy/src/server/server_to_client_sender.rs b/crates/longboy/src/server/server_to_client_sender.rs index 4c4cd7c..690233d 100644 --- a/crates/longboy/src/server/server_to_client_sender.rs +++ b/crates/longboy/src/server/server_to_client_sender.rs @@ -5,6 +5,7 @@ use enum_map::EnumMap; use flume::Receiver as FlumeReceiver; use fnv::FnvHashMap; use thunderdome::{Arena, Index}; +use tracing::trace; use crate::{Constants, Factory, Mirroring, RuntimeTask, Sender, ServerSessionEvent, Source, UdpSocketExt}; @@ -34,6 +35,31 @@ where sender: Sender, } +impl std::fmt::Debug + for ServerToClientSender +where + SourceFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.debug_struct("ServerToClientSender") + .field("name", &self.name) + .field("mapper_socket", &self.mapper_socket) + .field("sockets", &self.sockets) + .field("session_receiver", &"FlumeReceiver") + .field( + "sessions", + &format!( + "Arena>", + "SourceFactoryType::Type", SIZE, WINDOW_SIZE + ), + ) + .field("session_id_to_session_map", &self.session_id_to_session_map) + .finish() + } +} + impl ServerToClientSender where @@ -73,22 +99,9 @@ where source_factory, }) } -} - -impl RuntimeTask - for ServerToClientSender -where - SourceFactoryType: Factory>, - [(); >::DATAGRAM_SIZE]:, -{ - fn name(&self) -> &str - { - &self.name - } - fn poll(&mut self, timestamp: u16) + fn poll_server_session_events(&mut self, _timestamp: u16) { - // Handle Session changes. for event in self.session_receiver.try_iter() { match event @@ -113,7 +126,10 @@ where } } } + } + fn poll_for_client_socket_addresses(&mut self) + { // Update Client socket addresses. let mut buffer = [0; 64]; while let Ok((len, socket_addr)) = self.mapper_socket.recv_from(&mut buffer) @@ -127,10 +143,17 @@ where if let Some(index) = self.session_id_to_session_map.get(&session_id) { + trace!( + "Received mapping from session {} to socket address {}", + session_id, socket_addr + ); self.sessions[*index].socket_addr = Some(socket_addr); } } + } + fn poll_sessions(&mut self, timestamp: u16) + { // Poll Sessions for (_, session) in self.sessions.iter_mut() { @@ -139,9 +162,32 @@ where { for socket in self.sockets.values() { + trace!( + "Sending datagram to socket address {} via socket {:?}", + socket_addr, socket + ); socket.send_to(datagram, socket_addr).expect("send_to failure"); } } } } } + +impl RuntimeTask + for ServerToClientSender +where + SourceFactoryType: Factory>, + [(); >::DATAGRAM_SIZE]:, +{ + fn name(&self) -> &str + { + &self.name + } + + fn poll(&mut self, timestamp: u16) + { + self.poll_server_session_events(timestamp); + self.poll_for_client_socket_addresses(); + self.poll_sessions(timestamp); + } +} diff --git a/docker/.docker-compose.yml b/docker/.docker-compose.yml new file mode 100644 index 0000000..8c8fed5 --- /dev/null +++ b/docker/.docker-compose.yml @@ -0,0 +1,41 @@ +services: + server: + container_name: longboy_server + build: + context: .. + dockerfile: docker/server.dockerfile + environment: + - LONGBOY_SERVER_RUNTIME_TYPE=Tokio + - LONGBOY_SERVER_SESSION_CAPACITY=1000 + - LONGBOY_SERVER_PUBLIC_CERTIFICATE_PATH=./localhost.crt + - LONGBOY_SERVER_PRIVATE_KEY_PATH=./localhost.key + - LONGBOY_SERVER_LISTEN_ADDRESS=0.0.0.0:4433 + networks: + longboy-network: + ipv4_address: 192.168.1.10 + + client: + build: + context: .. + dockerfile: docker/client.dockerfile + environment: + - LONGBOY_CLIENT_CERTIFICATE_TRUST_STORE=./rootCA.pem + - LONGBOY_CLIENT_SERVER_ADDRESS=192.168.1.10:4433 + - LONGBOY_CLIENT_SERVER_NAME=localhost + networks: + - longboy-network + depends_on: + - server + stdin_open: true + tty: true + command: /bin/bash + deploy: + replicas: 16 + +networks: + longboy-network: + driver: bridge + ipam: + config: + - subnet: "192.168.1.0/24" + gateway: "192.168.1.1" \ No newline at end of file diff --git a/docker/client.dockerfile b/docker/client.dockerfile new file mode 100644 index 0000000..e8f12a8 --- /dev/null +++ b/docker/client.dockerfile @@ -0,0 +1,25 @@ +FROM rust:latest AS base + +FROM base AS sources + +WORKDIR /tmp/app + +COPY ./crates ./crates +COPY .rustfmt.toml . +COPY Cargo.toml . +COPY rust-toolchain.toml . +COPY crates/longboy-server-cli/test/data/ ./certs + +FROM sources AS build + +WORKDIR /tmp/app +RUN cargo build + +FROM rust:latest AS release + +WORKDIR /tmp/app + +COPY --from=sources /tmp/app/certs . +COPY --from=build /tmp/app/target/debug/longboy-client* . + +ENTRYPOINT ["/tmp/app/longboy-client-cli"] \ No newline at end of file diff --git a/docker/server.dockerfile b/docker/server.dockerfile new file mode 100644 index 0000000..c1eec5b --- /dev/null +++ b/docker/server.dockerfile @@ -0,0 +1,25 @@ +FROM rust:latest AS base + +FROM base AS sources + +WORKDIR /tmp/app + +COPY ./crates ./crates +COPY .rustfmt.toml . +COPY Cargo.toml . +COPY rust-toolchain.toml . +COPY crates/longboy-server-cli/test/data/ ./certs + +FROM sources AS build + +WORKDIR /tmp/app +RUN cargo build + +FROM rust:latest AS release + +WORKDIR /tmp/app + +COPY --from=sources /tmp/app/certs . +COPY --from=build /tmp/app/target/debug/longboy-server* . + +ENTRYPOINT ["/tmp/app/longboy-server-cli"] \ No newline at end of file diff --git a/generate-certs.ps1 b/generate-certs.ps1 new file mode 100644 index 0000000..bdd5d1d --- /dev/null +++ b/generate-certs.ps1 @@ -0,0 +1,128 @@ +# PowerShell script to generate root CA, CSR, and signed localhost certificate in PEM format +# Usage: .\generate-certs.ps1 [-OutputPath ] +# If -OutputPath is not specified, certificates will be generated in the script's directory + +param( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$OutputPath = (Split-Path -Parent $MyInvocation.MyCommand.Path) +) + +# Ensure output directory exists +if (-not (Test-Path $OutputPath)) { + New-Item -ItemType Directory -Path $OutputPath | Out-Null +} + +Write-Host "Generating certificates in: $OutputPath" + +# Step 1: Create Root CA Certificate +Write-Host "`n[1/5] Creating Root CA certificate..." +$rootCA = New-SelfSignedCertificate -Type Custom ` + -KeyExportPolicy Exportable ` + -Subject "CN=Root CA,O=Test Organization,C=US" ` + -KeyUsage CertSign, CRLSign ` + -KeyLength 2048 ` + -NotAfter (Get-Date).AddYears(10) ` + -CertStoreLocation "Cert:\CurrentUser\My" + +Write-Host "Root CA Thumbprint: $($rootCA.Thumbprint)" + +# Step 2: Create a server certificate request +Write-Host "`n[2/5] Creating server certificate signing request..." +$serverCert = New-SelfSignedCertificate -Type Custom ` + -KeyExportPolicy Exportable ` + -Subject "CN=localhost,O=Test Organization,C=US" ` + -DnsName localhost, 127.0.0.1, "::1" ` + -KeyUsage DigitalSignature, KeyEncipherment ` + -KeyLength 2048 ` + -NotAfter (Get-Date).AddYears(1) ` + -CertStoreLocation "Cert:\CurrentUser\My" + +Write-Host "Server Cert Thumbprint: $($serverCert.Thumbprint)" + +# Step 3: Sign the server certificate with root CA +Write-Host "`n[3/5] Signing server certificate with Root CA..." +$signedCert = New-SelfSignedCertificate -Type Custom ` + -KeyExportPolicy Exportable ` + -Subject "CN=localhost,O=Test Organization,C=US" ` + -DnsName localhost, 127.0.0.1, "::1" ` + -KeyUsage DigitalSignature, KeyEncipherment ` + -KeyLength 2048 ` + -Signer $rootCA ` + -NotAfter (Get-Date).AddYears(1) ` + -CertStoreLocation "Cert:\CurrentUser\My" + +Write-Host "Signed Cert Thumbprint: $($signedCert.Thumbprint)" + +# Step 4: Export Root CA to PEM format +Write-Host "`n[4/5] Exporting Root CA to PEM format..." +$rootCAPath = Join-Path $OutputPath "rootCA.pem" +$rootCABytes = $rootCA.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) +$rootCABase64 = [Convert]::ToBase64String($rootCABytes, 'InsertLineBreaks') +$rootCAContent = "-----BEGIN CERTIFICATE-----`n$rootCABase64`n-----END CERTIFICATE-----" +# Use UTF8 without BOM for Rust compatibility +$utf8NoBOM = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($rootCAPath, $rootCAContent, $utf8NoBOM) + +Write-Host "Root CA saved to: $rootCAPath" + +# Step 5: Export signed server certificate and private key to PEM format +Write-Host "`n[5/5] Exporting server certificate and private key to PEM format..." + +# Export certificate +$certPath = Join-Path $OutputPath "localhost.crt" +$certBytes = $signedCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) +$certBase64 = [Convert]::ToBase64String($certBytes, 'InsertLineBreaks') +$certContent = "-----BEGIN CERTIFICATE-----`n$certBase64`n-----END CERTIFICATE-----" +# Use UTF8 without BOM for Rust compatibility +$utf8NoBOM = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($certPath, $certContent, $utf8NoBOM) + +Write-Host "Certificate saved to: $certPath" + +# Export private key +$keyPath = Join-Path $OutputPath "localhost.key" + +# Retrieve the certificate with private key from the store +$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) +$certStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) +$certs = $certStore.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $signedCert.Thumbprint, $false) +$certWithKey = $certs[0] +$certStore.Close() + +# Get the RSA private key using the extension method +$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certWithKey) + +# Export as PKCS8 format (required by Rust's PrivateKeyDer::from_pem_file()) +[System.Security.Cryptography.CngKey]$cngKey = $null +$keyObject = $rsaKey +if ($keyObject -is [System.Security.Cryptography.RSACng]) { + # RSACng has a Key property that gives us the CngKey + $cngKey = $keyObject.Key + # Export PKCS8 from CNG key + $keyBytes = $cngKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob) +} else { + # Fallback for other RSA implementations + $keyBytes = $rsaKey.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob) +} + +$keyBase64 = [Convert]::ToBase64String($keyBytes, 'InsertLineBreaks') +$keyContent = "-----BEGIN PRIVATE KEY-----`n$keyBase64`n-----END PRIVATE KEY-----" +# Use UTF8 without BOM for Rust compatibility +$utf8NoBOM = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($keyPath, $keyContent, $utf8NoBOM) + +Write-Host "Private key saved to: $keyPath" + +# Verify files were created +Write-Host "" +Write-Host "[OK] Certificate generation complete!" +Write-Host "Generated files:" +Get-Item -Path @($rootCAPath, $certPath, $keyPath) -ErrorAction SilentlyContinue | ForEach-Object { + $size = $_.Length + Write-Host " - $($_.Name) ($size bytes)" +} + +Write-Host "" +Write-Host "Trust relationship: localhost.crt is signed by rootCA.pem" +Write-Host "A client with rootCA.pem can verify the localhost.crt certificate."