diff --git a/Cargo.lock b/Cargo.lock index 8886007a..e37cf9cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,11 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -25,24 +19,23 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", ] [[package]] -name = "ahash" -version = "0.8.3" +name = "aho-corasick" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ - "cfg-if", - "once_cell", - "version_check", + "memchr", ] [[package]] @@ -53,9 +46,9 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "allocator-api2" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android-tzdata" @@ -74,9 +67,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arc-swap" @@ -86,12 +79,13 @@ checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "argon2" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" dependencies = [ "base64ct", "blake2", + "cpufeatures", "password-hash", ] @@ -114,25 +108,25 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] name = "atoi" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] @@ -145,9 +139,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", @@ -194,9 +188,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -207,24 +201,11 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "bae" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "base64" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64ct" @@ -240,9 +221,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] [[package]] name = "blake2" @@ -253,36 +237,13 @@ dependencies = [ "digest", ] -[[package]] -name = "blaze-pk" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff945f7246f4046ed4460a0d2a4d4a7648b7cd71318efb9b93a7c742b9ff27" -dependencies = [ - "blaze-pk-derive", - "bytes", - "serde", - "tokio-util", -] - -[[package]] -name = "blaze-pk-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877d4598ebe1dd905c74c51fb9b052e87ae74439b2f7f91ada79e11e4c0030b1" -dependencies = [ - "darling", - "quote", - "syn 2.0.23", -] - [[package]] name = "blaze-ssl-async" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25662e81aaa3ed48b51b19c101c3e36b37285303c55b394624a018adfca6ecaa" dependencies = [ - "rsa", + "rsa 0.8.2", "tokio", "x509-cert", ] @@ -316,9 +277,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -328,9 +292,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "d87d9d13be47a5b7c3907137f1290b0459a7f80efb26be8c52afb11963bccb02" dependencies = [ "android-tzdata", "iana-time-zone", @@ -339,14 +303,14 @@ dependencies = [ "serde", "time", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] name = "const-oid" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "core-foundation-sys" @@ -354,6 +318,30 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32fast" version = "1.3.2" @@ -394,9 +382,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -404,27 +392,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] @@ -436,7 +424,18 @@ dependencies = [ "const-oid", "der_derive", "flagset", - "pem-rfc7468", + "pem-rfc7468 0.6.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -483,9 +482,12 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] [[package]] name = "email_address" @@ -512,25 +514,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0fab0b584e67341bbfedce7c8d59d9cebaa9088fa494338ed4f8be92130bd3" dependencies = [ "quote", - "syn 2.0.23", + "syn 2.0.31", "walkdir", ] [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "flagset" version = "0.4.3" @@ -539,9 +591,9 @@ checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "libz-sys", @@ -618,13 +670,13 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.11.2", + "parking_lot", ] [[package]] @@ -641,7 +693,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] @@ -697,15 +749,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -713,7 +765,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -732,28 +784,19 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "ahash 0.8.3", + "ahash", "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -765,9 +808,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -775,6 +818,33 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -805,9 +875,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -826,7 +896,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -835,15 +905,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ + "futures-util", "http", "hyper", - "rustls 0.21.2", + "rustls", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", ] [[package]] @@ -896,12 +967,24 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ - "cfg-if", + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "inherent" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce243b1bfa62ffc028f1cc3b6034ec63d649f3031bc8a4fbbb004e1ac17d1f68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", ] [[package]] @@ -924,7 +1007,7 @@ checksum = "2f00e8918e7eb15347ee01e4423d19be3ec30b4d9fec32b76cdbed1adff8ee73" dependencies = [ "darling", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] @@ -935,18 +1018,18 @@ checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -980,9 +1063,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsqlite3-sys" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" dependencies = [ "cc", "pkg-config", @@ -991,19 +1074,26 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "pkg-config", "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "local-ip-address" -version = "0.5.2" -source = "git+https://github.com/jacobtread/local-ip-address.git#8be57e884721f8201ca04195472e5f6f3129fb5f" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885efb07efcd6ae1c6af70be7565544121424fa9e5b1c3e4b58bbbf141a58cef" dependencies = [ "libc", "neli", @@ -1023,9 +1113,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ "serde", ] @@ -1060,7 +1150,7 @@ dependencies = [ "libc", "log", "log-mdc", - "parking_lot 0.12.1", + "parking_lot", "thiserror", "thread-id", "winapi", @@ -1072,20 +1162,29 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] name = "matchit" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" + +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest", +] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mime" @@ -1194,9 +1293,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -1214,9 +1313,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1227,38 +1326,37 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ordered-float" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" +dependencies = [ + "num-traits", +] + [[package]] name = "ouroboros" -version = "0.15.6" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" dependencies = [ "aliasable", "ouroboros_macro", + "static_assertions", ] [[package]] name = "ouroboros_macro" -version = "0.15.6" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ - "Inflector", + "heck", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", + "syn 2.0.31", ] [[package]] @@ -1268,21 +1366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -1311,9 +1395,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pem-rfc7468" @@ -1324,6 +1408,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -1332,29 +1425,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1368,20 +1461,41 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", "zeroize", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.8", + "pkcs8 0.10.2", + "spki 0.7.2", +] + [[package]] name = "pkcs8" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -1392,14 +1506,14 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "pocket-relay" -version = "0.5.8" +version = "0.5.9" dependencies = [ "argon2", "axum", "base64ct", - "bitflags 2.3.3", - "blaze-pk", + "bitflags 2.4.0", "blaze-ssl-async", + "bytes", "chrono", "email_address", "embeddy", @@ -1417,6 +1531,7 @@ dependencies = [ "sea-orm-migration", "serde", "serde_json", + "tdf", "thiserror", "tokio", "tokio-util", @@ -1455,18 +1570,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1477,6 +1592,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core", ] @@ -1520,11 +1636,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ - "regex-syntax 0.7.2", + "aho-corasick", + "memchr", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", ] [[package]] @@ -1536,6 +1655,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1544,15 +1674,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ "base64", "bytes", @@ -1571,19 +1701,19 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.2", + "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.2", "winreg", ] @@ -1614,10 +1744,32 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "pkcs1", - "pkcs8", + "pkcs1 0.4.1", + "pkcs8 0.9.0", + "rand_core", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1 0.7.5", + "pkcs8 0.10.2", "rand_core", "signature", + "spki 0.7.2", "subtle", "zeroize", ] @@ -1629,22 +1781,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustls" -version = "0.20.8" +name = "rustix" +version = "0.38.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" dependencies = [ - "log", - "ring", - "sct", - "webpki", + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", ] [[package]] name = "rustls" -version = "0.21.2" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", @@ -1663,9 +1816,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ "ring", "untrusted", @@ -1673,15 +1826,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -1694,9 +1847,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -1708,11 +1861,24 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sea-bae" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.31", +] + [[package]] name = "sea-orm" -version = "0.11.3" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fade86e8d41fd1a4721f84cb834f4ca2783f973cc30e6212b7fafc134f169214" +checksum = "61f6c7daef05dde3476d97001e11fca7a52b655aa3bf4fd610ab2da1176a2ed5" dependencies = [ "async-stream", "async-trait", @@ -1723,9 +1889,9 @@ dependencies = [ "sea-orm-macros", "sea-query", "sea-query-binder", - "sea-strum", "serde", "sqlx", + "strum", "thiserror", "tracing", "url", @@ -1733,22 +1899,23 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "0.11.3" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28936f26d62234ff0be16f80115dbdeb3237fe9c25cf18fbcd1e3b3592360f20" +checksum = "cd90e73d5f5b184bad525767da29fbfec132b4e62ebd6f60d2f2737ec6468f62" dependencies = [ - "bae", - "heck 0.3.3", + "heck", "proc-macro2", "quote", - "syn 1.0.109", + "sea-bae", + "syn 2.0.31", + "unicode-ident", ] [[package]] name = "sea-orm-migration" -version = "0.11.3" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278d3adfd0832b6ffc17d3cfbc574d3695a5c1b38814e0bc8ac238d33f3d87cf" +checksum = "21f673fcefb3a7e7b89a12b6c0e854ec0be14367635ac3435369c8ad7f11e09e" dependencies = [ "async-trait", "futures", @@ -1760,19 +1927,22 @@ dependencies = [ [[package]] name = "sea-query" -version = "0.28.5" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbab99b8cd878ab7786157b7eb8df96333a6807cc6e45e8888c85b51534b401a" +checksum = "28c05a5bf6403834be253489bbe95fa9b1e5486bc843b61f60d26b5c9c1e244b" dependencies = [ "chrono", + "derivative", + "inherent", + "ordered-float", "sea-query-derive", ] [[package]] name = "sea-query-binder" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cea85029985b40dfbf18318d85fe985c04db7c1b4e5e8e0a0a0cdff5f1e30f9" +checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9" dependencies = [ "chrono", "sea-query", @@ -1781,11 +1951,11 @@ dependencies = [ [[package]] name = "sea-query-derive" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f62030c60f3a691f5fe251713b4e220b306e50a71e1d6f9cce1f24bb781978" +checksum = "bd78f2e0ee8e537e9195d1049b752e0433e2cac125426bccb7b5c3e508096117" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "syn 1.0.109", @@ -1794,9 +1964,9 @@ dependencies = [ [[package]] name = "sea-schema" -version = "0.11.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb2940bb5a10bc6cd05b450ce6cd3993e27fddd7eface2becb97fc5af3a040e" +checksum = "c3e09eb40c78cee8fef8dfbb648036a26b7ad1f618499203ad0e8b6f97593f7f" dependencies = [ "futures", "sea-query", @@ -1805,63 +1975,41 @@ dependencies = [ [[package]] name = "sea-schema-derive" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56821b7076f5096b8f726e2791ad255a99c82498e08ec477a65a96c461ff1927" +checksum = "c6f686050f76bffc4f635cda8aea6df5548666b830b52387e8bc7de11056d11e" dependencies = [ - "heck 0.3.3", + "heck", "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "sea-strum" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391d06a6007842cfe79ac6f7f53911b76dfd69fc9a6769f1cf6569d12ce20e1b" -dependencies = [ - "sea-strum_macros", -] - -[[package]] -name = "sea-strum_macros" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "serde" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] name = "serde_json" -version = "1.0.99" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -1870,9 +2018,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1b6471d7496b051e03f1958802a73f88b947866f5146f329e47e36554f4e55" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" dependencies = [ "itoa", "serde", @@ -1890,6 +2038,28 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1920,18 +2090,18 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -1943,6 +2113,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" @@ -1965,14 +2145,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.8", ] [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ "itertools", "nom", @@ -1981,94 +2171,219 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721" dependencies = [ "sqlx-core", "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", ] [[package]] name = "sqlx-core" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" dependencies = [ - "ahash 0.7.6", + "ahash", "atoi", - "bitflags 1.3.2", "byteorder", "bytes", "chrono", + "crc", "crossbeam-queue", "dotenvy", "either", "event-listener", - "flume", "futures-channel", "futures-core", - "futures-executor", "futures-intrusive", + "futures-io", "futures-util", "hashlink", "hex", - "indexmap", - "itoa", - "libc", - "libsqlite3-sys", + "indexmap 2.0.0", "log", "memchr", "once_cell", "paste", "percent-encoding", - "rustls 0.20.8", + "rustls", "rustls-pemfile", + "serde", + "serde_json", + "sha2", "smallvec", "sqlformat", - "sqlx-rt", - "stringprep", "thiserror", + "tokio", "tokio-stream", + "tracing", "url", - "webpki-roots", + "webpki-roots 0.24.0", ] [[package]] name = "sqlx-macros" -version = "0.6.3" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", + "hex", "once_cell", "proc-macro2", "quote", + "serde", + "serde_json", + "sha2", "sqlx-core", - "sqlx-rt", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", "syn 1.0.109", + "tempfile", + "tokio", "url", ] [[package]] -name = "sqlx-rt" -version = "0.6.3" +name = "sqlx-mysql" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" dependencies = [ + "atoi", + "base64", + "bitflags 2.4.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", "once_cell", - "tokio", - "tokio-rustls 0.23.4", + "percent-encoding", + "rand", + "rsa 0.9.2", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", ] +[[package]] +name = "sqlx-postgres" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -2079,6 +2394,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + [[package]] name = "subtle" version = "2.5.0" @@ -2098,9 +2419,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -2113,31 +2434,66 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "tdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a033ec7f181af10c095d38db6ed55e5ecd31e06cd5a3842c4884ff7ce484f44" +dependencies = [ + "serde", + "tdf-derive", +] + +[[package]] +name = "tdf-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f312e2b4ca289722dbdac182878247ee4c0111ca468871eaeaf3c208282ab04c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] name = "thread-id" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73" +checksum = "79474f573561cdc4871a0de34a51c92f7f5a56039113fbb5b9c9f96bdb756669" dependencies = [ "libc", "redox_syscall 0.2.16", @@ -2182,20 +2538,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys", ] @@ -2208,18 +2563,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", - "tokio", - "webpki", + "syn 2.0.31", ] [[package]] @@ -2228,7 +2572,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.2", + "rustls", "tokio", ] @@ -2306,7 +2650,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", ] [[package]] @@ -2353,9 +2697,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -2386,9 +2730,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -2409,9 +2753,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -2459,7 +2803,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", "wasm-bindgen-shared", ] @@ -2493,7 +2837,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.31", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2515,23 +2859,25 @@ dependencies = [ ] [[package]] -name = "webpki" -version = "0.22.0" +name = "webpki-roots" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "ring", - "untrusted", + "rustls-webpki", ] [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" [[package]] name = "winapi" @@ -2584,9 +2930,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -2599,53 +2945,54 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys", ] [[package]] @@ -2655,9 +3002,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d224a125dec5adda27d0346b9cae9794830279c4f9c27e4ab0b6c408d54012" dependencies = [ "const-oid", - "der", + "der 0.6.1", "flagset", - "spki", + "spki 0.6.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8db7044b..6a9085ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pocket-relay" -version = "0.5.8" +version = "0.5.9" description = "Pocket Relay Server" readme = "README.md" keywords = ["EA", "PocketRelay", "MassEffect"] @@ -21,8 +21,7 @@ serde_json = "1" log = { version = "0.4", features = ["serde"] } log-panics = { version = "2", features = ["with-backtrace"] } -# Blaze packet system & SSLv3 async impl -blaze-pk = "1.3" +# SSLv3 async impl blaze-ssl-async = "^0.3" # Resource embedding @@ -55,6 +54,8 @@ hyper = "0.14.25" tower = "0.4" bitflags = "2.3.1" +tdf = { version = "0.1" } +bytes = "1.4.0" # SeaORM @@ -111,11 +112,6 @@ version = "0.4" default-features = false features = ["std", "serde"] - -# Patch which updates the windows-sys dep -[patch.crates-io] -local-ip-address = { git = "https://github.com/jacobtread/local-ip-address.git" } - [profile.release] strip = true lto = true diff --git a/Dockerfile b/Dockerfile index d7d59ce8..34ae2851 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add curl WORKDIR /app # Download server executable -RUN curl -LJ -o pocket-relay-linux https://github.com/PocketRelay/Server/releases/download/v0.5.8/pocket-relay-linux +RUN curl -LJ -o pocket-relay-linux https://github.com/PocketRelay/Server/releases/download/v0.5.9/pocket-relay-linux # Make the server executable RUN chmod +x ./pocket-relay-linux diff --git a/src/config.rs b/src/config.rs index 7daa0968..c14bd658 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,9 @@ use log::LevelFilter; use serde::Deserialize; use std::{env, fs::read_to_string, path::Path}; +/// The server version extracted from the Cargo.toml +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + pub struct RuntimeConfig { pub reverse_proxy: bool, pub galaxy_at_war: GalaxyAtWarConfig, @@ -51,10 +54,6 @@ pub fn load_config() -> Option { Some(config) } -pub struct ServicesConfig { - pub retriever: RetrieverConfig, -} - #[derive(Deserialize)] #[serde(default)] pub struct Config { diff --git a/src/database/entities/players.rs b/src/database/entities/players.rs index bc128e35..d32e969e 100644 --- a/src/database/entities/players.rs +++ b/src/database/entities/players.rs @@ -1,7 +1,7 @@ //! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 +use crate::config::RuntimeConfig; use crate::database::DbResult; -use crate::state::App; use crate::utils::hashing::hash_password; use sea_orm::prelude::*; use sea_orm::{ @@ -68,14 +68,13 @@ impl Model { /// `display_name` The player display name /// `password` The hashed player password /// `origin` Whether the account is an origin account - pub fn create( - db: &DatabaseConnection, + pub fn create<'db>( + db: &'db DatabaseConnection, email: String, display_name: String, mut password: Option, - ) -> DbFuture { - let config = App::config(); - + config: &RuntimeConfig, + ) -> DbFuture<'db, Self> { let mut role = PlayerRole::Default; if config diff --git a/src/main.rs b/src/main.rs index 30a91f34..373f6405 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,18 @@ -use axum::Server; +use crate::{ + config::{RuntimeConfig, VERSION}, + services::{ + game::manager::GameManager, leaderboard::Leaderboard, retriever::Retriever, + sessions::Sessions, + }, +}; +use axum::{Extension, Server}; use config::load_config; -use log::{error, info}; -use state::App; -use std::net::{Ipv4Addr, SocketAddr}; -use tokio::{select, signal}; +use log::{error, info, LevelFilter}; +use std::{ + net::{Ipv4Addr, SocketAddr}, + sync::Arc, +}; +use tokio::{join, select, signal}; use utils::logging; mod config; @@ -12,7 +21,6 @@ mod middleware; mod routes; mod services; mod session; -mod state; mod utils; #[tokio::main] @@ -20,23 +28,64 @@ async fn main() { // Load configuration let config = load_config().unwrap_or_default(); + if config.logging == LevelFilter::Debug { + utils::components::initialize(); + } + // Initialize logging logging::setup(config.logging); // Create the server socket address while the port is still available let addr: SocketAddr = (Ipv4Addr::UNSPECIFIED, config.port).into(); - // Initialize global state - App::init(config).await; + // Config data persisted to runtime + let runtime_config = RuntimeConfig { + reverse_proxy: config.reverse_proxy, + galaxy_at_war: config.galaxy_at_war, + menu_message: config.menu_message, + dashboard: config.dashboard, + }; + + // This step may take longer than expected so its spawned instead of joined + tokio::spawn(logging::log_connection_urls(config.port)); + + let (db, retriever, sessions) = join!( + database::init(&runtime_config), + Retriever::start(config.retriever), + Sessions::start() + ); + let game_manager = GameManager::start(); + let leaderboard = Leaderboard::start(); + let config = Arc::new(runtime_config); + + // Initialize session router + let mut router = session::routes::router(); + + router.add_extension(db.clone()); + router.add_extension(config.clone()); + router.add_extension(retriever.clone()); + router.add_extension(game_manager.clone()); + router.add_extension(leaderboard.clone()); + router.add_extension(sessions.clone()); + + let router = router.build(); // Create the HTTP router - let router = routes::router().into_make_service_with_connect_info::(); + let router = routes::router() + // Apply data extensions + .layer(Extension(db)) + .layer(Extension(config)) + .layer(Extension(router)) + .layer(Extension(game_manager)) + .layer(Extension(leaderboard)) + .layer(Extension(sessions)) + .into_make_service_with_connect_info::(); // Create futures for server and shutdown signal let server_future = Server::bind(&addr).serve(router); let close_future = signal::ctrl_c(); - info!("Started server on {} (v{})", addr, state::VERSION); + info!("Started server on {} (v{})", addr, VERSION); // Await server termination or shutdown signal select! { diff --git a/src/middleware/auth.rs b/src/middleware/auth.rs index 8bbfe0c4..0666c5bb 100644 --- a/src/middleware/auth.rs +++ b/src/middleware/auth.rs @@ -3,8 +3,7 @@ use crate::{ entities::{players::PlayerRole, Player}, DbErr, }, - services::tokens::{Tokens, VerifyError}, - state::App, + services::sessions::{Sessions, VerifyError, VerifyTokenMessage}, utils::types::BoxFuture, }; use axum::{ @@ -13,50 +12,40 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use std::marker::PhantomData; +use interlink::prelude::{Link, LinkError}; +use sea_orm::DatabaseConnection; use thiserror::Error; -/// Extractor for extracting authentication from a request -/// authorization header Bearer token -pub struct Auth(pub Player, PhantomData); +pub struct Auth(pub Player); +pub struct AdminAuth(pub Player); -impl Auth { - /// Converts the auth guard into its inner player - pub fn into_inner(self) -> Player { - self.0 - } -} - -/// Alias for an auth gaurd using admin verification -pub type AdminAuth = Auth; - -pub trait AuthVerifier { - /// Verify function for checking that the provided - /// player meets the requirements - fn verify(player: &Player) -> bool; -} - -/// Unit auth verifier type for accepting any player -impl AuthVerifier for () { - fn verify(_player: &Player) -> bool { - true - } -} - -/// Auth verifier implementation requiring a role of -/// Admin or higher -pub struct AdminVerify; +impl FromRequestParts for AdminAuth { + type Rejection = TokenError; -impl AuthVerifier for AdminVerify { - fn verify(player: &Player) -> bool { - player.role >= PlayerRole::Admin + fn from_request_parts<'a, 'b, 'c>( + parts: &'a mut axum::http::request::Parts, + state: &'b S, + ) -> BoxFuture<'c, Result> + where + 'a: 'c, + 'b: 'c, + Self: 'c, + { + let auth = Auth::from_request_parts(parts, state); + Box::pin(async move { + let Auth(player) = auth.await?; + if player.role < PlayerRole::Admin { + return Err(TokenError::MissingRole); + } + Ok(AdminAuth(player)) + }) } } /// The HTTP header that contains the authentication token const TOKEN_HEADER: &str = "X-Token"; -impl FromRequestParts for Auth { +impl FromRequestParts for Auth { type Rejection = TokenError; fn from_request_parts<'a, 'b, 'c>( @@ -68,6 +57,17 @@ impl FromRequestParts for Auth { 'b: 'c, Self: 'c, { + let db = parts + .extensions + .get::() + .expect("Database connection extension missing") + .clone(); + let sessions = parts + .extensions + .get::>() + .expect("Database connection extension missing") + .clone(); + Box::pin(async move { // Extract the token from the headers let token = parts @@ -76,11 +76,20 @@ impl FromRequestParts for Auth { .and_then(|value| value.to_str().ok()) .ok_or(TokenError::MissingToken)?; - // Verify the token claim - let db = App::database(); - let player: Player = Tokens::service_verify(db, token).await?; + let player_id = sessions + .send(VerifyTokenMessage(token.to_string())) + .await + .map_err(TokenError::SessionService)? + .map_err(|err| match err { + VerifyError::Expired => TokenError::ExpiredToken, + VerifyError::Invalid => TokenError::InvalidToken, + })?; + + let player = Player::by_id(&db, player_id) + .await? + .ok_or(TokenError::InvalidToken)?; - Ok(Self(player, PhantomData)) + Ok(Self(player)) }) } } @@ -98,18 +107,15 @@ pub enum TokenError { /// The provided token was not a valid token #[error("Invalid token")] InvalidToken, + /// Authentication is not high enough role + #[error("Missing required role")] + MissingRole, /// Database error #[error("Internal server error")] Database(#[from] DbErr), -} - -impl From for TokenError { - fn from(value: VerifyError) -> Self { - match value { - VerifyError::Expired => Self::ExpiredToken, - _ => Self::InvalidToken, - } - } + /// Session service error + #[error("Session service unavailable")] + SessionService(LinkError), } /// IntoResponse implementation for TokenError to allow it to be @@ -120,7 +126,8 @@ impl IntoResponse for TokenError { let status = match &self { Self::MissingToken => StatusCode::BAD_REQUEST, Self::InvalidToken | Self::ExpiredToken => StatusCode::UNAUTHORIZED, - Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::MissingRole => StatusCode::FORBIDDEN, + Self::Database(_) | Self::SessionService(_) => StatusCode::INTERNAL_SERVER_ERROR, }; (status, boxed(self.to_string())).into_response() diff --git a/src/middleware/ip_address.rs b/src/middleware/ip_address.rs index 62e19449..9b5dc8aa 100644 --- a/src/middleware/ip_address.rs +++ b/src/middleware/ip_address.rs @@ -1,5 +1,4 @@ -use std::net::SocketAddr; - +use crate::config::RuntimeConfig; use axum::{ async_trait, body::boxed, @@ -10,12 +9,14 @@ use axum::{ }; use hyper::{HeaderMap, StatusCode}; use log::warn; +use std::{ + net::{Ipv4Addr, SocketAddr}, + sync::Arc, +}; use thiserror::Error; -use crate::state::App; - /// Middleware for extracting the server public address -pub struct IpAddress(pub SocketAddr); +pub struct IpAddress(pub Ipv4Addr); const REAL_IP_HEADER: &str = "X-Real-IP"; @@ -27,28 +28,49 @@ where type Rejection = IpAddressError; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let reverse_proxy = App::config().reverse_proxy; + let config = parts + .extensions + .get::>() + .expect("Missing runtime config"); + + let reverse_proxy = config.reverse_proxy; if reverse_proxy { let ip = match extract_ip_header(&parts.headers) { - Some(ip) => ip, - None => { - warn!("Failed to extract X-Real-IP header from connecting client. If you are NOT using a reverse proxy\n\ + Ok(ip) => ip, + Err(err) => { + warn!("Failed to extract X-Real-IP header from incoming request. If you are NOT using a reverse proxy\n\ disable the `reverse_proxy` config property, otherwise check that your reverse proxy is configured\n\ - correctly according the guide. (Closing connection with error)"); - return Err(IpAddressError::InvalidOrMissing); + correctly according the guide. (Closing connection with error) cause: {}", err); + return Err(err); } }; return Ok(Self(ip)); } - let value = Extension::>::from_request_parts(parts, state).await?; - Ok(Self(value.0 .0)) + let Extension(ConnectInfo(addr)) = + Extension::>::from_request_parts(parts, state).await?; + + if let SocketAddr::V4(addr) = addr { + return Ok(Self(*addr.ip())); + } + + Err(IpAddressError::InvalidHeader) } } -fn extract_ip_header(headers: &HeaderMap) -> Option { - let header = headers.get(REAL_IP_HEADER)?; - let value = header.to_str().ok()?; - value.parse().ok() +fn extract_ip_header(headers: &HeaderMap) -> Result { + let header = headers + .get(REAL_IP_HEADER) + .ok_or(IpAddressError::MissingHeader)?; + let value = header.to_str().map_err(|_| IpAddressError::InvalidHeader)?; + if let Ok(addr) = value.parse::() { + return Ok(addr); + } + + if let Ok(SocketAddr::V4(addr)) = value.parse::() { + return Ok(*addr.ip()); + } + + Err(IpAddressError::InvalidHeader) } /// Error type used by the token checking middleware to handle @@ -57,8 +79,10 @@ fn extract_ip_header(headers: &HeaderMap) -> Option { pub enum IpAddressError { #[error(transparent)] ConnectInfo(#[from] ExtensionRejection), - #[error("X-Real-IP header is invalid or missing")] - InvalidOrMissing, + #[error("X-Real-IP header is missing")] + MissingHeader, + #[error("X-Real-IP header is invalid")] + InvalidHeader, } /// IntoResponse implementation for TokenError to allow it to be diff --git a/src/routes/auth.rs b/src/routes/auth.rs index ae286a4c..70b830e9 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -1,14 +1,17 @@ +use std::sync::Arc; + use crate::{ + config::RuntimeConfig, database::entities::Player, - services::tokens::Tokens, - state::App, + services::sessions::{CreateTokenMessage, Sessions}, utils::hashing::{hash_password, verify_password}, }; use axum::{ http::StatusCode, response::{IntoResponse, Response}, - Json, + Extension, Json, }; +use interlink::prelude::{Link, LinkError}; use sea_orm::{DatabaseConnection, DbErr}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,6 +23,10 @@ pub enum AuthError { #[error("Server error occurred")] Database(#[from] DbErr), + /// Session service error + #[error("Session service unavailable")] + SessionService(LinkError), + /// Failed to hash the user password #[error("Server error occurred")] PasswordHash(#[from] argon2::password_hash::Error), @@ -70,13 +77,15 @@ pub struct TokenResponse { /// Handles authenticating a user using a username and /// password. Upon success will provide a [`TokenResponse`] /// containing the authentication token for the user -pub async fn login(Json(req): Json) -> AuthRes { +pub async fn login( + Extension(db): Extension, + Extension(sessions): Extension>, + Json(req): Json, +) -> AuthRes { let LoginRequest { email, password } = req; - let db: &DatabaseConnection = App::database(); - // Find a player with the matching email - let player: Player = Player::by_email(db, &email) + let player: Player = Player::by_email(&db, &email) .await? .ok_or(AuthError::InvalidCredentails)?; @@ -88,7 +97,11 @@ pub async fn login(Json(req): Json) -> AuthRes { return Err(AuthError::InvalidCredentails); } - Ok(player.into()) + let token = sessions + .send(CreateTokenMessage(player.id)) + .await + .map_err(AuthError::SessionService)?; + Ok(Json(TokenResponse { token })) } /// Request structure for creating a new account contains @@ -108,8 +121,12 @@ pub struct CreateRequest { /// Handles creating a new user from the provided credentials. /// Upon success will provide a [`TokenResponse`] containing /// the authentication token for the created user -pub async fn create(Json(req): Json) -> AuthRes { - let config = App::config(); +pub async fn create( + Extension(db): Extension, + Extension(config): Extension>, + Extension(sessions): Extension>, + Json(req): Json, +) -> AuthRes { if config.dashboard.disable_registration { return Err(AuthError::RegistrationDisabled); } @@ -125,33 +142,28 @@ pub async fn create(Json(req): Json) -> AuthRes { return Err(AuthError::InvalidUsername); } - let db: &DatabaseConnection = App::database(); - // Validate email taken status - if Player::by_email(db, &email).await?.is_some() { + if Player::by_email(&db, &email).await?.is_some() { return Err(AuthError::EmailTaken); } let password: String = hash_password(&password)?; - let player: Player = Player::create(db, email, username, Some(password)).await?; + let player: Player = Player::create(&db, email, username, Some(password), &config).await?; - Ok(player.into()) -} - -/// Allow conversion from player into JSON token response for simplicity -impl From for Json { - fn from(value: Player) -> Self { - // Claim a token and response with a response - let token: String = Tokens::service_claim(value.id); - Json(TokenResponse { token }) - } + let token = sessions + .send(CreateTokenMessage(player.id)) + .await + .map_err(AuthError::SessionService)?; + Ok(Json(TokenResponse { token })) } /// Response implementation for auth errors impl IntoResponse for AuthError { fn into_response(self) -> Response { let status_code = match &self { - Self::Database(_) | Self::PasswordHash(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::Database(_) | Self::PasswordHash(_) | Self::SessionService(_) => { + StatusCode::INTERNAL_SERVER_ERROR + } Self::InvalidCredentails | Self::OriginAccess => StatusCode::UNAUTHORIZED, Self::EmailTaken | Self::InvalidUsername => StatusCode::BAD_REQUEST, Self::RegistrationDisabled => StatusCode::FORBIDDEN, diff --git a/src/routes/games.rs b/src/routes/games.rs index 0b3bde5e..65fc34e3 100644 --- a/src/routes/games.rs +++ b/src/routes/games.rs @@ -2,19 +2,18 @@ use crate::{ database::entities::players::PlayerRole, middleware::auth::Auth, services::game::{ - manager::{GetGameMessage, SnapshotQueryMessage}, + manager::{GameManager, GetGameMessage, SnapshotQueryMessage}, GameSnapshot, SnapshotMessage, }, - state::App, utils::types::GameID, }; use axum::{ extract::{Path, Query}, http::StatusCode, response::{IntoResponse, Response}, - Json, + Extension, Json, }; -use interlink::prelude::LinkError; +use interlink::prelude::{Link, LinkError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -62,18 +61,18 @@ pub struct GamesResponse { /// /// Player networking information is included for requesting /// players with admin level or greater access. -pub async fn get_games(Query(query): Query, auth: Auth) -> GamesRes { +pub async fn get_games( + Query(query): Query, + Extension(game_manager): Extension>, + Auth(auth): Auth, +) -> GamesRes { let GamesRequest { offset, count } = query; - let auth = auth.into_inner(); let count: usize = count.unwrap_or(20) as usize; let offset: usize = offset * count; - let services = App::services(); - // Retrieve the game snapshots - let (games, more) = services - .game_manager + let (games, more) = game_manager .send(SnapshotQueryMessage { offset, count, @@ -91,12 +90,12 @@ pub async fn get_games(Query(query): Query, auth: Auth) -> GamesRe /// /// Player networking information is included for requesting /// players with admin level or greater access. -pub async fn get_game(Path(game_id): Path, auth: Auth) -> GamesRes { - let auth = auth.into_inner(); - let services = App::services(); - - let game = services - .game_manager +pub async fn get_game( + Path(game_id): Path, + Extension(game_manager): Extension>, + Auth(auth): Auth, +) -> GamesRes { + let game = game_manager .send(GetGameMessage { game_id }) .await? .ok_or(GamesError::NotFound)?; diff --git a/src/routes/gaw.rs b/src/routes/gaw.rs index e2395ddc..6c499538 100644 --- a/src/routes/gaw.rs +++ b/src/routes/gaw.rs @@ -5,22 +5,24 @@ //! other than the Mass Effect 3 client itself. use crate::{ + config::RuntimeConfig, database::{ entities::{GalaxyAtWar, Player, PlayerData}, DatabaseConnection, DbErr, DbResult, }, middleware::xml::Xml, - services::tokens::Tokens, - state::App, + services::sessions::{Sessions, VerifyTokenMessage}, utils::parsing::PlayerClass, }; use axum::{ extract::{Path, Query}, http::{header, HeaderValue, StatusCode}, response::{IntoResponse, Response}, + Extension, }; +use interlink::prelude::Link; use serde::Deserialize; -use std::fmt::Display; +use std::{fmt::Display, sync::Arc}; use tokio::try_join; /// Error type used in gaw routes to handle errors such @@ -93,9 +95,13 @@ pub async fn shared_token_login(Query(query): Query) -> Xml { /// with the provied ID /// /// `id` The hex encoded ID of the player -pub async fn get_ratings(Path(id): Path) -> Result { - let db = App::database(); - let (gaw_data, promotions) = get_player_gaw_data(db, &id).await?; +pub async fn get_ratings( + Path(id): Path, + Extension(db): Extension, + Extension(config): Extension>, + Extension(sessions): Extension>, +) -> Result { + let (gaw_data, promotions) = get_player_gaw_data(&db, sessions, &id, &config).await?; Ok(ratings_response(gaw_data, promotions)) } @@ -131,11 +137,13 @@ pub struct IncreaseQuery { pub async fn increase_ratings( Path(id): Path, Query(query): Query, + Extension(db): Extension, + Extension(config): Extension>, + Extension(sessions): Extension>, ) -> Result { - let db = App::database(); - let (gaw_data, promotions) = get_player_gaw_data(db, &id).await?; + let (gaw_data, promotions) = get_player_gaw_data(&db, sessions, &id, &config).await?; let gaw_data = gaw_data - .increase(db, (query.a, query.b, query.c, query.d, query.e)) + .increase(&db, (query.a, query.b, query.c, query.d, query.e)) .await?; Ok(ratings_response(gaw_data, promotions)) } @@ -147,22 +155,32 @@ pub async fn increase_ratings( /// `id` The hex ID of the player async fn get_player_gaw_data( db: &DatabaseConnection, + sessions: Link, token: &str, + config: &RuntimeConfig, ) -> Result<(GalaxyAtWar, u32), GAWError> { - let player: Player = Tokens::service_verify(db, token) + let player_id = sessions + .send(VerifyTokenMessage(token.to_string())) .await + .map_err(|_| GAWError::ServerError)? .map_err(|_| GAWError::InvalidToken)?; - let config = App::config(); + + let player = Player::by_id(db, player_id) + .await? + .ok_or(GAWError::InvalidToken)?; let (gaw_data, promotions) = try_join!( GalaxyAtWar::find_or_create(db, player.id, config.galaxy_at_war.decay), - get_promotions(db, &player) + get_promotions(db, &player, config) )?; Ok((gaw_data, promotions)) } -async fn get_promotions(db: &DatabaseConnection, player: &Player) -> DbResult { - let config = App::config(); +async fn get_promotions( + db: &DatabaseConnection, + player: &Player, + config: &RuntimeConfig, +) -> DbResult { if !config.galaxy_at_war.promotions { return Ok(0); } diff --git a/src/routes/leaderboard.rs b/src/routes/leaderboard.rs index 0d1b7dc9..c1d518ec 100644 --- a/src/routes/leaderboard.rs +++ b/src/routes/leaderboard.rs @@ -1,15 +1,15 @@ use crate::{ - services::leaderboard::{models::*, QueryMessage}, - state::App, + services::leaderboard::{models::*, Leaderboard, QueryMessage}, utils::types::PlayerID, }; use axum::{ extract::{Path, Query}, http::StatusCode, response::{IntoResponse, Response}, - Json, + Extension, Json, }; -use interlink::prelude::LinkError; +use interlink::prelude::{Link, LinkError}; +use sea_orm::DatabaseConnection; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -67,14 +67,14 @@ pub struct LeaderboardResponse<'a> { /// `query` The leaderboard query pub async fn get_leaderboard( Path(name): Path, + Extension(db): Extension, + Extension(leaderboard): Extension>, Query(query): Query, ) -> Result { let LeaderboardQuery { offset, count } = query; let ty: LeaderboardType = LeaderboardType::try_parse(&name).ok_or(LeaderboardError::UnknownLeaderboard)?; - let services = App::services(); - let leaderboard = &services.leaderboard; /// The default number of entries to return in a leaderboard response const DEFAULT_COUNT: u8 = 40; @@ -84,7 +84,7 @@ pub async fn get_leaderboard( // Calculate the start and ending indexes let start: usize = offset * count; - let group = leaderboard.send(QueryMessage(ty)).await?; + let group = leaderboard.send(QueryMessage(ty, db)).await?; let (entries, more) = group .get_normal(start, count) @@ -107,14 +107,13 @@ pub async fn get_leaderboard( /// `name` The name of the leaderboard type to query /// `player_id` The ID of the player to find the leaderboard ranking of pub async fn get_player_ranking( + Extension(db): Extension, + Extension(leaderboard): Extension>, Path((name, player_id)): Path<(String, PlayerID)>, ) -> Result { let ty: LeaderboardType = LeaderboardType::try_parse(&name).ok_or(LeaderboardError::UnknownLeaderboard)?; - let services = App::services(); - let leaderboard = &services.leaderboard; - - let group = leaderboard.send(QueryMessage(ty)).await?; + let group = leaderboard.send(QueryMessage(ty, db)).await?; let entry = match group.get_entry(player_id) { Some(value) => value, diff --git a/src/routes/players.rs b/src/routes/players.rs index c8eb750b..97e6282b 100644 --- a/src/routes/players.rs +++ b/src/routes/players.rs @@ -6,7 +6,6 @@ use crate::{ DatabaseConnection, DbErr, }, middleware::auth::{AdminAuth, Auth}, - state::App, utils::{ hashing::{hash_password, verify_password}, types::PlayerID, @@ -16,13 +15,13 @@ use axum::{ extract::{Path, Query}, http::StatusCode, response::{IntoResponse, Response}, - Json, + Extension, Json, }; +use email_address::EmailAddress; use log::error; use sea_orm::{EntityTrait, PaginatorTrait, QueryOrder}; use serde::{ser::SerializeMap, Deserialize, Serialize}; use thiserror::Error; -use email_address::EmailAddress; /// Enum for errors that could occur when accessing any of /// the players routes @@ -108,19 +107,18 @@ pub struct PlayersResponse { /// is the number of rows to collect. Offset = offset * count /// /// `query` The query containing the offset and count values -/// `_auth` The currently authenticated (Admin) player pub async fn get_players( + _: AdminAuth, Query(query): Query, - _auth: AdminAuth, + Extension(db): Extension, ) -> PlayersRes { const DEFAULT_COUNT: u8 = 20; - let db = App::database(); let count = query.count.unwrap_or(DEFAULT_COUNT); let paginator = players::Entity::find() .order_by_asc(players::Column::Id) - .paginate(db, count as u64); + .paginate(&db, count as u64); let page = query.offset as u64; let total_pages = paginator.num_pages().await?; let more = page < total_pages; @@ -135,8 +133,8 @@ pub async fn get_players( /// authentication token /// /// `auth` The currently authenticated player -pub async fn get_self(auth: Auth) -> Json { - Json(auth.into_inner()) +pub async fn get_self(Auth(auth): Auth) -> Json { + Json(auth) } /// GET /api/players/:id @@ -145,10 +143,12 @@ pub async fn get_self(auth: Auth) -> Json { /// matches the provided {id} /// /// `player_id` The ID of the player to get -/// `_auth` The currently authenticated (Admin) player -pub async fn get_player(Path(player_id): Path, _auth: AdminAuth) -> PlayersRes { - let db = App::database(); - let player = find_player(db, player_id).await?; +pub async fn get_player( + _: AdminAuth, + Path(player_id): Path, + Extension(db): Extension, +) -> PlayersRes { + let player = find_player(&db, player_id).await?; Ok(Json(player)) } @@ -174,22 +174,20 @@ pub struct UpdateDetailsRequest { /// `auth` The currently authenticated player /// `req` The update details request pub async fn set_details( + AdminAuth(auth): AdminAuth, Path(player_id): Path, - auth: AdminAuth, + Extension(db): Extension, Json(req): Json, ) -> PlayersResult<()> { - let auth = auth.into_inner(); - // Get the target player - let db = App::database(); - let player = find_player(db, player_id).await?; + let player = find_player(&db, player_id).await?; // Check modification permission if !auth.has_permission_over(&player) { return Err(PlayersError::InvalidPermission); } - attempt_set_details(db, player, req).await?; + attempt_set_details(&db, player, req).await?; // Ok status code indicating updated Ok(()) @@ -204,18 +202,15 @@ pub async fn set_details( /// `auth` The currently authenticated player /// `req` The details update request pub async fn update_details( - auth: Auth, + Auth(auth): Auth, + Extension(db): Extension, Json(req): Json, ) -> PlayersResult<()> { - // Obtain the player from auth - let player = auth.into_inner(); - if !EmailAddress::is_valid(&req.email) { return Err(PlayersError::InvalidEmail); } - let db = App::database(); - attempt_set_details(db, player, req).await?; + attempt_set_details(&db, auth, req).await?; // Ok status code indicating updated Ok(()) @@ -276,15 +271,13 @@ pub struct SetPasswordRequest { /// `auth` The currently authenticated (Admin) player /// `req` The password set request pub async fn set_password( + AdminAuth(auth): AdminAuth, Path(player_id): Path, - auth: AdminAuth, + Extension(db): Extension, Json(req): Json, ) -> PlayersResult<()> { - let auth = auth.into_inner(); - // Get the target player - let db = App::database(); - let player = find_player(db, player_id).await?; + let player = find_player(&db, player_id).await?; // Check modification permission if !auth.has_permission_over(&player) { @@ -292,7 +285,7 @@ pub async fn set_password( } let password = hash_password(&req.password)?; - player.set_password(db, password).await?; + player.set_password(&db, password).await?; // Ok status code indicating updated Ok(()) @@ -308,12 +301,11 @@ pub struct SetPlayerRoleRequest { } pub async fn set_role( + AdminAuth(auth): AdminAuth, Path(player_id): Path, - auth: AdminAuth, + Extension(db): Extension, Json(req): Json, ) -> PlayersResult<()> { - let auth = auth.into_inner(); - let role = req.role; // Super admin role cannot be granted by anyone but the server @@ -327,10 +319,9 @@ pub async fn set_role( } // Get the target player - let db = App::database(); - let player = find_player(db, player_id).await?; + let player = find_player(&db, player_id).await?; - player.set_role(db, role).await?; + player.set_role(&db, role).await?; Ok(()) } @@ -350,7 +341,8 @@ pub struct UpdatePasswordRequest { /// takes the current account password and the new account password /// as the request data pub async fn update_password( - auth: Auth, + Auth(player): Auth, + Extension(db): Extension, Json(req): Json, ) -> PlayersResult<()> { let UpdatePasswordRequest { @@ -358,9 +350,6 @@ pub async fn update_password( new_password, } = req; - // Obtain the player from auth - let player = auth.into_inner(); - let player_password: &str = player .password .as_ref() @@ -371,9 +360,8 @@ pub async fn update_password( return Err(PlayersError::InvalidPassword); } - let db = App::database(); let password = hash_password(&new_password)?; - player.set_password(db, password).await?; + player.set_password(&db, password).await?; Ok(()) } @@ -384,18 +372,18 @@ pub async fn update_password( /// /// `player_id` The ID of the player to delete /// `auth` The currently authenticated (Admin) player -pub async fn delete_player(auth: AdminAuth, Path(player_id): Path) -> PlayersResult<()> { - // Obtain the authenticated player - let auth = auth.into_inner(); - - let db = App::database(); - let player: Player = find_player(db, player_id).await?; +pub async fn delete_player( + AdminAuth(auth): AdminAuth, + Path(player_id): Path, + Extension(db): Extension, +) -> PlayersResult<()> { + let player: Player = find_player(&db, player_id).await?; if !auth.can_delete(&player) { return Err(PlayersError::InvalidPermission); } - player.delete(db).await?; + player.delete(&db).await?; Ok(()) } /// Request to update the password of the current user account @@ -408,10 +396,11 @@ pub struct DeleteSelfRequest { /// DELETE /api/players/self /// /// Route for deleting the authenticated player -pub async fn delete_self(auth: Auth, Json(req): Json) -> PlayersResult<()> { - // Obtain the authenticated player - let auth = auth.into_inner(); - +pub async fn delete_self( + Auth(auth): Auth, + Extension(db): Extension, + Json(req): Json, +) -> PlayersResult<()> { let player_password: &str = auth .password .as_ref() @@ -422,8 +411,7 @@ pub async fn delete_self(auth: Auth, Json(req): Json) -> Play return Err(PlayersError::InvalidPassword); } - let db = App::database(); - auth.delete(db).await?; + auth.delete(&db).await?; Ok(()) } @@ -450,13 +438,12 @@ impl Serialize for PlayerDataMap { /// matches the provided {id} /// /// `player_id` The ID of the player -/// `_admin` The currently authenticated (Admin) player pub async fn all_data( + _: AdminAuth, Path(player_id): Path, - _admin: AdminAuth, + Extension(db): Extension, ) -> PlayersRes { - let db = App::database(); - let data = PlayerData::all(db, player_id).await?; + let data = PlayerData::all(&db, player_id).await?; Ok(Json(PlayerDataMap(data))) } @@ -470,18 +457,17 @@ pub async fn all_data( /// `key` The player data key /// `auth` The currently authenticated player pub async fn get_data( + Auth(auth): Auth, Path((player_id, key)): Path<(PlayerID, String)>, - auth: Auth, + Extension(db): Extension, ) -> PlayersRes { - let auth = auth.into_inner(); - let db = App::database(); - let player: Player = find_player(db, player_id).await?; + let player: Player = find_player(&db, player_id).await?; if !auth.has_permission_over(&player) { return Err(PlayersError::InvalidPermission); } - let value = PlayerData::get(db, player.id, &key) + let value = PlayerData::get(&db, player.id, &key) .await? .ok_or(PlayersError::DataNotFound)?; Ok(Json(value)) @@ -504,21 +490,18 @@ pub struct SetDataRequest { /// `auth` The currently authenticated (Admin) player /// `req` The request containing the data value pub async fn set_data( + AdminAuth(auth): AdminAuth, Path((player_id, key)): Path<(PlayerID, String)>, - auth: AdminAuth, + Extension(db): Extension, Json(req): Json, ) -> PlayersRes { - // Obtain the authenticated player - let auth = auth.into_inner(); - - let db = App::database(); - let player: Player = find_player(db, player_id).await?; + let player: Player = find_player(&db, player_id).await?; if !auth.has_permission_over(&player) { return Err(PlayersError::InvalidPermission); } - let data = PlayerData::set(db, player.id, key, req.value).await?; + let data = PlayerData::set(&db, player.id, key, req.value).await?; Ok(Json(data)) } @@ -531,20 +514,17 @@ pub async fn set_data( /// `key` The player data key /// `auth` The currently authenticated (Admin) player pub async fn delete_data( + AdminAuth(auth): AdminAuth, Path((player_id, key)): Path<(PlayerID, String)>, - auth: AdminAuth, + Extension(db): Extension, ) -> PlayersResult<()> { - // Obtain the authenticated player - let auth = auth.into_inner(); - - let db = App::database(); - let player: Player = find_player(db, player_id).await?; + let player: Player = find_player(&db, player_id).await?; if !auth.has_permission_over(&player) { return Err(PlayersError::InvalidPermission); } - PlayerData::delete(db, player.id, &key).await?; + PlayerData::delete(&db, player.id, &key).await?; Ok(()) } @@ -555,14 +535,13 @@ pub async fn delete_data( /// matches the provided `id` /// /// `player_id` The ID of the player to get the GAW data for -/// `_admin` The currently authenticated (Admin) player pub async fn get_player_gaw( + _: AdminAuth, Path(player_id): Path, - _admin: AdminAuth, + Extension(db): Extension, ) -> PlayersRes { - let db = App::database(); - let player = find_player(db, player_id).await?; - let galax_at_war = GalaxyAtWar::find_or_create(db, player.id, 0.0).await?; + let player = find_player(&db, player_id).await?; + let galax_at_war = GalaxyAtWar::find_or_create(&db, player.id, 0.0).await?; Ok(Json(galax_at_war)) } diff --git a/src/routes/server.rs b/src/routes/server.rs index 7ef020f8..e57191fe 100644 --- a/src/routes/server.rs +++ b/src/routes/server.rs @@ -2,23 +2,26 @@ //! about the server such as the version and services running use crate::{ + config::{RuntimeConfig, VERSION}, database::entities::players::PlayerRole, middleware::{auth::AdminAuth, blaze_upgrade::BlazeUpgrade, ip_address::IpAddress}, - session::Session, - state::{self, App}, + services::{game::manager::GameManager, sessions::Sessions}, + session::{packet::PacketCodec, router::BlazeRouter, Session}, utils::logging::LOG_FILE_NAME, }; use axum::{ body::Empty, http::{header, HeaderValue, StatusCode}, response::{IntoResponse, Response}, - Json, + Extension, Json, }; -use blaze_pk::packet::PacketCodec; -use interlink::service::Service; +use interlink::{prelude::Link, service::Service}; use log::{debug, error}; use serde::{Deserialize, Serialize}; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Arc, +}; use tokio::{fs::read_to_string, io::split}; use tokio_util::codec::{FramedRead, FramedWrite}; @@ -42,7 +45,7 @@ pub struct ServerDetails { pub async fn server_details() -> Json { Json(ServerDetails { ident: "POCKET_RELAY_SERVER", - version: state::VERSION, + version: VERSION, }) } @@ -58,9 +61,9 @@ pub struct DashboardDetails { /// Handles providing the server details. The Pocket Relay client tool /// uses this endpoint to validate that the provided host is a valid /// Pocket Relay server. -pub async fn dashboard_details() -> Json { - let config = App::config(); - +pub async fn dashboard_details( + Extension(config): Extension>, +) -> Json { Json(DashboardDetails { disable_registration: config.dashboard.disable_registration, }) @@ -71,7 +74,13 @@ pub async fn dashboard_details() -> Json { /// Handles upgrading connections from the Pocket Relay Client tool /// from HTTP over to the Blaze protocol for proxing the game traffic /// as blaze sessions using HTTP Upgrade -pub async fn upgrade(IpAddress(socket_addr): IpAddress, upgrade: BlazeUpgrade) -> Response { +pub async fn upgrade( + IpAddress(addr): IpAddress, + Extension(router): Extension>, + Extension(game_manager): Extension>, + Extension(sessions): Extension>, + upgrade: BlazeUpgrade, +) -> Response { // TODO: Socket address extraction for forwarded reverse proxy tokio::spawn(async move { @@ -94,7 +103,15 @@ pub async fn upgrade(IpAddress(socket_addr): IpAddress, upgrade: BlazeUpgrade) - ctx.attach_stream(read, true); let writer = ctx.attach_sink(write); - Session::new(session_id, socket.host_target, writer, socket_addr) + Session::new( + session_id, + socket.host_target, + writer, + addr, + router, + game_manager, + sessions, + ) }); }); @@ -115,10 +132,9 @@ pub async fn upgrade(IpAddress(socket_addr): IpAddress, upgrade: BlazeUpgrade) - /// Handles loading and responding with the server log file /// contents for the log section on the super admin portion /// of the dashboard -pub async fn get_log(auth: AdminAuth) -> Result { - let auth = auth.into_inner(); +pub async fn get_log(AdminAuth(auth): AdminAuth) -> Result { if auth.role < PlayerRole::SuperAdmin { - return Err(StatusCode::UNAUTHORIZED); + return Err(StatusCode::FORBIDDEN); } let path = std::path::Path::new(LOG_FILE_NAME); read_to_string(path) diff --git a/src/services/game/manager.rs b/src/services/game/manager.rs index 376f40cc..98299cda 100644 --- a/src/services/game/manager.rs +++ b/src/services/game/manager.rs @@ -1,13 +1,26 @@ use super::{ models::{DatalessContext, GameSettings, GameSetupContext, PlayerState}, + rules::RuleSet, AddPlayerMessage, AttrMap, CheckJoinableMessage, Game, GameJoinableState, GamePlayer, GameSnapshot, }; -use crate::{services::matchmaking::rules::RuleSet, utils::types::GameID}; +use crate::{ + services::game::models::AsyncMatchmakingStatus, + session::{packet::Packet, PushExt}, + utils::{ + components::game_manager, + types::{GameID, PlayerID}, + }, +}; use interlink::prelude::*; use log::debug; -use std::{collections::HashMap, sync::Arc}; -use tokio::task::JoinSet; +use std::{ + collections::{HashMap, VecDeque}, + ops::DerefMut, + sync::Arc, + time::SystemTime, +}; +use tokio::{sync::RwLock, task::JoinSet}; /// Manager which controls all the active games on the server /// commanding them to do different actions and removing them @@ -18,6 +31,8 @@ pub struct GameManager { games: HashMap>, /// Stored value for the ID to give the next game next_id: GameID, + /// Matchmaking entry queue + queue: Arc>>, } impl GameManager { @@ -26,11 +41,77 @@ impl GameManager { let this = GameManager { games: Default::default(), next_id: 1, + queue: Default::default(), }; this.start() } } +/// Entry into the matchmaking queue +struct MatchmakingEntry { + /// The player entry + player: GamePlayer, + /// The rules that a game must match for the player to join + rule_set: Arc, + /// Time that the player entered matchmaking + started: SystemTime, +} + +/// Message to remove a player from the matchmaking queue +#[derive(Message)] +pub struct RemoveQueueMessage { + /// The player ID of the player to remove + pub player_id: PlayerID, +} + +impl Handler for GameManager { + /// Empty response type + type Response = Fr; + + fn handle( + &mut self, + msg: RemoveQueueMessage, + _ctx: &mut ServiceContext, + ) -> Self::Response { + let queue_handle = self.queue.clone(); + Fr::new(Box::pin(async move { + let mut queue = queue_handle.write().await; + queue.retain(|value| value.player.player.id != msg.player_id); + })) + } +} + +/// Message to add a new player to the matchmaking queue +#[derive(Message)] +pub struct QueuePlayerMessage { + /// The player to add to the queue + pub player: GamePlayer, + /// The rules for the player + pub rule_set: Arc, +} + +impl Handler for GameManager { + /// Empty response type + type Response = Fr; + + fn handle( + &mut self, + msg: QueuePlayerMessage, + _ctx: &mut ServiceContext, + ) -> Self::Response { + let started = SystemTime::now(); + let queue_handle = self.queue.clone(); + Fr::new(Box::pin(async move { + let mut queue = queue_handle.write().await; + queue.push_back(MatchmakingEntry { + player: msg.player, + rule_set: msg.rule_set, + started, + }); + })) + } +} + /// Message for taking a snapshot of multiple games /// within the specified query range #[derive(Message)] @@ -119,18 +200,14 @@ pub struct CreateMessage { impl Handler for GameManager { type Response = Mr; - fn handle( - &mut self, - mut msg: CreateMessage, - _ctx: &mut ServiceContext, - ) -> Self::Response { + fn handle(&mut self, mut msg: CreateMessage, ctx: &mut ServiceContext) -> Self::Response { let id = self.next_id; self.next_id = self.next_id.wrapping_add(1); msg.host.state = PlayerState::ActiveConnected; - let link = Game::start(id, msg.attributes, msg.setting); + let link = Game::start(id, msg.attributes, msg.setting, ctx.link()); self.games.insert(id, link.clone()); let _ = link.do_send(AddPlayerMessage { @@ -235,3 +312,96 @@ impl Handler for GameManager { } } } + +/// Process the contents of the matchmaking queue against +/// a game link +#[derive(Message)] +pub struct ProcessQueueMessage { + pub link: Link, + pub game_id: GameID, +} + +impl Handler for GameManager { + type Response = Fr; + + fn handle( + &mut self, + msg: ProcessQueueMessage, + _ctx: &mut ServiceContext, + ) -> Self::Response { + let queue_handle = self.queue.clone(); + + Fr::new(Box::pin(async move { + let mut queue = queue_handle.write().await; + let queue = queue.deref_mut(); + if queue.is_empty() { + return; + } + + let link = msg.link; + + while let Some(entry) = queue.front() { + let join_state = match link + .send(CheckJoinableMessage { + rule_set: Some(entry.rule_set.clone()), + }) + .await + { + Ok(value) => value, + // Game is no longer available stop checking + Err(_) => break, + }; + + // TODO: If player has been in queue long enough create + // a game matching their specifics + + match join_state { + GameJoinableState::Joinable => { + let entry = queue + .pop_front() + .expect("Expecting matchmaking entry but nothing was present"); + + debug!( + "Found player from queue adding them to the game (GID: {})", + msg.game_id + ); + let time = SystemTime::now(); + let elapsed = time.duration_since(entry.started); + if let Ok(elapsed) = elapsed { + debug!("Matchmaking time elapsed: {}s", elapsed.as_secs()) + } + + let msid = entry.player.player.id; + + // Send the async update (TODO: Do this at intervals) + entry.player.link.push(Packet::notify( + game_manager::COMPONENT, + game_manager::MATCHMAKING_ASYNC_STATUS, + AsyncMatchmakingStatus { player_id: msid }, + )); + + // Add the player to the game + if link + .do_send(AddPlayerMessage { + player: entry.player, + context: GameSetupContext::Matchmaking(msid), + }) + .is_err() + { + break; + } + } + GameJoinableState::Full => { + // If the game is not joinable push the entry back to the + // front of the queue and early return + break; + } + GameJoinableState::NotMatch => { + // TODO: Check started time and timeout + // player if they've been waiting too long + } + } + } + })) + } +} diff --git a/src/services/game/mod.rs b/src/services/game/mod.rs index 1387de64..ff515739 100644 --- a/src/services/game/mod.rs +++ b/src/services/game/mod.rs @@ -1,31 +1,27 @@ +use self::{manager::GameManager, rules::RuleSet}; use crate::{ database::entities::Player, - services::{ - game::manager::RemoveGameMessage, - matchmaking::{rules::RuleSet, CheckGameMessage}, + services::game::manager::{ProcessQueueMessage, RemoveGameMessage}, + session::{ + packet::Packet, router::RawBlaze, DetailsMessage, InformSessions, PushExt, Session, + SetGameMessage, }, - session::{DetailsMessage, InformSessions, PushExt, Session, SetGameMessage}, - state::App, utils::{ - components::{Components, GameManager, UserSessions}, + components::{game_manager, user_sessions}, models::NetData, types::{GameID, PlayerID}, }, }; -use blaze_pk::{ - codec::Encodable, - packet::{Packet, PacketBody}, - types::TdfMap, - writer::TdfWriter, -}; use interlink::prelude::*; use log::debug; use models::*; use serde::Serialize; use std::sync::Arc; +use tdf::{ObjectId, TdfMap, TdfSerialize, TdfSerializer}; pub mod manager; pub mod models; +pub mod rules; /// Game service running within the server pub struct Game { @@ -39,14 +35,15 @@ pub struct Game { pub attributes: AttrMap, /// The list of players in this game pub players: Vec, + /// Services access + pub game_manager: Link, } impl Service for Game { fn stopping(&mut self) { debug!("Game is stopping (GID: {})", self.id); // Remove the stopping game - let services = App::services(); - let _ = services + let _ = self .game_manager .do_send(RemoveGameMessage { game_id: self.id }); } @@ -58,13 +55,19 @@ impl Game { /// `id` The unique ID for the game /// `attributes` The initial game attributes /// `setting` The initial game setting value - pub fn start(id: GameID, attributes: AttrMap, setting: GameSettings) -> Link { + pub fn start( + id: GameID, + attributes: AttrMap, + setting: GameSettings, + game_manager: Link, + ) -> Link { let this = Game { id, state: GameState::Initializing, setting, attributes, players: Vec::with_capacity(4), + game_manager, }; this.start() @@ -148,22 +151,22 @@ impl GamePlayer { } } - pub fn encode(&self, game_id: GameID, slot: usize, writer: &mut TdfWriter) { - writer.tag_empty_blob(b"BLOB"); - writer.tag_u8(b"EXID", 0); - writer.tag_u32(b"GID", game_id); - writer.tag_u32(b"LOC", 0x64654445); - writer.tag_str(b"NAME", &self.player.display_name); - writer.tag_u32(b"PID", self.player.id); - self.net.tag_groups(b"PNET", writer); - writer.tag_usize(b"SID", slot); - writer.tag_u8(b"SLOT", 0); - writer.tag_value(b"STAT", &self.state); - writer.tag_u16(b"TIDX", 0xffff); - writer.tag_u8(b"TIME", 0); /* Unix timestamp in millseconds */ - writer.tag_triple(b"UGID", (0, 0, 0)); - writer.tag_u32(b"UID", self.player.id); - writer.tag_group_end(); + pub fn encode(&self, game_id: GameID, slot: usize, w: &mut S) { + w.tag_blob_empty(b"BLOB"); + w.tag_u8(b"EXID", 0); + w.tag_owned(b"GID", game_id); + w.tag_u32(b"LOC", 0x64654445); + w.tag_str(b"NAME", &self.player.display_name); + w.tag_u32(b"PID", self.player.id); + w.tag_ref(b"PNET", &self.net.addr); + w.tag_owned(b"SID", slot); + w.tag_u8(b"SLOT", 0); + w.tag_ref(b"STAT", &self.state); + w.tag_u16(b"TIDX", 0xffff); + w.tag_u8(b"TIME", 0); /* Unix timestamp in millseconds */ + w.tag_alt(b"UGID", ObjectId::new_raw(0, 0, 0)); + w.tag_u32(b"UID", self.player.id); + w.tag_group_end(); } } @@ -204,7 +207,8 @@ impl Handler for Game { if is_other { // Notify other players of the joined player self.notify_all( - Components::GameManager(GameManager::PlayerJoining), + game_manager::COMPONENT, + game_manager::PLAYER_JOINING, PlayerJoining { slot, player, @@ -267,7 +271,8 @@ impl Handler for Game { debug!("Updating game setting (Value: {:?})", &setting); self.setting = setting; self.notify_all( - Components::GameManager(GameManager::GameSettingsChange), + game_manager::COMPONENT, + game_manager::GAME_SETTINGS_CHANGE, SettingChange { id: self.id, setting, @@ -292,19 +297,20 @@ impl Handler for Game { debug!("Updating game attributes"); let packet = Packet::notify( - Components::GameManager(GameManager::GameAttribChange), + game_manager::COMPONENT, + game_manager::GAME_ATTRIB_CHANGE, AttributesChange { id: self.id, attributes: &attributes, }, ); - self.attributes.extend(attributes); + + self.attributes.insert_presorted(attributes.into_inner()); self.push_all(&packet); // Don't update matchmaking for full games if self.players.len() < Self::MAX_PLAYERS { - let services = App::services(); - let _ = services.matchmaking.do_send(CheckGameMessage { + let _ = self.game_manager.do_send(ProcessQueueMessage { link: ctx.link(), game_id: self.id, }); @@ -363,13 +369,15 @@ impl Handler for Game { // Notify players of the player state change self.notify_all( - Components::GameManager(GameManager::GamePlayerStateChange), + game_manager::COMPONENT, + game_manager::GAME_PLAYER_STATE_CHANGE, state_change, ); // Notify players of the completed connection self.notify_all( - Components::GameManager(GameManager::PlayerJoinCompleted), + game_manager::COMPONENT, + game_manager::PLAYER_JOIN_COMPLETED, JoinComplete { game_id: self.id, player_id, @@ -516,7 +524,7 @@ impl Handler for Game { /// Message for getting an encoded packet body of the game data #[derive(Message)] -#[msg(rtype = "PacketBody")] +#[msg(rtype = "RawBlaze")] pub struct GetGameDataMessage; /// Handler for getting an encoded packet body of the game data @@ -529,7 +537,7 @@ impl Handler for Game { _ctx: &mut ServiceContext, ) -> Self::Response { let data = GetGameDetails { game: self }; - let data: PacketBody = data.into(); + let data: RawBlaze = data.into(); Mr(data) } } @@ -555,15 +563,16 @@ impl Game { /// /// `component` The packet component /// `contents` The packet contents - fn notify_all(&self, component: Components, contents: C) { - let packet = Packet::notify(component, contents); + fn notify_all(&self, component: u16, command: u16, contents: C) { + let packet = Packet::notify(component, command, contents); self.push_all(&packet); } /// Notifies all players of the current game state fn notify_state(&self) { self.notify_all( - Components::GameManager(GameManager::GameStateChange), + game_manager::COMPONENT, + game_manager::GAME_STATE_CHANGE, StateChange { id: self.id, state: self.state, @@ -597,7 +606,8 @@ impl Game { /// `slot` The slot the player is joining into fn notify_game_setup(&self, player: &GamePlayer, context: GameSetupContext) { let packet = Packet::notify( - Components::GameManager(GameManager::GameSetup), + game_manager::COMPONENT, + game_manager::GAME_SETUP, GameDetails { game: self, context, @@ -619,7 +629,8 @@ impl Game { }; self.notify_all( - Components::GameManager(GameManager::AdminListChange), + game_manager::COMPONENT, + game_manager::ADMIN_LIST_CHANGE, AdminListChange { game_id: self.id, player_id: target, @@ -636,8 +647,10 @@ impl Game { /// `player_id` The player ID of the removed player fn notify_player_removed(&self, player: &GamePlayer, reason: RemoveReason) { let packet = Packet::notify( - Components::GameManager(GameManager::PlayerRemoved), + game_manager::COMPONENT, + game_manager::PLAYER_REMOVED, PlayerRemoved { + cntx: 0, game_id: self.id, player_id: player.player.id, reason, @@ -656,7 +669,8 @@ impl Game { /// `player_id` The player id of the session to update fn notify_fetch_data(&self, player: &GamePlayer) { self.notify_all( - Components::UserSessions(UserSessions::FetchExtendedData), + user_sessions::COMPONENT, + user_sessions::FETCH_EXTENDED_DATA, FetchExtendedData { player_id: player.player.id, }, @@ -664,7 +678,8 @@ impl Game { for other_player in &self.players { let packet = Packet::notify( - Components::UserSessions(UserSessions::FetchExtendedData), + user_sessions::COMPONENT, + user_sessions::FETCH_EXTENDED_DATA, FetchExtendedData { player_id: other_player.player.id, }, @@ -690,10 +705,13 @@ impl Game { self.state = GameState::Migrating; self.notify_state(); self.notify_all( - Components::GameManager(GameManager::HostMigrationStart), + game_manager::COMPONENT, + game_manager::HOST_MIGRATION_START, HostMigrateStart { game_id: self.id, host_id: new_host.player.id, + pmig: 2, + slot: 0, }, ); @@ -701,7 +719,8 @@ impl Game { self.state = GameState::InGame; self.notify_state(); self.notify_all( - Components::GameManager(GameManager::HostMigrationFinished), + game_manager::COMPONENT, + game_manager::HOST_MIGRATION_FINISHED, HostMigrateFinished { game_id: self.id }, ); diff --git a/src/services/game/models.rs b/src/services/game/models.rs index cd80c6b4..62b53165 100644 --- a/src/services/game/models.rs +++ b/src/services/game/models.rs @@ -1,25 +1,24 @@ use super::{AttrMap, Game, GamePlayer}; use crate::utils::{ - components::{Components, GameManager}, + models::NetworkAddress, types::{GameID, GameSlot, PlayerID}, }; use bitflags::bitflags; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeResult, - packet::Packet, - reader::TdfReader, - tag::TdfType, - value_type, - writer::TdfWriter, -}; + use serde::Serialize; +use tdf::{ + TdfDeserialize, TdfDeserializeOwned, TdfSerialize, TdfSerializeOwned, TdfType, TdfTyped, +}; /// Different states the game can be in -#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive( + Default, Debug, Serialize, Clone, Copy, PartialEq, Eq, TdfSerialize, TdfDeserialize, TdfTyped, +)] #[repr(u8)] pub enum GameState { NewState = 0x0, + #[tdf(default)] + #[default] Initializing = 0x1, Virtual = 0x2, PreGame = 0x82, @@ -31,43 +30,6 @@ pub enum GameState { ReplaySetup = 0x8, } -impl GameState { - /// Gets the state from the provided value - /// - /// `value` The value to get the state of - pub fn from_value(value: u8) -> Self { - match value { - 0x0 => Self::NewState, - 0x1 => Self::Initializing, - 0x2 => Self::Virtual, - 0x82 => Self::PreGame, - 0x83 => Self::InGame, - 0x4 => Self::PostGame, - 0x5 => Self::Migrating, - 0x6 => Self::Destructing, - 0x7 => Self::Resetable, - 0x8 => Self::ReplaySetup, - // Default to initializing state - _ => Self::Initializing, - } - } -} - -impl Encodable for GameState { - fn encode(&self, writer: &mut TdfWriter) { - writer.write_u8((*self) as u8); - } -} - -impl Decodable for GameState { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let value = reader.read_u8()?; - Ok(Self::from_value(value)) - } -} - -value_type!(GameState, TdfType::VarInt); - bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GameSettings: u16 { @@ -91,24 +53,37 @@ bitflags! { } } -impl Encodable for GameSettings { - fn encode(&self, output: &mut TdfWriter) { - output.write_u16(self.bits()) +impl From for u16 { + fn from(value: GameSettings) -> Self { + value.bits() + } +} + +impl TdfSerialize for GameSettings { + fn serialize(&self, w: &mut S) { + ::serialize_owned(self.bits(), w) } } -impl Decodable for GameSettings { - fn decode(reader: &mut TdfReader) -> DecodeResult { - Ok(GameSettings::from_bits_retain(reader.read_u16()?)) +impl TdfDeserializeOwned for GameSettings { + fn deserialize_owned(r: &mut tdf::TdfDeserializer<'_>) -> tdf::DecodeResult { + let value = u16::deserialize_owned(r)?; + Ok(GameSettings::from_bits_retain(value)) } } -value_type!(GameSettings, TdfType::VarInt); +impl TdfTyped for GameSettings { + const TYPE: TdfType = TdfType::VarInt; +} -#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive( + Default, Debug, Serialize, Clone, Copy, PartialEq, Eq, TdfDeserialize, TdfSerialize, TdfTyped, +)] #[repr(u8)] pub enum PlayerState { /// Link between the mesh points is not connected + #[default] + #[tdf(default)] Reserved = 0x0, Queued = 0x1, /// Link is being formed between two mesh points @@ -119,79 +94,40 @@ pub enum PlayerState { ActiveKickPending = 0x5, } -impl PlayerState { - /// Gets the mesh state from the provided value - /// - /// `value` The value of the mesh state - pub fn from_value(value: u8) -> Self { - match value { - 0x0 => Self::Reserved, - 0x1 => Self::Queued, - 0x2 => Self::ActiveConnecting, - 0x3 => Self::ActiveMigrating, - 0x4 => Self::ActiveConnected, - 0x5 => Self::ActiveKickPending, - _ => Self::Reserved, - } - } -} - -impl Encodable for PlayerState { - fn encode(&self, output: &mut TdfWriter) { - output.write_u8((*self) as u8) - } -} - -impl Decodable for PlayerState { - fn decode(reader: &mut TdfReader) -> DecodeResult { - Ok(PlayerState::from_value(reader.read_u8()?)) - } -} - -value_type!(PlayerState, TdfType::VarInt); - /// Message for a game state changing +#[derive(TdfSerialize)] pub struct StateChange { /// The ID of the game + #[tdf(tag = "GID")] pub id: GameID, /// The game state + #[tdf(tag = "GSTA")] pub state: GameState, } -impl Encodable for StateChange { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.id); - writer.tag_value(b"GSTA", &self.state); - } -} - /// Message for a game setting changing +#[derive(TdfSerialize)] pub struct SettingChange { /// The game setting + #[tdf(tag = "ATTR", into = u16)] pub setting: GameSettings, /// The ID of the game + #[tdf(tag = "GID")] pub id: GameID, } -impl Encodable for SettingChange { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u16(b"ATTR", self.setting.bits()); - writer.tag_u32(b"GID", self.id); - } -} - /// Packet for game attribute changes pub struct AttributesChange<'a> { - /// The id of the game the attributes have changed for - pub id: GameID, /// Borrowed game attributes map pub attributes: &'a AttrMap, + /// The id of the game the attributes have changed for + pub id: GameID, } -impl Encodable for AttributesChange<'_> { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"ATTR", self.attributes); - writer.tag_u32(b"GID", self.id); +impl TdfSerialize for AttributesChange<'_> { + fn serialize(&self, w: &mut S) { + w.tag_ref(b"ATTR", self.attributes); + w.tag_owned(b"GID", self.id) } } @@ -205,19 +141,12 @@ pub struct PlayerJoining<'a> { pub player: &'a GamePlayer, } -impl Encodable for PlayerJoining<'_> { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.game_id); - - writer.tag_group(b"PDAT"); - self.player.encode(self.game_id, self.slot, writer); - } -} +impl TdfSerialize for PlayerJoining<'_> { + fn serialize(&self, w: &mut S) { + w.tag_u32(b"GID", self.game_id); -fn write_admin_list(writer: &mut TdfWriter, game: &Game) { - writer.tag_list_start(b"ADMN", TdfType::VarInt, game.players.len()); - for player in &game.players { - writer.write_u32(player.player.id); + w.tag_group(b"PDAT"); + self.player.encode(self.game_id, self.slot, w); } } @@ -247,8 +176,8 @@ pub struct GameDetails<'a> { pub context: GameSetupContext, } -impl Encodable for GameDetails<'_> { - fn encode(&self, writer: &mut TdfWriter) { +impl TdfSerialize for GameDetails<'_> { + fn serialize(&self, w: &mut S) { let game = self.game; let host_player = match game.players.first() { Some(value) => value, @@ -256,80 +185,77 @@ impl Encodable for GameDetails<'_> { }; // Game details - writer.group(b"GAME", |writer| { - write_admin_list(writer, game); - writer.tag_value(b"ATTR", &game.attributes); - { - writer.tag_list_start(b"CAP", TdfType::VarInt, 2); - writer.write_u8(4); - writer.write_u8(0); - } + w.group(b"GAME", |w| { + w.tag_list_iter_owned(b"ADMN", game.players.iter().map(|player| player.player.id)); + w.tag_ref(b"ATTR", &game.attributes); + + w.tag_list_slice(b"CAP", &[4u8, 0u8]); - writer.tag_u32(b"GID", game.id); - writer.tag_str(b"GNAM", &host_player.player.display_name); + w.tag_u32(b"GID", game.id); + w.tag_str(b"GNAM", &host_player.player.display_name); - writer.tag_u64(b"GPVH", 0x5a4f2b378b715c6); - writer.tag_u16(b"GSET", game.setting.bits()); - writer.tag_u64(b"GSID", 0x4000000a76b645); - writer.tag_value(b"GSTA", &game.state); + w.tag_u64(b"GPVH", 0x5a4f2b378b715c6); + w.tag_u16(b"GSET", game.setting.bits()); + w.tag_u64(b"GSID", 0x4000000a76b645); + w.tag_ref(b"GSTA", &game.state); - writer.tag_str_empty(b"GTYP"); + w.tag_str_empty(b"GTYP"); { - writer.tag_list_start(b"HNET", TdfType::Group, 1); - writer.write_byte(2); - if let Some(groups) = &host_player.net.groups { - groups.encode(writer); + w.tag_list_start(b"HNET", TdfType::Group, 1); + w.write_byte(2); + if let NetworkAddress::AddressPair(pair) = &host_player.net.addr { + TdfSerialize::serialize(pair, w) } } - writer.tag_u32(b"HSES", host_player.player.id); - writer.tag_zero(b"IGNO"); - writer.tag_u8(b"MCAP", 4); - writer.tag_value(b"NQOS", &host_player.net.qos); - writer.tag_zero(b"NRES"); - writer.tag_zero(b"NTOP"); - writer.tag_str_empty(b"PGID"); - writer.tag_empty_blob(b"PGSR"); - - writer.group(b"PHST", |writer| { - writer.tag_u32(b"HPID", host_player.player.id); - writer.tag_zero(b"HSLT"); + w.tag_u32(b"HSES", host_player.player.id); + w.tag_zero(b"IGNO"); + w.tag_u8(b"MCAP", 4); + w.tag_ref(b"NQOS", &host_player.net.qos); + w.tag_zero(b"NRES"); + w.tag_zero(b"NTOP"); + w.tag_str_empty(b"PGID"); + w.tag_blob_empty(b"PGSR"); + + w.group(b"PHST", |w| { + w.tag_u32(b"HPID", host_player.player.id); + w.tag_zero(b"HSLT"); }); - writer.tag_u8(b"PRES", 0x1); - writer.tag_str_empty(b"PSAS"); - writer.tag_u8(b"QCAP", 0x0); - writer.tag_u32(b"SEED", 0x4cbc8585); - writer.tag_u8(b"TCAP", 0x0); + w.tag_u8(b"PRES", 0x1); + w.tag_str_empty(b"PSAS"); + w.tag_u8(b"QCAP", 0x0); + w.tag_u32(b"SEED", 0x4cbc8585); + w.tag_u8(b"TCAP", 0x0); - writer.group(b"THST", |writer| { - writer.tag_u32(b"HPID", host_player.player.id); - writer.tag_u8(b"HSLT", 0x0); + w.group(b"THST", |w| { + w.tag_u32(b"HPID", host_player.player.id); + w.tag_u8(b"HSLT", 0x0); }); - writer.tag_str(b"UUID", "286a2373-3e6e-46b9-8294-3ef05e479503"); - writer.tag_u8(b"VOIP", 0x2); - writer.tag_str(b"VSTR", VSTR); - writer.tag_empty_blob(b"XNNC"); - writer.tag_empty_blob(b"XSES"); + w.tag_str(b"UUID", "286a2373-3e6e-46b9-8294-3ef05e479503"); + w.tag_u8(b"VOIP", 0x2); + w.tag_str(b"VSTR", VSTR); + w.tag_blob_empty(b"XNNC"); + w.tag_blob_empty(b"XSES"); }); // Player list - writer.tag_list_start(b"PROS", TdfType::Group, game.players.len()); + w.tag_list_start(b"PROS", TdfType::Group, game.players.len()); for (slot, player) in game.players.iter().enumerate() { - player.encode(game.id, slot, writer); + player.encode(game.id, slot, w); } match &self.context { GameSetupContext::Dataless(context) => { - writer.tag_union_start(b"REAS", 0x0); - writer.group(b"VALU", |writer| { + w.tag_union_start(b"REAS", 0x0); + w.group(b"VALU", |writer| { writer.tag_u8(b"DCTX", (*context) as u8); }); } GameSetupContext::Matchmaking(id) => { - writer.tag_union_start(b"REAS", 0x3); - writer.group(b"VALU", |writer| { + w.tag_union_start(b"REAS", 0x3); + w.group(b"VALU", |writer| { const FIT: u16 = 21600; writer.tag_u16(b"FIT", FIT); @@ -355,113 +281,101 @@ pub struct GetGameDetails<'a> { pub game: &'a Game, } -impl Encodable for GetGameDetails<'_> { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_list_start(b"GDAT", TdfType::Group, 1); +impl TdfSerialize for GetGameDetails<'_> { + fn serialize(&self, w: &mut S) { let game = self.game; let host_player = match game.players.first() { Some(value) => value, None => return, }; - write_admin_list(writer, game); - writer.tag_value(b"ATTR", &game.attributes); - { - writer.tag_list_start(b"CAP", TdfType::VarInt, 2); - writer.write_u8(4); - writer.write_u8(0); - } - writer.tag_u32(b"GID", game.id); - writer.tag_str(b"GNAM", &host_player.player.display_name); - writer.tag_u16(b"GSET", game.setting.bits()); - writer.tag_value(b"GSTA", &game.state); - { - writer.tag_list_start(b"HNET", TdfType::Group, 1); - writer.write_byte(2); - if let Some(groups) = &host_player.net.groups { - groups.encode(writer); - } - } - writer.tag_u32(b"HOST", host_player.player.id); - writer.tag_zero(b"NTOP"); - - { - writer.tag_list_start(b"PCNT", TdfType::VarInt, 2); - writer.write_u8(1); - writer.write_u8(0); - } + w.tag_list_start(b"GDAT", TdfType::Group, 1); + w.group_body(|w| { + w.tag_list_iter_owned(b"ADMN", game.players.iter().map(|player| player.player.id)); + w.tag_ref(b"ATTR", &game.attributes); + w.tag_list_slice(b"CAP", &[4u8, 0u8]); - writer.tag_u8(b"PRES", 0x2); - writer.tag_str(b"PSAS", "ea-sjc"); - writer.tag_str_empty(b"PSID"); - writer.tag_zero(b"QCAP"); - writer.tag_zero(b"QCNT"); - writer.tag_zero(b"SID"); - writer.tag_zero(b"TCAP"); - writer.tag_u8(b"VOIP", 0x2); - writer.tag_str(b"VSTR", VSTR); - writer.tag_group_end(); + w.tag_u32(b"GID", game.id); + w.tag_str(b"GNAM", &host_player.player.display_name); + w.tag_u16(b"GSET", game.setting.bits()); + w.tag_ref(b"GSTA", &game.state); + { + w.tag_list_start(b"HNET", TdfType::Group, 1); + w.write_byte(2); + if let NetworkAddress::AddressPair(pair) = &host_player.net.addr { + TdfSerialize::serialize(pair, w) + } + } + w.tag_u32(b"HOST", host_player.player.id); + w.tag_zero(b"NTOP"); + + w.tag_list_slice(b"PCNT", &[1u8, 0u8]); + + w.tag_u8(b"PRES", 0x2); + w.tag_str(b"PSAS", "ea-sjc"); + w.tag_str_empty(b"PSID"); + w.tag_zero(b"QCAP"); + w.tag_zero(b"QCNT"); + w.tag_zero(b"SID"); + w.tag_zero(b"TCAP"); + w.tag_u8(b"VOIP", 0x2); + w.tag_str(b"VSTR", VSTR); + }); } } +#[derive(TdfSerialize)] pub struct PlayerStateChange { + #[tdf(tag = "GID")] pub gid: GameID, + #[tdf(tag = "PID")] pub pid: PlayerID, + #[tdf(tag = "STAT")] pub state: PlayerState, } -impl Encodable for PlayerStateChange { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.gid); - writer.tag_u32(b"PID", self.pid); - writer.tag_value(b"STAT", &self.state); - } -} - +#[derive(TdfSerialize)] pub struct JoinComplete { + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "PID")] pub player_id: PlayerID, } -impl Encodable for JoinComplete { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.game_id); - writer.tag_u32(b"PID", self.player_id); - } -} - +#[derive(TdfSerialize)] pub struct AdminListChange { + #[tdf(tag = "ALST")] pub player_id: PlayerID, + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "OPER")] pub operation: AdminListOperation, + #[tdf(tag = "UID")] pub host_id: PlayerID, } /// Different operations that can be performed on /// the admin list -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, TdfSerialize, TdfTyped)] #[repr(u8)] pub enum AdminListOperation { Add = 0, Remove = 1, } -impl Encodable for AdminListChange { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"ALST", self.player_id); - writer.tag_u32(b"GID", self.game_id); - writer.tag_u8(b"OPER", self.operation as u8); - writer.tag_u32(b"UID", self.host_id); - } -} - +#[derive(TdfSerialize)] pub struct PlayerRemoved { + #[tdf(tag = "CNTX")] + pub cntx: u8, + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "PID")] pub player_id: PlayerID, + #[tdf(tag = "REAS")] pub reason: RemoveReason, } -#[derive(Debug, Clone, Copy)] +#[derive(Default, Debug, Clone, Copy, TdfSerialize, TdfDeserialize, TdfTyped)] #[repr(u8)] pub enum RemoveReason { /// Hit timeout while joining @@ -475,6 +389,8 @@ pub enum RemoveReason { GameDestroyed = 0x4, GameEnded = 0x5, /// Generic player left the game reason + #[tdf(default)] + #[default] PlayerLeft = 0x6, GroupLeft = 0x7, /// Player kicked @@ -487,104 +403,30 @@ pub enum RemoveReason { HostEjected = 0xC, } -impl RemoveReason { - pub fn from_value(value: u8) -> Self { - match value { - 0x0 => Self::JoinTimeout, - 0x1 => Self::PlayerConnectionLost, - 0x2 => Self::ServerConnectionLost, - 0x3 => Self::MigrationFailed, - 0x4 => Self::GameDestroyed, - 0x5 => Self::GameEnded, - 0x6 => Self::PlayerLeft, - 0x7 => Self::GroupLeft, - 0x8 => Self::PlayerKicked, - 0x9 => Self::PlayerKickedWithBan, - 0xA => Self::PlayerJoinFromQueueFailed, - 0xB => Self::PlayerReservationTimeout, - 0xC => Self::HostEjected, - // Default to generic reason for unknown - _ => Self::PlayerLeft, - } - } -} - -impl Encodable for RemoveReason { - fn encode(&self, writer: &mut TdfWriter) { - writer.write_u8((*self) as u8); - } -} - -impl Decodable for RemoveReason { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let value: u8 = reader.read_u8()?; - Ok(Self::from_value(value)) - } -} - -value_type!(RemoveReason, TdfType::VarInt); - -impl Encodable for PlayerRemoved { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u8(b"CNTX", 0); - writer.tag_u32(b"GID", self.game_id); - writer.tag_u32(b"PID", self.player_id); - writer.tag_value(b"REAS", &self.reason); - } -} - +#[derive(TdfSerialize)] pub struct FetchExtendedData { + #[tdf(tag = "BUID")] pub player_id: PlayerID, } -impl Encodable for FetchExtendedData { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"BUID", self.player_id); - } -} - +#[derive(TdfSerialize)] pub struct HostMigrateStart { + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "HOST")] pub host_id: PlayerID, + #[tdf(tag = "PMIG")] + pub pmig: u32, + #[tdf(tag = "SLOT")] + pub slot: u8, } -impl Encodable for HostMigrateStart { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.game_id); - writer.tag_u32(b"HOST", self.host_id); - writer.tag_u8(b"PMIG", 0x2); - writer.tag_u8(b"SLOT", 0x0); - } -} - -impl From for Packet { - fn from(value: HostMigrateStart) -> Self { - Packet::notify( - Components::GameManager(GameManager::HostMigrationStart), - value, - ) - } -} - +#[derive(TdfSerialize)] pub struct HostMigrateFinished { + #[tdf(tag = "GID")] pub game_id: GameID, } -impl Encodable for HostMigrateFinished { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.game_id) - } -} - -impl From for Packet { - fn from(value: HostMigrateFinished) -> Self { - Packet::notify( - Components::GameManager(GameManager::HostMigrationFinished), - value, - ) - } -} - /// /// # Example /// ``` @@ -686,103 +528,102 @@ pub struct AsyncMatchmakingStatus { pub player_id: PlayerID, } -impl Encodable for AsyncMatchmakingStatus { - fn encode(&self, writer: &mut TdfWriter) { - { - writer.tag_list_start(b"ASIL", TdfType::Group, 1); +impl TdfSerialize for AsyncMatchmakingStatus { + fn serialize(&self, w: &mut S) { + w.tag_list_start(b"ASIL", TdfType::Group, 1); + w.group_body(|w| { // Create game status - writer.group(b"CGS", |writer| { + w.group(b"CGS", |w| { // Evaluate status // PlayerCountSufficient = 1, // AcceptableHostFound = 2, // TeamSizesSufficient = 4 - writer.tag_u8(b"EVST", 2 | 4); + w.tag_u8(b"EVST", 2 | 4); // Number of matchmaking sessions - writer.tag_u8(b"MMSN", 1); + w.tag_u8(b"MMSN", 1); // Number of matched players - writer.tag_u8(b"NOMP", 0); + w.tag_u8(b"NOMP", 0); }); // Custom async status - writer.group(b"CUST", |_| {}); + w.tag_group_empty(b"CUST"); // DNF rule status - writer.group(b"DNFS", |writer| { + w.group(b"DNFS", |w| { // My DNF value - writer.tag_zero(b"MDNF"); + w.tag_zero(b"MDNF"); // Max DNF value - writer.tag_zero(b"XDNF"); + w.tag_zero(b"XDNF"); }); // Find game status - writer.group(b"FGS", |writer| { + w.group(b"FGS", |w| { // Number of games - writer.tag_zero(b"GNUM"); + w.tag_zero(b"GNUM"); }); // Geo location rule status - writer.group(b"GEOS", |writer| { + w.group(b"GEOS", |w| { // Max distance - writer.tag_zero(b"DIST"); + w.tag_zero(b"DIST"); }); // Generic rule status dictionary (TODO: RULES HERE) - writer.tag_map_start(b"GRDA", TdfType::String, TdfType::Group, 0); + w.tag_map_start(b"GRDA", TdfType::String, TdfType::Group, 0); // Game size rule status - writer.group(b"GSRD", |writer| { + w.group(b"GSRD", |w| { // Max player count accepted - writer.tag_u8(b"PMAX", 4); + w.tag_u8(b"PMAX", 4); // Min player count accepted - writer.tag_u8(b"PMIN", 2); + w.tag_u8(b"PMIN", 2); }); // Host balance rule status - writer.group(b"HBRD", |writer| { + w.group(b"HBRD", |w| { // Host balance values // HOSTS_STRICTLY_BALANCED = 0, // HOSTS_BALANCED = 1, // HOSTS_UNBALANCED = 2, - writer.tag_u8(b"BVAL", 1); + w.tag_u8(b"BVAL", 1); }); // Host viability rule status - writer.group(b"HVRD", |writer| { + w.group(b"HVRD", |w| { // Host viability values // CONNECTION_ASSURED = 0, // CONNECTION_LIKELY = 1, // CONNECTION_FEASIBLE = 2, // CONNECTION_UNLIKELY = 3, - writer.tag_zero(b"VVAL"); + w.tag_zero(b"VVAL"); }); // Ping site rule status - writer.group(b"PSRS", |_| {}); + w.group(b"PSRS", |_| {}); // Rank rule status - writer.group(b"RRDA", |writer| { + w.group(b"RRDA", |w| { // Matched rank flags - writer.tag_zero(b"RVAL"); + w.tag_zero(b"RVAL"); }); // Team size rule status - writer.group(b"TSRS", |writer| { + w.group(b"TSRS", |w| { // Max team size accepted - writer.tag_zero(b"TMAX"); + w.tag_zero(b"TMAX"); // Min team size accepted - writer.tag_zero(b"TMIN"); + w.tag_zero(b"TMIN"); }); // UED rule status - writer.tag_map_start(b"GRDA", TdfType::String, TdfType::Group, 0); + w.tag_map_empty(b"GRDA", TdfType::String, TdfType::Group); // Virtual game rule status - writer.group(b"VGRS", |writer| writer.tag_zero(b"VVAL")); - writer.tag_group_end(); - } + w.group(b"VGRS", |w| w.tag_zero(b"VVAL")); + }); - writer.tag_u32(b"MSID", self.player_id); - writer.tag_u32(b"USID", self.player_id); + w.tag_owned(b"MSID", self.player_id); + w.tag_owned(b"USID", self.player_id); } } diff --git a/src/services/matchmaking/rules.rs b/src/services/game/rules.rs similarity index 100% rename from src/services/matchmaking/rules.rs rename to src/services/game/rules.rs diff --git a/src/services/leaderboard/mod.rs b/src/services/leaderboard/mod.rs index 2d70269e..f6a97d21 100644 --- a/src/services/leaderboard/mod.rs +++ b/src/services/leaderboard/mod.rs @@ -5,7 +5,6 @@ use crate::{ entities::{Player, PlayerData}, DatabaseConnection, DbResult, }, - state::App, utils::parsing::{KitNameDeployed, PlayerClass}, }; use interlink::prelude::*; @@ -36,7 +35,7 @@ struct GroupState { /// of the specific leaderboard type #[derive(Message)] #[msg(rtype = "Arc")] -pub struct QueryMessage(pub LeaderboardType); +pub struct QueryMessage(pub LeaderboardType, pub DatabaseConnection); impl Handler for Leaderboard { type Response = Fr; @@ -69,7 +68,7 @@ impl Handler for Leaderboard { Fr::new(Box::pin(async move { // Compute new leaderboard values - let values = Self::compute(&ty).await; + let values = Self::compute(&ty, msg.1).await; let group = Arc::new(LeaderboardGroup::new(values)); // Store the group and respond to the request @@ -123,24 +122,22 @@ impl Leaderboard { /// on their value. /// /// `ty` The leaderboard type - async fn compute(ty: &LeaderboardType) -> Box<[LeaderboardEntry]> { + async fn compute(ty: &LeaderboardType, db: DatabaseConnection) -> Box<[LeaderboardEntry]> { let start_time = Instant::now(); // The amount of players to process in each database request const BATCH_COUNT: u64 = 20; - let db = App::database(); - let mut values: Vec = Vec::new(); let mut join_set = JoinSet::new(); let mut paginator = players::Entity::find() .order_by_asc(players::Column::Id) - .paginate(db, BATCH_COUNT); + .paginate(&db, BATCH_COUNT); // Function pointer to the computing function for the desired type - let fun: fn(&'static DatabaseConnection, Player) -> Lf = match ty { + let fun: fn(DatabaseConnection, Player) -> Lf = match ty { LeaderboardType::N7Rating => compute_n7_player, LeaderboardType::ChallengePoints => compute_cp_player, }; @@ -157,7 +154,7 @@ impl Leaderboard { // Add the futures for all the players for player in players { - join_set.spawn(fun(db, player)); + join_set.spawn(fun(db.clone(), player)); } // Await computed results @@ -191,12 +188,12 @@ type Lf = BoxFuture<'static, DbResult>; /// /// `db` The database connection /// `player` The player to rank -fn compute_n7_player(db: &'static DatabaseConnection, player: Player) -> Lf { +fn compute_n7_player(db: DatabaseConnection, player: Player) -> Lf { Box::pin(async move { let mut total_promotions: u32 = 0; let mut total_level: u32 = 0; - let data: Vec = PlayerData::all(db, player.id).await?; + let data: Vec = PlayerData::all(&db, player.id).await?; let mut classes: Vec = Vec::new(); let mut characters: Vec = Vec::new(); @@ -241,9 +238,9 @@ fn compute_n7_player(db: &'static DatabaseConnection, player: Player) -> Lf { /// /// `db` The database connection /// `player` The player to rank -fn compute_cp_player(db: &'static DatabaseConnection, player: Player) -> Lf { +fn compute_cp_player(db: DatabaseConnection, player: Player) -> Lf { Box::pin(async move { - let value = PlayerData::get_challenge_points(db, player.id) + let value = PlayerData::get_challenge_points(&db, player.id) .await .unwrap_or(0); Ok(LeaderboardEntry { diff --git a/src/services/leaderboard/models.rs b/src/services/leaderboard/models.rs index c7f59b45..89c66f91 100644 --- a/src/services/leaderboard/models.rs +++ b/src/services/leaderboard/models.rs @@ -6,7 +6,7 @@ use std::{ }; /// Structure for an entry in a leaderboard group -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct LeaderboardEntry { /// The ID of the player this entry is for pub player_id: PlayerID, diff --git a/src/services/matchmaking/mod.rs b/src/services/matchmaking/mod.rs deleted file mode 100644 index 724741ee..00000000 --- a/src/services/matchmaking/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -use crate::{ - services::game::{ - models::{AsyncMatchmakingStatus, GameSetupContext}, - AddPlayerMessage, CheckJoinableMessage, Game, GameJoinableState, GamePlayer, - }, - session::PushExt, - utils::{ - components::{Components, GameManager}, - types::{GameID, PlayerID}, - }, -}; -use blaze_pk::packet::Packet; -use interlink::prelude::*; -use log::debug; -use rules::RuleSet; -use std::{collections::VecDeque, sync::Arc, time::SystemTime}; - -pub mod rules; - -#[derive(Service)] -pub struct Matchmaking { - /// The queue of matchmaking entries - queue: VecDeque, -} - -impl Matchmaking { - /// Starts a new matchmaking service returning its link - pub fn start() -> Link { - let this = Matchmaking { - queue: Default::default(), - }; - this.start() - } -} - -/// Structure of a entry within the matchmaking queue -/// containing information about the queue item -struct QueueEntry { - /// The player this entry is for - player: GamePlayer, - /// The set of rules the game must match in - /// order for this player to be removed from - /// the queue and placed into a game - rule_set: Arc, - /// The system time of when the player was added - /// to the matchmaking queue - time: SystemTime, -} - -/// Message for handling checking the queue for those -/// who can join the game. This is sent when a game is -/// created or when its attributes are updated -#[derive(Message)] -pub struct CheckGameMessage { - /// The link to the game - pub link: Link, - /// The ID of the created game - pub game_id: GameID, -} - -impl Handler for Matchmaking { - type Response = Sfr; - - fn handle(&mut self, msg: CheckGameMessage, _ctx: &mut ServiceContext) -> Self::Response { - Sfr::new(move |service: &mut Matchmaking, _ctx| { - Box::pin(async move { - let link = msg.link; - let queue = &mut service.queue; - if queue.is_empty() { - return; - } - - let mut requeue = VecDeque::new(); - - while let Some(entry) = queue.pop_front() { - let join_state = match link - .send(CheckJoinableMessage { - rule_set: Some(entry.rule_set.clone()), - }) - .await - { - Ok(value) => value, - // Game is no longer available - Err(_) => { - requeue.push_back(entry); - break; - } - }; - - // TODO: If player has been in queue long enough create - // a game matching their specifics - - match join_state { - GameJoinableState::Joinable => { - debug!( - "Found player from queue adding them to the game (GID: {})", - msg.game_id - ); - let time = SystemTime::now(); - let elapsed = time.duration_since(entry.time); - if let Ok(elapsed) = elapsed { - debug!("Matchmaking time elapsed: {}s", elapsed.as_secs()) - } - - let msid = entry.player.player.id; - - // Send the async update (TODO: Do this at intervals) - entry.player.link.push(Packet::notify( - Components::GameManager(GameManager::MatchmakingAsyncStatus), - AsyncMatchmakingStatus { player_id: msid }, - )); - - // Add the player to the game - if link - .do_send(AddPlayerMessage { - player: entry.player, - context: GameSetupContext::Matchmaking(msid), - }) - .is_err() - { - break; - } - } - GameJoinableState::Full => { - // If the game is not joinable push the entry back to the - // front of the queue and early return - requeue.push_back(entry); - break; - } - GameJoinableState::NotMatch => { - // TODO: Check started time and timeout - // player if they've been waiting too long - requeue.push_back(entry); - } - } - } - - queue.append(&mut requeue) - }) - }) - } -} - -/// Message to add a new player to the matchmaking queue -#[derive(Message)] -pub struct QueuePlayerMessage { - /// The player to add to the queue - pub player: GamePlayer, - /// The rules for the player - pub rule_set: Arc, -} - -impl Handler for Matchmaking { - /// Empty response type - type Response = (); - - fn handle(&mut self, msg: QueuePlayerMessage, _ctx: &mut ServiceContext) { - let time = SystemTime::now(); - self.queue.push_back(QueueEntry { - player: msg.player, - rule_set: msg.rule_set, - time, - }); - } -} - -/// Message to remove a player from the matchmaking queue -#[derive(Message)] -pub struct RemoveQueueMessage { - /// The player ID of the player to remove - pub player_id: PlayerID, -} - -impl Handler for Matchmaking { - /// Empty response type - type Response = (); - - fn handle(&mut self, msg: RemoveQueueMessage, _ctx: &mut ServiceContext) { - self.queue - .retain(|value| value.player.player.id != msg.player_id); - } -} diff --git a/src/services/mod.rs b/src/services/mod.rs index 93d00ccb..3b905893 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,42 +1,4 @@ -use self::{ - game::manager::GameManager, leaderboard::Leaderboard, matchmaking::Matchmaking, - retriever::Retriever, sessions::AuthedSessions, tokens::Tokens, -}; -use crate::config::ServicesConfig; -use interlink::prelude::Link; -use tokio::join; - pub mod game; pub mod leaderboard; -pub mod matchmaking; pub mod retriever; pub mod sessions; -pub mod tokens; - -pub struct Services { - pub game_manager: Link, - pub matchmaking: Link, - pub leaderboard: Link, - pub retriever: Link, - pub sessions: Link, - pub tokens: Tokens, -} - -impl Services { - pub async fn init(config: ServicesConfig) -> Self { - let (retriever, tokens) = join!(Retriever::new(config.retriever), Tokens::new()); - let game_manager = GameManager::start(); - let matchmaking = Matchmaking::start(); - let leaderboard = Leaderboard::start(); - let sessions = AuthedSessions::start(); - - Self { - game_manager, - matchmaking, - leaderboard, - retriever, - sessions, - tokens, - } - } -} diff --git a/src/services/retriever/mod.rs b/src/services/retriever/mod.rs index 37a7b204..26f5dfe4 100644 --- a/src/services/retriever/mod.rs +++ b/src/services/retriever/mod.rs @@ -10,16 +10,13 @@ use std::{ use self::origin::OriginFlowService; use crate::{ config::RetrieverConfig, + session::packet::{Packet, PacketCodec, PacketDebug, PacketHeader, PacketType}, utils::{ - components::{Components, Redirector}, + components::redirector, models::{InstanceDetails, InstanceNet, Port}, }, }; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeError, - packet::{Packet, PacketCodec, PacketComponents, PacketDebug, PacketHeader, PacketType}, -}; + use blaze_ssl_async::{stream::BlazeStream, BlazeError}; use futures_util::{SinkExt, StreamExt}; use interlink::prelude::*; @@ -28,6 +25,7 @@ use models::InstanceRequest; use origin::OriginFlow; use reqwest; use serde::Deserialize; +use tdf::{DecodeError, TdfDeserialize, TdfSerialize}; use thiserror::Error; use tokio::io; use tokio_util::codec::Framed; @@ -124,6 +122,8 @@ pub enum InstanceError { Blaze(#[from] BlazeError), #[error("Failed to retrieve instance: {0}")] InstanceRequest(#[from] RetrieverError), + #[error("Server response missing address")] + MissingAddress, } impl OfficialInstance { @@ -167,13 +167,17 @@ impl OfficialInstance { // Request the server instance let instance: InstanceDetails = session .request( - Components::Redirector(Redirector::GetServerInstance), + redirector::COMPONENT, + redirector::GET_SERVER_INSTANCE, InstanceRequest, ) .await?; // Extract the host and port turning the host into a string - let InstanceNet { host, port } = instance.net; + let (host, port) = match instance.net { + InstanceNet::InstanceAddress(addr) => (addr.host, addr.port), + _ => return Err(InstanceError::MissingAddress), + }; let host: String = host.into(); debug!( @@ -243,7 +247,7 @@ impl Retriever { /// ip address of the gosredirector.ea.com host and then creates a /// connection to the redirector server and obtains the IP and Port /// of the Official server. - pub async fn new(config: RetrieverConfig) -> Link { + pub async fn start(config: RetrieverConfig) -> Link { let instance = if config.enabled { match OfficialInstance::obtain().await { Ok(value) => Some(value), @@ -313,24 +317,30 @@ impl OfficialSession { /// Writes a request packet and waits until the response packet is /// recieved returning the contents of that response packet. - pub async fn request( + pub async fn request( &mut self, - component: Components, + component: u16, + command: u16, contents: Req, - ) -> RetrieverResult { - let response = self.request_raw(component, contents).await?; - let contents = response.decode::()?; + ) -> RetrieverResult + where + Req: TdfSerialize, + for<'a> Res: TdfDeserialize<'a> + 'a, + { + let response = self.request_raw(component, command, contents).await?; + let contents = response.deserialize::()?; Ok(contents) } /// Writes a request packet and waits until the response packet is /// recieved returning the contents of that response packet. - pub async fn request_raw( + pub async fn request_raw( &mut self, - component: Components, + component: u16, + command: u16, contents: Req, ) -> RetrieverResult { - let request = Packet::request(self.id, component, contents); + let request = Packet::request(self.id, component, command, contents); debug_log_packet(&request, "Sending to Official"); let header = request.header; @@ -344,19 +354,23 @@ impl OfficialSession { /// Writes a request packet and waits until the response packet is /// recieved returning the contents of that response packet. The /// request will have no content - pub async fn request_empty( - &mut self, - component: Components, - ) -> RetrieverResult { - let response = self.request_empty_raw(component).await?; - let contents = response.decode::()?; + pub async fn request_empty(&mut self, component: u16, command: u16) -> RetrieverResult + where + for<'a> Res: TdfDeserialize<'a> + 'a, + { + let response = self.request_empty_raw(component, command).await?; + let contents = response.deserialize::()?; Ok(contents) } /// Writes a request packet and waits until the response packet is /// recieved returning the raw response packet - pub async fn request_empty_raw(&mut self, component: Components) -> RetrieverResult { - let request = Packet::request_empty(self.id, component); + pub async fn request_empty_raw( + &mut self, + component: u16, + command: u16, + ) -> RetrieverResult { + let request = Packet::request_empty(self.id, component, command); debug_log_packet(&request, "Sent to Official"); let header = request.header; self.stream.send(request).await?; @@ -397,10 +411,8 @@ fn debug_log_packet(packet: &Packet, action: &str) { if !log_enabled!(log::Level::Debug) { return; } - let component = Components::from_header(&packet.header); let debug = PacketDebug { packet, - component: component.as_ref(), minified: false, }; debug!("\n{}\n{:?}", action, debug); diff --git a/src/services/retriever/models.rs b/src/services/retriever/models.rs index 51cbc0f8..a2bb7388 100644 --- a/src/services/retriever/models.rs +++ b/src/services/retriever/models.rs @@ -1,10 +1,4 @@ -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeResult, - reader::TdfReader, - tag::TdfType, - writer::TdfWriter, -}; +use tdf::{TdfDeserializeOwned, TdfSerialize, TdfType}; /// Packet encoding for Redirector GetServerInstance packets /// this contains basic information about the client session. @@ -12,21 +6,21 @@ use blaze_pk::{ /// These details are extracted from an official game copy pub struct InstanceRequest; -impl Encodable for InstanceRequest { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_str(b"BSDK", "3.15.6.0"); - writer.tag_str(b"BTIM", "Dec 21 2012 12:47:10"); - writer.tag_str(b"CLNT", "MassEffect3-pc"); - writer.tag_u8(b"CLTP", 0); - writer.tag_str(b"CSKU", "134845"); - writer.tag_str(b"CVER", "05427.124"); - writer.tag_str(b"DSDK", "8.14.7.1"); - writer.tag_str(b"ENV", "prod"); - writer.tag_union_unset(b"FPID"); - writer.tag_u32(b"LOC", 0x656e4e5a); - writer.tag_str(b"NAME", "masseffect-3-pc"); - writer.tag_str(b"PLAT", "Windows"); - writer.tag_str(b"PROF", "standardSecure_v3"); +impl TdfSerialize for InstanceRequest { + fn serialize(&self, w: &mut S) { + w.tag_str(b"BSDK", "3.15.6.0"); + w.tag_str(b"BTIM", "Dec 21 2012 12:47:10"); + w.tag_str(b"CLNT", "MassEffect3-pc"); + w.tag_u8(b"CLTP", 0); + w.tag_str(b"CSKU", "134845"); + w.tag_str(b"CVER", "05427.124"); + w.tag_str(b"DSDK", "8.14.7.1"); + w.tag_str(b"ENV", "prod"); + w.tag_union_unset(b"FPID"); + w.tag_u32(b"LOC", 0x656e4e5a); + w.tag_str(b"NAME", "masseffect-3-pc"); + w.tag_str(b"PLAT", "Windows"); + w.tag_str(b"PROF", "standardSecure_v3"); } } @@ -40,13 +34,13 @@ pub struct OriginLoginResponse { pub display_name: String, } -impl Decodable for OriginLoginResponse { - fn decode(reader: &mut TdfReader) -> DecodeResult { - reader.until_tag(b"SESS", TdfType::Group)?; - let email: String = reader.tag(b"MAIL")?; +impl TdfDeserializeOwned for OriginLoginResponse { + fn deserialize_owned(r: &mut tdf::TdfDeserializer<'_>) -> tdf::DecodeResult { + r.until_tag(b"SESS", TdfType::Group)?; + let email: String = r.tag(b"MAIL")?; - reader.until_tag(b"PDTL", TdfType::Group)?; - let display_name: String = reader.tag(b"DSNM")?; + r.until_tag(b"PDTL", TdfType::Group)?; + let display_name: String = r.tag(b"DSNM")?; Ok(Self { email, display_name, diff --git a/src/services/retriever/origin.rs b/src/services/retriever/origin.rs index 6cf9e109..cabf8ad3 100644 --- a/src/services/retriever/origin.rs +++ b/src/services/retriever/origin.rs @@ -3,13 +3,14 @@ use super::{models::OriginLoginResponse, OfficialSession, RetrieverResult}; use crate::{ + config::RuntimeConfig, database::entities::{Player, PlayerData}, session::models::{auth::OriginLoginRequest, util::SettingsResponse}, - utils::components::{Authentication, Components, Util}, + utils::components::{authentication, util}, }; -use blaze_pk::types::TdfMap; use log::{debug, error, warn}; use sea_orm::{DatabaseConnection, DbErr}; +use tdf::TdfMap; use thiserror::Error; /// Service for providing origin flows from a retriever @@ -63,6 +64,7 @@ impl OriginFlow { &mut self, db: &DatabaseConnection, token: String, + config: &RuntimeConfig, ) -> Result { // Authenticate with the official servers let details = self @@ -75,7 +77,8 @@ impl OriginFlow { return Ok(player); } - let player: Player = Player::create(db, details.email, details.display_name, None).await?; + let player: Player = + Player::create(db, details.email, details.display_name, None, config).await?; // If data fetching is ena if self.data { @@ -99,8 +102,9 @@ impl OriginFlow { let value = self .session .request::( - Components::Authentication(Authentication::OriginLogin), - OriginLoginRequest { token }, + authentication::COMPONENT, + authentication::ORIGIN_LOGIN, + OriginLoginRequest { token, ty: 1 }, ) .await?; @@ -116,7 +120,7 @@ impl OriginFlow { async fn get_settings(&mut self) -> RetrieverResult> { let value = self .session - .request_empty::(Components::Util(Util::UserSettingsLoadAll)) + .request_empty::(util::COMPONENT, util::USER_SETTINGS_LOAD_ALL) .await?; Ok(value.settings) } diff --git a/src/services/sessions/mod.rs b/src/services/sessions/mod.rs index 59922aab..056abc44 100644 --- a/src/services/sessions/mod.rs +++ b/src/services/sessions/mod.rs @@ -2,24 +2,189 @@ //! authenticated sessions on the server use crate::{session::Session, utils::types::PlayerID}; +use argon2::password_hash::rand_core::{OsRng, RngCore}; +use base64ct::{Base64UrlUnpadded, Encoding}; use interlink::prelude::*; +use interlink::service::ServiceContext; +use log::error; +use ring::hmac::{self, Key, HMAC_SHA256}; use std::collections::HashMap; +use std::{ + path::Path, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; +use thiserror::Error; +use tokio::{ + fs::{write, File}, + io::{self, AsyncReadExt}, +}; -/// Service for storing links to authenticated sessions +/// Service for storing links to authenticated sessions and +/// functionality for authenticating sessions #[derive(Service)] -pub struct AuthedSessions { +pub struct Sessions { /// Map of the authenticated players to their session links values: HashMap>, + + /// HMAC key used for computing signatures + key: Key, } -impl AuthedSessions { +/// Message for creating a new authentication token for the provided +/// [PlayerID] +#[derive(Message)] +#[msg(rtype = "String")] +pub struct CreateTokenMessage(pub PlayerID); + +/// Message for verifying the provided token +#[derive(Message)] +#[msg(rtype = "Result")] +pub struct VerifyTokenMessage(pub String); + +impl Handler for Sessions { + type Response = Mr; + + fn handle( + &mut self, + msg: CreateTokenMessage, + _ctx: &mut ServiceContext, + ) -> Self::Response { + let id = msg.0; + + // Compute expiry timestamp + let exp = SystemTime::now() + .checked_add(Self::EXPIRY_TIME) + .expect("Expiry timestamp too far into the future") + .duration_since(UNIX_EPOCH) + .expect("Clock went backwards") + .as_secs(); + + // Create encoded token value + let mut data = [0u8; 12]; + data[..4].copy_from_slice(&id.to_be_bytes()); + data[4..].copy_from_slice(&exp.to_be_bytes()); + let data = &data; + + // Encode the message + let msg = Base64UrlUnpadded::encode_string(data); + + // Create a signature from the raw message bytes + let sig = hmac::sign(&self.key, data); + let sig = Base64UrlUnpadded::encode_string(sig.as_ref()); + + // Join the message and signature to create the token + let token = [msg, sig].join("."); + + Mr(token) + } +} + +impl Handler for Sessions { + type Response = Mr; + + fn handle( + &mut self, + msg: VerifyTokenMessage, + _ctx: &mut ServiceContext, + ) -> Self::Response { + Mr(self.verify(&msg.0)) + } +} + +impl Sessions { /// Starts a new service returning its link - pub fn start() -> Link { + pub async fn start() -> Link { + let key = Self::create_key().await; let this = Self { values: Default::default(), + key, }; this.start() } + + /// Expiry time for tokens + const EXPIRY_TIME: Duration = Duration::from_secs(60 * 60 * 24 * 30 /* 30 Days */); + + /// Creates a new instance of the tokens structure loading/creating + /// the secret bytes that are used for signing authentication tokens + pub async fn create_key() -> Key { + // Path to the file containing the server secret value + let secret_path = Path::new("data/secret.bin"); + + // The bytes of the secret + let mut secret = [0u8; 64]; + + // Attempt to load existing secret + if secret_path.exists() { + if let Err(err) = Self::read_secret(&mut secret, secret_path).await { + error!("Failed to read secrets file: {:?}", err); + } else { + return Key::new(HMAC_SHA256, &secret); + } + } + + // Generate random secret bytes + OsRng.fill_bytes(&mut secret); + + // Save the created secret + if let Err(err) = write(secret_path, &secret).await { + error!("Failed to write secrets file: {:?}", err); + } + + Key::new(HMAC_SHA256, &secret) + } + + /// Reads the secret from the secrets file into the provided buffer + /// returning whether the entire secret could be read + /// + /// `out` The buffer to read the secret to + async fn read_secret(out: &mut [u8], path: &Path) -> io::Result<()> { + let mut file = File::open(path).await?; + file.read_exact(out).await?; + Ok(()) + } + + fn verify(&self, token: &str) -> Result { + // Split the token parts + let (msg_raw, sig_raw) = match token.split_once('.') { + Some(value) => value, + None => return Err(VerifyError::Invalid), + }; + + // Decode the 12 byte token message + let mut msg = [0u8; 12]; + Base64UrlUnpadded::decode(msg_raw, &mut msg)?; + + // Decode 32byte signature (SHA256) + let mut sig = [0u8; 32]; + Base64UrlUnpadded::decode(sig_raw, &mut sig)?; + + // Verify the signature + if hmac::verify(&self.key, &msg, &sig).is_err() { + return Err(VerifyError::Invalid); + } + + // Extract ID and expiration from the msg bytes + let mut id = [0u8; 4]; + id.copy_from_slice(&msg[..4]); + let id = u32::from_be_bytes(id); + + let mut exp = [0u8; 8]; + exp.copy_from_slice(&msg[4..]); + let exp = u64::from_be_bytes(exp); + + // Ensure the timestamp is not expired + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Clock went backwards") + .as_secs(); + + if exp < now { + return Err(VerifyError::Expired); + } + + Ok(id) + } } /// Message for removing players from the authenticated @@ -50,7 +215,7 @@ pub struct LookupMessage { } /// Handle messages to add authenticated sessions -impl Handler for AuthedSessions { +impl Handler for Sessions { type Response = (); fn handle(&mut self, msg: AddMessage, _ctx: &mut ServiceContext) -> Self::Response { @@ -59,7 +224,7 @@ impl Handler for AuthedSessions { } /// Handle messages to remove authenticated sessions -impl Handler for AuthedSessions { +impl Handler for Sessions { type Response = (); fn handle(&mut self, msg: RemoveMessage, _ctx: &mut ServiceContext) -> Self::Response { @@ -68,7 +233,7 @@ impl Handler for AuthedSessions { } /// Handle messages to lookup authenticated sessions -impl Handler for AuthedSessions { +impl Handler for Sessions { type Response = Mr; fn handle(&mut self, msg: LookupMessage, _ctx: &mut ServiceContext) -> Self::Response { @@ -76,3 +241,20 @@ impl Handler for AuthedSessions { Mr(value) } } + +/// Errors that can occur while verifying a token +#[derive(Debug, Error)] +pub enum VerifyError { + /// The token is expired + #[error("Expired token")] + Expired, + /// The token is invalid + #[error("Invalid token")] + Invalid, +} + +impl From for VerifyError { + fn from(_: base64ct::Error) -> Self { + Self::Invalid + } +} diff --git a/src/services/tokens/mod.rs b/src/services/tokens/mod.rs deleted file mode 100644 index 8170bfb6..00000000 --- a/src/services/tokens/mod.rs +++ /dev/null @@ -1,197 +0,0 @@ -//! Authentication token provider and verification service. Makes use of -//! HS256 signed tokens which are 12 bytes 4 for the player ID and 8 for -//! the expiry date - -use argon2::password_hash::rand_core::{OsRng, RngCore}; -use base64ct::{Base64UrlUnpadded, Encoding}; -use log::error; -use ring::hmac::{self, Key, HMAC_SHA256}; -use sea_orm::DatabaseConnection; -use std::{ - path::Path, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; -use thiserror::Error; -use tokio::{ - fs::{write, File}, - io::{self, AsyncReadExt}, -}; - -use crate::{database::entities::Player, state::App, utils::types::PlayerID}; - -use super::Services; - -/// Token provider and verification service -pub struct Tokens { - /// HMAC key used for computing signatures - key: Key, -} - -impl Tokens { - /// Expiry time for tokens - const EXPIRY_TIME: Duration = Duration::from_secs(60 * 60 * 24 * 30 /* 30 Days */); - - /// Creates a new instance of the tokens structure loading/creating - /// the secret bytes that are used for signing authentication tokens - pub async fn new() -> Self { - // Path to the file containing the server secret value - let secret_path = Path::new("data/secret.bin"); - - // The bytes of the secret - let mut secret = [0u8; 64]; - - // Attempt to load existing secret - if secret_path.exists() { - if let Err(err) = Self::read_secret(&mut secret, secret_path).await { - error!("Failed to read secrets file: {:?}", err); - } else { - let key = Key::new(HMAC_SHA256, &secret); - - // Return the loaded secret key - return Self { key }; - } - } - - // Generate random secret bytes - OsRng.fill_bytes(&mut secret); - - // Save the created secret - if let Err(err) = write(secret_path, &secret).await { - error!("Failed to write secrets file: {:?}", err); - } - - let key = Key::new(HMAC_SHA256, &secret); - Self { key } - } - - /// Reads the secret from the secrets file into the provided buffer - /// returning whether the entire secret could be read - /// - /// `out` The buffer to read the secret to - async fn read_secret(out: &mut [u8], path: &Path) -> io::Result<()> { - let mut file = File::open(path).await?; - file.read_exact(out).await?; - Ok(()) - } - - /// Claim by directly obtaining the services reference. This - /// exists because everywhere claim is used its always using - /// a call to [`App::services`] before - pub fn service_claim(id: u32) -> String { - let services = App::services(); - services.tokens.claim(id) - } - - /// Creates a new claim using the provided claim value - /// - /// `id` The ID of the player to claim for - fn claim(&self, id: u32) -> String { - // Compute expiry timestamp - let exp = SystemTime::now() - .checked_add(Self::EXPIRY_TIME) - .expect("Expiry timestamp too far into the future") - .duration_since(UNIX_EPOCH) - .expect("Clock went backwards") - .as_secs(); - - // Create encoded token value - let mut data = [0u8; 12]; - data[..4].copy_from_slice(&id.to_be_bytes()); - data[4..].copy_from_slice(&exp.to_be_bytes()); - let data = &data; - - // Encode the message - let msg = Base64UrlUnpadded::encode_string(data); - - // Create a signature from the raw message bytes - let sig = hmac::sign(&self.key, data); - let sig = Base64UrlUnpadded::encode_string(sig.as_ref()); - - // Join the message and signature to create the token - [msg, sig].join(".") - } - - /// Verify by directly obtaining the services reference. This - /// exists because everywhere verify is used its always using - /// a call to [`App::services`] before - /// - /// Looks up the player that token verifies and treats missing - /// players as invalid tokens. - pub async fn service_verify( - db: &DatabaseConnection, - token: &str, - ) -> Result { - let services: &'static Services = App::services(); - let player_id: PlayerID = services.tokens.verify(token)?; - - Player::by_id(db, player_id) - .await - .map_err(|_| VerifyError::Server)? - .ok_or(VerifyError::Invalid) - } - - /// Verifies a token claims returning the claimed ID - /// - /// `token` The token to verify - fn verify(&self, token: &str) -> Result { - // Split the token parts - let (msg_raw, sig_raw) = match token.split_once('.') { - Some(value) => value, - None => return Err(VerifyError::Invalid), - }; - - // Decode the 12 byte token message - let mut msg = [0u8; 12]; - Base64UrlUnpadded::decode(msg_raw, &mut msg)?; - - // Decode 32byte signature (SHA256) - let mut sig = [0u8; 32]; - Base64UrlUnpadded::decode(sig_raw, &mut sig)?; - - // Verify the signature - if hmac::verify(&self.key, &msg, &sig).is_err() { - return Err(VerifyError::Invalid); - } - - // Extract ID and expiration from the msg bytes - let mut id = [0u8; 4]; - id.copy_from_slice(&msg[..4]); - let id = u32::from_be_bytes(id); - - let mut exp = [0u8; 8]; - exp.copy_from_slice(&msg[4..]); - let exp = u64::from_be_bytes(exp); - - // Ensure the timestamp is not expired - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Clock went backwards") - .as_secs(); - - if exp < now { - return Err(VerifyError::Expired); - } - - Ok(id) - } -} - -/// Errors that can occur while verifying a token -#[derive(Debug, Error)] -pub enum VerifyError { - /// The token is expired - #[error("Expired token")] - Expired, - /// The token is invalid - #[error("Invalid token")] - Invalid, - /// Internal server error - #[error("Server error")] - Server, -} - -impl From for VerifyError { - fn from(_: base64ct::Error) -> Self { - Self::Invalid - } -} diff --git a/src/session/mod.rs b/src/session/mod.rs index a92aa5d2..93732794 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -6,31 +6,33 @@ use crate::{ database::entities::Player, middleware::blaze_upgrade::BlazeScheme, services::{ - game::{manager::GetGameMessage, models::RemoveReason, GamePlayer, RemovePlayerMessage}, - matchmaking::RemoveQueueMessage, - sessions::{AddMessage, RemoveMessage}, - Services, + game::{ + manager::{GameManager, GetGameMessage, RemoveQueueMessage}, + models::RemoveReason, + GamePlayer, RemovePlayerMessage, + }, + sessions::{AddMessage, RemoveMessage, Sessions}, }, - state::App, utils::{ - components::{self, Components, UserSessions}, - models::{NetData, NetGroups, Port, QosNetworkData, UpdateExtDataAttr}, + components::{self, game_manager::GAME_TYPE, user_sessions}, + models::{NetData, NetworkAddress, Port, QosNetworkData, UpdateExtDataAttr}, types::{GameID, PlayerID, SessionID}, }, }; -use blaze_pk::{ - codec::Encodable, - packet::{Packet, PacketComponents, PacketDebug}, - router::HandleError, - tag::TdfType, - value_type, - writer::TdfWriter, -}; + use interlink::prelude::*; -use log::{debug, error, log_enabled}; -use std::{fmt::Debug, io, net::SocketAddr}; +use log::{debug, log_enabled}; +use std::{fmt::Debug, io, net::Ipv4Addr, sync::Arc}; +use tdf::{ObjectId, TdfSerialize, TdfType, TdfTyped}; + +use self::{ + packet::{Packet, PacketDebug}, + router::BlazeRouter, +}; pub mod models; +pub mod packet; +pub mod router; pub mod routes; /// Structure for storing a client session. This includes the @@ -41,7 +43,7 @@ pub struct Session { id: SessionID, /// Connection socket addr - addr: SocketAddr, + addr: Ipv4Addr, /// Packet writer sink for the session writer: SinkLink, @@ -51,6 +53,11 @@ pub struct Session { /// Data associated with this session data: SessionData, + + router: Arc, + + game_manager: Link, + sessions: Link, } #[derive(Default, Clone)] @@ -66,8 +73,7 @@ pub struct SessionData { impl Service for Session { fn stopping(&mut self) { - let services = App::services(); - self.clear_auth(services); + self.clear_auth(); debug!("Session stopped (SID: {})", self.id); } } @@ -155,15 +161,13 @@ pub struct SetPlayerMessage(pub Option); impl Handler for Session { type Response = (); fn handle(&mut self, msg: SetPlayerMessage, ctx: &mut ServiceContext) -> Self::Response { - let services = App::services(); - // Clear the current authentication - self.clear_auth(services); + self.clear_auth(); // If we are setting a new player if let Some(player) = msg.0 { // Add the session to authenticated sessions - let _ = services.sessions.do_send(AddMessage { + let _ = self.sessions.do_send(AddMessage { player_id: player.id, link: ctx.link(), }); @@ -242,23 +246,17 @@ impl StreamHandler> for Session { fn handle(&mut self, msg: io::Result, ctx: &mut ServiceContext) { if let Ok(packet) = msg { self.debug_log_packet("Read", &packet); - let mut addr = ctx.link(); + let addr = ctx.link(); + let router = self.router.clone(); tokio::spawn(async move { - let router = App::router(); - let response = match router.handle(&mut addr, packet) { + let response = match router.handle(addr.clone(), packet) { // Await the handler response future Ok(fut) => fut.await, - // Handle any errors that occur - Err(err) => { - match err { - // No handler set-up just respond with a default empty response - HandleError::MissingHandler(packet) => packet.respond_empty(), - HandleError::Decoding(err) => { - error!("Error while decoding packet: {:?}", err); - return; - } - } + // Handle no handler for packet + Err(packet) => { + debug!("Missing packet handler"); + Packet::response_empty(&packet) } }; // Push the response to the client @@ -287,10 +285,11 @@ impl Handler for Session { fn handle(&mut self, _msg: UpdateClientMessage, _ctx: &mut ServiceContext) { if let Some(player) = &self.data.player { let packet = Packet::notify( - Components::UserSessions(UserSessions::SetSession), + user_sessions::COMPONENT, + user_sessions::SET_SESSION, SetSession { player_id: player.id, - session: self, + session: &self.data, }, ); self.push(packet); @@ -299,7 +298,7 @@ impl Handler for Session { } #[derive(Message)] -#[msg(rtype = "SocketAddr")] +#[msg(rtype = "Ipv4Addr")] pub struct GetSocketAddrMessage; impl Handler for Session { @@ -328,10 +327,11 @@ impl Handler for Session { fn handle(&mut self, msg: InformSessions, _ctx: &mut ServiceContext) -> Self::Response { if let Some(player) = &self.data.player { let packet = Packet::notify( - Components::UserSessions(UserSessions::SetSession), + user_sessions::COMPONENT, + user_sessions::SET_SESSION, SetSession { player_id: player.id, - session: self, + session: &self.data, }, ); @@ -363,7 +363,7 @@ impl Handler for Session { #[derive(Message)] pub struct NetworkInfoMessage { - pub groups: NetGroups, + pub address: NetworkAddress, pub qos: QosNetworkData, } @@ -373,7 +373,7 @@ impl Handler for Session { fn handle(&mut self, msg: NetworkInfoMessage, ctx: &mut ServiceContext) { let net = &mut &mut self.data.net; net.qos = msg.qos; - net.groups = Some(msg.groups); + net.addr = msg.address; // Notify the client of the change via a message rather than // directly so its sent after the response @@ -416,7 +416,8 @@ impl Handler for Session { // Create the details packets let a = Packet::notify( - Components::UserSessions(UserSessions::SessionDetails), + user_sessions::COMPONENT, + user_sessions::SESSION_DETAILS, SessionUpdate { session: self, player_id: player.id, @@ -425,7 +426,8 @@ impl Handler for Session { ); let b = Packet::notify( - Components::UserSessions(UserSessions::UpdateExtendedDataAttribute), + user_sessions::COMPONENT, + user_sessions::UPDATE_EXTENDED_DATA_ATTRIBUTE, UpdateExtDataAttr { flags: 0x3, player_id: player.id, @@ -439,16 +441,14 @@ impl Handler for Session { } impl Session { - /// Creates a new session with the provided values. - /// - /// `id` The unique session ID - /// `values` The networking TcpStream and address - /// `message_sender` The message sender for session messages pub fn new( id: SessionID, host_target: SessionHostTarget, writer: SinkLink, - addr: SocketAddr, + addr: Ipv4Addr, + router: Arc, + game_manager: Link, + sessions: Link, ) -> Self { Self { id, @@ -456,6 +456,9 @@ impl Session { data: SessionData::default(), host_target, addr, + router, + game_manager, + sessions, } } @@ -482,18 +485,10 @@ impl Session { return; } - let component = Components::from_header(&packet.header); - // Ping messages are ignored from debug logging as they are very frequent - let ignored = if let Some(component) = &component { - matches!( - component, - Components::Util(components::Util::Ping) - | Components::Util(components::Util::SuspendUserPing) - ) - } else { - false - }; + let ignored = packet.header.component == components::util::COMPONENT + && (packet.header.command == components::util::PING + || packet.header.command == components::util::SUSPEND_USER_PING); if ignored { return; @@ -502,7 +497,6 @@ impl Session { let debug = SessionPacketDebug { action, packet, - component, session: self, }; @@ -511,7 +505,7 @@ impl Session { /// Removes the session from any connected games and the /// matchmaking queue - pub fn remove_games(&mut self, services: &'static Services) { + pub fn remove_games(&mut self) { // Don't attempt to remove if theres no active player let player_id = match &self.data.player { Some(value) => value.id, @@ -519,10 +513,11 @@ impl Session { }; if let Some(game_id) = self.data.game.take() { + let game_manager = self.game_manager.clone(); // Remove the player from the game tokio::spawn(async move { // Obtain the current game - let game = match services.game_manager.send(GetGameMessage { game_id }).await { + let game = match game_manager.send(GetGameMessage { game_id }).await { Ok(Some(value)) => value, _ => return, }; @@ -537,16 +532,14 @@ impl Session { }); } else { // Remove the player from matchmaking if present - let _ = services - .matchmaking - .do_send(RemoveQueueMessage { player_id }); + let _ = self.game_manager.do_send(RemoveQueueMessage { player_id }); } } /// Removes the player from the authenticated sessions list /// if the player is authenticated - pub fn clear_auth(&mut self, services: &'static Services) { - self.remove_games(services); + pub fn clear_auth(&mut self) { + self.remove_games(); // Check that theres authentication let player = match self.data.player.take() { @@ -555,7 +548,7 @@ impl Session { }; // Send the remove session message - let _ = services.sessions.do_send(RemoveMessage { + let _ = self.sessions.do_send(RemoveMessage { player_id: player.id, }); } @@ -566,7 +559,6 @@ impl Session { struct SessionPacketDebug<'a> { action: &'static str, packet: &'a Packet, - component: Option, session: &'a Session, } @@ -574,8 +566,6 @@ impl Debug for SessionPacketDebug<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "Session {} Packet", self.action)?; - let component = &self.component; - if let Some(player) = &self.session.data.player { writeln!( f, @@ -586,54 +576,49 @@ impl Debug for SessionPacketDebug<'_> { writeln!(f, "Info: ( SID: {})", &self.session.id)?; } - let minified = if let Some(component) = &self.component { - matches!( - component, - Components::Authentication(components::Authentication::ListUserEntitlements2) - | Components::Util(components::Util::FetchClientConfig) - | Components::Util(components::Util::UserSettingsLoadAll) - ) - } else { - false - }; + let header = &self.packet.header; + + let minified = (header.component == components::authentication::COMPONENT + && header.command == components::authentication::LIST_USER_ENTITLEMENTS_2) + || (header.component == components::util::COMPONENT + && (header.command == components::util::FETCH_CLIENT_CONFIG + || header.command == components::util::USER_SETTINGS_LOAD_ALL)); PacketDebug { packet: self.packet, - component: component.as_ref(), minified, } .fmt(f) } } -impl Encodable for SessionData { - fn encode(&self, writer: &mut TdfWriter) { - self.net.tag_groups(b"ADDR", writer); - writer.tag_str(b"BPS", "ea-sjc"); - writer.tag_str_empty(b"CTY"); - writer.tag_var_int_list_empty(b"CVAR"); - { - writer.tag_map_start(b"DMAP", TdfType::VarInt, TdfType::VarInt, 1); - writer.write_u32(0x70001); - writer.write_u16(0x409a); - } - writer.tag_u16(b"HWFG", self.net.hardware_flags); - { +impl TdfSerialize for SessionData { + fn serialize(&self, w: &mut S) { + w.group_body(|w| { + w.tag_ref(b"ADDR", &self.net.addr); + w.tag_str(b"BPS", "ea-sjc"); + w.tag_str_empty(b"CTY"); + w.tag_var_int_list_empty(b"CVAR"); + + w.tag_map_tuples(b"DMAP", &[(0x70001, 0x409a)]); + + w.tag_u16(b"HWFG", self.net.hardware_flags); + // Ping latency to the Quality of service servers - writer.tag_list_start(b"PSLM", TdfType::VarInt, 1); - 0xfff0fff.encode(writer); - } - writer.tag_value(b"QDAT", &self.net.qos); - writer.tag_u8(b"UATT", 0); - if let Some(game_id) = &self.game { - writer.tag_list_start(b"ULST", TdfType::Triple, 1); - (4, 1, *game_id).encode(writer); - } - writer.tag_group_end(); + w.tag_list_slice(b"PSLM", &[0xfff0fff]); + + w.tag_ref(b"QDAT", &self.net.qos); + w.tag_u8(b"UATT", 0); + if let Some(game_id) = &self.game { + w.tag_list_slice(b"ULST", &[ObjectId::new(GAME_TYPE, *game_id as u64)]); + } + }); } } -value_type!(SessionData, TdfType::Group); +impl TdfTyped for SessionData { + const TYPE: TdfType = TdfType::Group; +} /// Session update for a session other than ourselves /// which contains the details for that session @@ -646,16 +631,16 @@ struct SessionUpdate<'a> { display_name: &'a str, } -impl Encodable for SessionUpdate<'_> { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"DATA", &self.session.data); +impl TdfSerialize for SessionUpdate<'_> { + fn serialize(&self, w: &mut S) { + w.tag_ref(b"DATA", &self.session.data); - writer.group(b"USER", |writer| { - writer.tag_u32(b"AID", self.player_id); + w.group(b"USER", |writer| { + writer.tag_owned(b"AID", self.player_id); writer.tag_u32(b"ALOC", 0x64654445); - writer.tag_empty_blob(b"EXBB"); + writer.tag_blob_empty(b"EXBB"); writer.tag_u8(b"EXID", 0); - writer.tag_u32(b"ID", self.player_id); + writer.tag_owned(b"ID", self.player_id); writer.tag_str(b"NAME", self.display_name); }); } @@ -667,34 +652,34 @@ pub struct LookupResponse { display_name: String, } -impl Encodable for LookupResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"EDAT", &self.session_data); +impl TdfSerialize for LookupResponse { + fn serialize(&self, w: &mut S) { + w.tag_ref(b"EDAT", &self.session_data); - writer.tag_u8(b"FLGS", 2); + w.tag_u8(b"FLGS", 2); - writer.group(b"USER", |writer| { - writer.tag_u32(b"AID", self.player_id); - writer.tag_u32(b"ALOC", 0x64654445); - writer.tag_empty_blob(b"EXBB"); - writer.tag_u8(b"EXID", 0); - writer.tag_u32(b"ID", self.player_id); - writer.tag_str(b"NAME", &self.display_name); + w.group(b"USER", |w| { + w.tag_owned(b"AID", self.player_id); + w.tag_u32(b"ALOC", 0x64654445); + w.tag_blob_empty(b"EXBB"); + w.tag_u8(b"EXID", 0); + w.tag_owned(b"ID", self.player_id); + w.tag_str(b"NAME", &self.display_name); }); } } /// Session update for ourselves struct SetSession<'a> { + /// The session this update is for + session: &'a SessionData, /// The player ID the update is for player_id: PlayerID, - /// The session this update is for - session: &'a Session, } -impl Encodable for SetSession<'_> { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"DATA", &self.session.data); - writer.tag_u32(b"USID", self.player_id); +impl TdfSerialize for SetSession<'_> { + fn serialize(&self, w: &mut S) { + w.tag_ref(b"DATA", self.session); + w.tag_owned(b"USID", self.player_id) } } diff --git a/src/session/models/auth.rs b/src/session/models/auth.rs index 10344c59..c93b4893 100644 --- a/src/session/models/auth.rs +++ b/src/session/models/auth.rs @@ -1,14 +1,34 @@ +use tdf::{TdfDeserialize, TdfSerialize, TdfSerializer, TdfType, TdfTyped}; + use crate::{database::entities::Player, utils::types::PlayerID}; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeResult, - reader::TdfReader, - tag::TdfType, - value_type, - writer::TdfWriter, -}; use std::borrow::Cow; +#[derive(Debug, Clone)] +#[repr(u16)] +#[allow(unused)] +pub enum AuthenticationError { + InvalidUser = 0xb, + InvalidPassword = 0xc, + InvalidToken = 0xd, + ExpiredToken = 0xe, + Exists = 0xf, + TooYoung = 0x10, + NoAccount = 0x11, + PersonaNotFound = 0x12, + PersonaInactive = 0x13, + InvalidPMail = 0x14, + InvalidField = 0x15, + InvalidEmail = 0x16, + InvalidStatus = 0x17, + InvalidSessionKey = 0x1f, + PersonaBanned = 0x20, + InvalidPersona = 0x21, + Banned = 0x2b, + FieldInvalidChars = 0xc9, + FieldTooShort = 0xca, + FieldTooLong = 0xcb, +} + /// Login through login prompt menu with email and password /// ``` /// { @@ -19,21 +39,16 @@ use std::borrow::Cow; /// "TYPE": 0 /// } /// ``` +#[derive(TdfDeserialize)] pub struct LoginRequest { /// The email addresss of the account to login with + #[tdf(tag = "MAIL")] pub email: String, /// The plain text password of the account to login to + #[tdf(tag = "PASS")] pub password: String, } -impl Decodable for LoginRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let email: String = reader.tag(b"MAIL")?; - let password: String = reader.tag(b"PASS")?; - Ok(Self { email, password }) - } -} - /// Silent token based authentication with player ID /// ``` /// { @@ -42,19 +57,14 @@ impl Decodable for LoginRequest { /// "TYPE": 2 /// } /// ``` +#[derive(TdfDeserialize)] pub struct SilentLoginRequest { /// The authentication token previously provided to the client /// on a previous successful authentication attempt + #[tdf(tag = "AUTH")] pub token: String, } -impl Decodable for SilentLoginRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let token: String = reader.tag(b"AUTH")?; - Ok(Self { token }) - } -} - /// Authentication through origin token /// ``` /// { @@ -62,23 +72,13 @@ impl Decodable for SilentLoginRequest { /// "TYPE": 1 /// } /// ``` +#[derive(TdfDeserialize, TdfSerialize)] pub struct OriginLoginRequest { /// The token generated by Origin + #[tdf(tag = "AUTH")] pub token: String, -} - -impl Decodable for OriginLoginRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let token: String = reader.tag(b"AUTH")?; - Ok(Self { token }) - } -} - -impl Encodable for OriginLoginRequest { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_str(b"AUTH", &self.token); - writer.tag_u8(b"TYPE", 0x1); - } + #[tdf(tag = "TYPE")] + pub ty: u8, } /// Encodes a mock persona from the provided player using its @@ -87,14 +87,15 @@ impl Encodable for OriginLoginRequest { /// `writer` The Tdf writer to use for writing the values /// `id` The id of the player to write for /// `display_name` The display name of the player -fn encode_persona(writer: &mut TdfWriter, id: PlayerID, display_name: &str) { - writer.tag_str(b"DSNM", display_name); - writer.tag_zero(b"LAST"); - writer.tag_u32(b"PID", id); - writer.tag_zero(b"STAS"); - writer.tag_zero(b"XREF"); - writer.tag_zero(b"XTYP"); - writer.tag_group_end(); +fn encode_persona(w: &mut S, id: PlayerID, display_name: &str) { + w.group_body(|w| { + w.tag_str(b"DSNM", display_name); + w.tag_zero(b"LAST"); + w.tag_u32(b"PID", id); + w.tag_zero(b"STAS"); + w.tag_zero(b"XREF"); + w.tag_zero(b"XTYP"); + }); } /// Structure for the response to an authentication request. @@ -107,19 +108,19 @@ pub struct AuthResponse { pub silent: bool, } -impl Encodable for AuthResponse { - fn encode(&self, writer: &mut TdfWriter) { +impl TdfSerialize for AuthResponse { + fn serialize(&self, w: &mut S) { if self.silent { - writer.tag_zero(b"AGUP"); + w.tag_zero(b"AGUP"); } - writer.tag_str_empty(b"LDHT"); - writer.tag_zero(b"NTOS"); - writer.tag_str(b"PCTK", &self.session_token); // PC Authentication Token + w.tag_str_empty(b"LDHT"); + w.tag_zero(b"NTOS"); + w.tag_str(b"PCTK", &self.session_token); // PC Authentication Token if self.silent { - writer.tag_str_empty(b"PRIV"); + w.tag_str_empty(b"PRIV"); { - writer.group(b"SESS", |writer| { - writer.tag_u32(b"BUID", self.player.id); + w.group(b"SESS", |writer| { + writer.tag_owned(b"BUID", self.player.id); writer.tag_zero(b"FRST"); writer.tag_str(b"KEY", &format!("{:X}", self.player.id)); // Session Token writer.tag_zero(b"LLOG"); @@ -129,42 +130,37 @@ impl Encodable for AuthResponse { encode_persona(writer, self.player.id, &self.player.display_name); // Persona Details } - writer.tag_u32(b"UID", self.player.id); + writer.tag_owned(b"UID", self.player.id); }); } } else { - writer.tag_list_start(b"PLST", TdfType::Group, 1); - encode_persona(writer, self.player.id, &self.player.display_name); - writer.tag_str_empty(b"PRIV"); - writer.tag_str(b"SKEY", &format!("{:X}", self.player.id)); + w.tag_list_start(b"PLST", TdfType::Group, 1); + encode_persona(w, self.player.id, &self.player.display_name); + w.tag_str_empty(b"PRIV"); + w.tag_str(b"SKEY", &format!("{:X}", self.player.id)); } - writer.tag_zero(b"SPAM"); - writer.tag_str_empty(b"THST"); - writer.tag_str_empty(b"TSUI"); - writer.tag_str_empty(b"TURI"); + w.tag_zero(b"SPAM"); + w.tag_str_empty(b"THST"); + w.tag_str_empty(b"TSUI"); + w.tag_str_empty(b"TURI"); if !self.silent { - writer.tag_u32(b"UID", self.player.id); + w.tag_owned(b"UID", self.player.id); } } } /// Structure for request to create a new account with /// the provided email and password +#[derive(TdfDeserialize)] pub struct CreateAccountRequest { /// The email address of the account to create + #[tdf(tag = "MAIL")] pub email: String, /// The password of the account to create + #[tdf(tag = "PASS")] pub password: String, } -impl Decodable for CreateAccountRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let email: String = reader.tag(b"MAIL")?; - let password: String = reader.tag(b"PASS")?; - Ok(Self { email, password }) - } -} - /// Structure for the persona response which contains details /// about the current persona. Which in this case is just the /// player details @@ -173,44 +169,35 @@ pub struct PersonaResponse { pub player: Player, } -impl Encodable for PersonaResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"BUID", self.player.id); - writer.tag_zero(b"FRST"); - writer.tag_str(b"KEY", &format!("{:X}", self.player.id)); - writer.tag_zero(b"LLOG"); - writer.tag_str(b"MAIL", &self.player.email); - - writer.tag_group(b"PDTL"); - encode_persona(writer, self.player.id, &self.player.display_name); - writer.tag_u32(b"UID", self.player.id); +impl TdfSerialize for PersonaResponse { + fn serialize(&self, w: &mut S) { + w.tag_owned(b"BUID", self.player.id); + w.tag_zero(b"FRST"); + w.tag_str(b"KEY", &format!("{:X}", self.player.id)); + w.tag_zero(b"LLOG"); + w.tag_str(b"MAIL", &self.player.email); + + w.tag_group(b"PDTL"); + encode_persona(w, self.player.id, &self.player.display_name); + w.tag_owned(b"UID", self.player.id); } } /// Request for listing entitlements +#[derive(TdfDeserialize)] pub struct ListEntitlementsRequest { /// The entitlements tag + #[tdf(tag = "ETAG")] pub tag: String, } -impl Decodable for ListEntitlementsRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let tag: String = reader.tag(b"ETAG")?; - Ok(Self { tag }) - } -} - /// Response of an entitlements list +#[derive(TdfSerialize)] pub struct ListEntitlementsResponse { + #[tdf(tag = "NLST")] pub list: &'static [Entitlement], } -impl Encodable for ListEntitlementsResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_slice_list(b"NLST", self.list); - } -} - //noinspection SpellCheckingInspection pub struct Entitlement { pub name: &'static str, @@ -265,86 +252,75 @@ impl Entitlement { } } -impl Encodable for Entitlement { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_str_empty(b"DEVI"); - writer.tag_str(b"GDAY", "2012-12-15T16:15Z"); - writer.tag_str(b"GNAM", self.name); - writer.tag_u64(b"ID", self.id); - writer.tag_u8(b"ISCO", 0); - writer.tag_u8(b"PID", 0); - writer.tag_str(b"PJID", self.pjid); - writer.tag_u8(b"PRCA", self.prca); - writer.tag_str(b"PRID", self.prid); - writer.tag_u8(b"STAT", 1); - writer.tag_u8(b"STRC", 0); - writer.tag_str(b"TAG", self.tag); - writer.tag_str_empty(b"TDAY"); - writer.tag_u8(b"TTYPE", self.ty); - writer.tag_u8(b"UCNT", 0); - writer.tag_u8(b"VER", 0); - writer.tag_group_end(); +impl TdfSerialize for Entitlement { + fn serialize(&self, w: &mut S) { + w.tag_str_empty(b"DEVI"); + w.tag_str(b"GDAY", "2012-12-15T16:15Z"); + w.tag_str(b"GNAM", self.name); + w.tag_u64(b"ID", self.id); + w.tag_u8(b"ISCO", 0); + w.tag_u8(b"PID", 0); + w.tag_str(b"PJID", self.pjid); + w.tag_u8(b"PRCA", self.prca); + w.tag_str(b"PRID", self.prid); + w.tag_u8(b"STAT", 1); + w.tag_u8(b"STRC", 0); + w.tag_str(b"TAG", self.tag); + w.tag_str_empty(b"TDAY"); + w.tag_u8(b"TYPE", self.ty); + w.tag_u8(b"UCNT", 0); + w.tag_u8(b"VER", 0); + w.tag_group_end(); } } -value_type!(Entitlement, TdfType::Group); +impl TdfTyped for Entitlement { + const TYPE: TdfType = TdfType::Group; +} /// Structure for a request to send a forgot password email. Currently /// only logs that a reset was requested and doesn't actually send /// an email. +#[derive(TdfDeserialize)] pub struct ForgotPasswordRequest { /// The email of the account that needs a password reset + #[tdf(tag = "MAIL")] pub email: String, } -impl Decodable for ForgotPasswordRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let email: String = reader.tag(b"MAIL")?; - Ok(Self { email }) - } -} - /// Dummy structure for the LegalDocsInfo response. None of the /// values in this struct ever change. pub struct LegalDocsInfo; -impl Encodable for LegalDocsInfo { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_zero(b"EAMC"); - writer.tag_str_empty(b"LHST"); - writer.tag_zero(b"PMC"); - writer.tag_str_empty(b"PPUI"); - writer.tag_str_empty(b"TSUI"); +impl TdfSerialize for LegalDocsInfo { + fn serialize(&self, w: &mut S) { + w.tag_zero(b"EAMC"); + w.tag_str_empty(b"LHST"); + w.tag_zero(b"PMC"); + w.tag_str_empty(b"PPUI"); + w.tag_str_empty(b"TSUI"); } } /// Structure for legal content responses such as the Privacy Policy /// and the terms and condition. +#[derive(TdfSerialize)] pub struct LegalContent { /// The url path to the legal content (Prefix this value with https://tos.ea.com/legalapp/ to get the url) + #[tdf(tag = "LDVC")] pub path: &'static str, - /// The actual HTML content of the legal document - pub content: Cow<'static, str>, /// Unknown value + #[tdf(tag = "TCOL")] pub col: u16, -} - -impl Encodable for LegalContent { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_str(b"LDVC", self.path); - writer.tag_u16(b"TCOL", self.col); - writer.tag_str(b"TCOT", &self.content); - } + /// The actual HTML content of the legal document + #[tdf(tag = "TCOT")] + pub content: Cow<'static, str>, } /// Response to the client requesting a shared token +#[derive(TdfSerialize)] pub struct GetTokenResponse { /// The generated shared token + #[tdf(tag = "AUTH")] pub token: String, } - -impl Encodable for GetTokenResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_str(b"AUTH", &self.token) - } -} diff --git a/src/session/models/errors.rs b/src/session/models/errors.rs index 89c094aa..8973b6e5 100644 --- a/src/session/models/errors.rs +++ b/src/session/models/errors.rs @@ -1,38 +1,102 @@ -use blaze_pk::packet::{IntoResponse, Packet}; +use interlink::prelude::LinkError; +use log::error; +use sea_orm::DbErr; -pub type ServerResult = Result; +use crate::session::{packet::Packet, router::IntoPacketResponse}; + +use super::{auth::AuthenticationError, game_manager::GameManagerError, util::UtilError}; + +pub type ServerResult = Result; + +// #[test] +// fn decode_error() { +// let value: i32 = 19791881; +// let bytes = value.to_le_bytes(); +// let mut out = [0u8; 2]; +// out.copy_from_slice(&bytes[2..]); +// let out = u16::from_le_bytes(out); +// println!("{:#00x}", out); +// } + +#[derive(Debug, Clone)] +#[repr(u16)] +#[allow(unused)] +pub enum GlobalError { + Cancelled = 0x4009, + Disconnected = 0x4006, + DuplicateLogin = 0x4007, + AuthorizationRequired = 0x4008, + Timeout = 0x4005, + ComponentNotFound = 0x4002, + CommandNotFound = 0x4003, + AuthenticationRequired = 0x4004, + System = 0x4001, +} -/// Enum for server error values #[derive(Debug, Clone)] #[repr(u16)] #[allow(unused)] -pub enum ServerError { - ServerUnavailable = 0x0, - EmailNotFound = 0xB, - WrongPassword = 0xC, - InvalidSession = 0xD, - EmailAlreadyInUse = 0x0F, - AgeRestriction = 0x10, - InvalidAccount = 0x11, - BannedAccount = 0x13, - InvalidInformation = 0x15, - InvalidEmail = 0x16, - LegalGuardianRequired = 0x2A, - CodeRequired = 0x32, - KeyCodeAlreadyInUse = 0x33, - InvalidCerberusKey = 0x34, - ServerUnavailableFinal = 0x4001, - FailedNoLoginAction = 0x4004, - ServerUnavailableNothing = 0x4005, - ConnectionLost = 0x4007, - UnableToUpdateSettings = 0xCB, - // Errors from suspend - Suspend12D = 0x12D, - Suspend12E = 0x12E, -} - -impl IntoResponse for ServerError { +pub enum DatabaseError { + Timeout = 0x406c, + InitFailure = 0x406d, + TranscationNotComplete = 0x406e, + Disconnected = 0x406b, + NoConnectionAvailable = 0x4068, + DuplicateEntry = 0x4069, + System = 0x4065, +} + +/// Response type for some blaze error code +pub struct BlazeError(u16); + +impl From for BlazeError { + fn from(_: LinkError) -> Self { + GlobalError::System.into() + } +} + +impl From for BlazeError { + fn from(value: DbErr) -> Self { + error!("Database error: {}", value); + match value { + DbErr::ConnectionAcquire(_) => DatabaseError::NoConnectionAvailable, + DbErr::Conn(_) => DatabaseError::InitFailure, + _ => DatabaseError::System, + } + .into() + } +} + +impl From for BlazeError { + fn from(value: GameManagerError) -> Self { + BlazeError(value as u16) + } +} + +impl From for BlazeError { + fn from(value: GlobalError) -> Self { + BlazeError(value as u16) + } +} +impl From for BlazeError { + fn from(value: AuthenticationError) -> Self { + BlazeError(value as u16) + } +} + +impl From for BlazeError { + fn from(value: DatabaseError) -> Self { + BlazeError(value as u16) + } +} +impl From for BlazeError { + fn from(value: UtilError) -> Self { + BlazeError(value as u16) + } +} + +impl IntoPacketResponse for BlazeError { fn into_response(self, req: &Packet) -> Packet { - req.respond_error_empty(self as u16) + Packet::error_empty(req, self.0) } } diff --git a/src/session/models/game_manager.rs b/src/session/models/game_manager.rs index d5114fa6..8b494a23 100644 --- a/src/session/models/game_manager.rs +++ b/src/session/models/game_manager.rs @@ -1,151 +1,108 @@ +use tdf::{Blob, GroupSlice, TdfDeserialize, TdfDeserializeOwned, TdfSerialize, TdfType, TdfTyped}; + use crate::{ - services::{ - game::{ - models::{GameSettings, GameState, PlayerState, RemoveReason}, - AttrMap, - }, - matchmaking::rules::RuleSet, + services::game::{ + models::{GameSettings, GameState, PlayerState, RemoveReason}, + rules::RuleSet, + AttrMap, }, utils::types::{GameID, PlayerID, SessionID}, }; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeResult, - reader::TdfReader, - tag::TdfType, - writer::TdfWriter, -}; + +#[derive(Debug, Clone)] +#[repr(u16)] +#[allow(unused)] +pub enum GameManagerError { + InvalidGameId = 0x2, + GameFull = 0x4, + PlayerNotFound = 0x65, + AlreadyGameMember = 0x67, + RemovePlayerFailed = 0x68, + JoinPlayerFailed = 0x6c, + AlreadyInQueue = 0x70, + TeamFull = 0xff, +} /// Structure of the request for creating new games contains the /// initial game attributes and game setting +#[derive(TdfDeserialize)] pub struct CreateGameRequest { /// The games initial attributes + #[tdf(tag = "ATTR")] pub attributes: AttrMap, /// The games initial setting + #[tdf(tag = "GSET")] pub setting: GameSettings, } -impl Decodable for CreateGameRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let attributes: AttrMap = reader.tag(b"ATTR")?; - let setting: GameSettings = reader.tag(b"GSET")?; - Ok(Self { - attributes, - setting, - }) - } -} - /// Structure for the response to game creation which contains /// the ID of the created game +#[derive(TdfSerialize)] pub struct CreateGameResponse { /// The game ID + #[tdf(tag = "GID")] pub game_id: GameID, } -impl Encodable for CreateGameResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.game_id); - } -} - /// Structure of request to remove player from a game +#[derive(TdfDeserialize)] pub struct RemovePlayerRequest { /// The ID of the game to remove from + #[tdf(tag = "GID")] pub game_id: GameID, /// The ID of the player to remove + #[tdf(tag = "PID")] pub player_id: PlayerID, // The reason the player was removed + #[tdf(tag = "REAS")] pub reason: RemoveReason, } -impl Decodable for RemovePlayerRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let game_id: GameID = reader.tag(b"GID")?; - let player_id: PlayerID = reader.tag(b"PID")?; - let reason: RemoveReason = reader.tag(b"REAS")?; - Ok(Self { - game_id, - player_id, - reason, - }) - } -} - +#[derive(TdfDeserialize)] pub struct SetAttributesRequest { /// The new game attributes + #[tdf(tag = "ATTR")] pub attributes: AttrMap, /// The ID of the game to set the attributes for + #[tdf(tag = "GID")] pub game_id: GameID, } -impl Decodable for SetAttributesRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let attributes = reader.tag(b"ATTR")?; - let game_id: GameID = reader.tag(b"GID")?; - - Ok(Self { - attributes, - game_id, - }) - } -} - +#[derive(TdfDeserialize)] pub struct SetStateRequest { + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "GSTA")] pub state: GameState, } -impl Decodable for SetStateRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let game_id: GameID = reader.tag(b"GID")?; - let state: GameState = reader.tag(b"GSTA")?; - Ok(Self { game_id, state }) - } -} +#[derive(TdfDeserialize)] pub struct SetSettingRequest { + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "GSET")] pub setting: GameSettings, } -impl Decodable for SetSettingRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let game_id: GameID = reader.tag(b"GID")?; - let setting: GameSettings = reader.tag(b"GSET")?; - Ok(Self { game_id, setting }) - } -} - /// Request to update the state of a mesh connection between /// payers. +#[derive(TdfDeserialize)] pub struct UpdateMeshRequest { + #[tdf(tag = "GID")] pub game_id: GameID, - pub target: Option, + #[tdf(tag = "TARG")] + pub targets: Vec, } +#[derive(TdfDeserialize, TdfTyped)] +#[tdf(group)] pub struct MeshTarget { + #[tdf(tag = "PID")] pub player_id: PlayerID, + #[tdf(tag = "STAT")] pub state: PlayerState, } -impl Decodable for UpdateMeshRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let game_id: GameID = reader.tag(b"GID")?; - let count: usize = reader.until_list(b"TARG", TdfType::Group)?; - - let target = if count > 0 { - let player_id: PlayerID = reader.tag(b"PID")?; - let state: PlayerState = reader.tag(b"STAT")?; - let target = MeshTarget { player_id, state }; - Some(target) - } else { - None - }; - - Ok(Self { game_id, target }) - } -} - /// Structure of the request for starting matchmaking. Contains /// the rule set that games must match in order to join pub struct MatchmakingRequest { @@ -153,25 +110,25 @@ pub struct MatchmakingRequest { pub rules: RuleSet, } -impl Decodable for MatchmakingRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - reader.until_tag(b"CRIT", TdfType::Group)?; - let rule_count: usize = reader.until_list(b"RLST", TdfType::Group)?; +impl TdfDeserializeOwned for MatchmakingRequest { + fn deserialize_owned(r: &mut tdf::TdfDeserializer<'_>) -> tdf::DecodeResult { + r.until_tag(b"CRIT", TdfType::Group)?; + let rule_count: usize = r.until_list_typed(b"RLST", TdfType::Group)?; let mut rules: Vec<(String, String)> = Vec::with_capacity(rule_count); for _ in 0..rule_count { - let name: String = reader.tag(b"NAME")?; - let values_count: usize = reader.until_list(b"VALU", TdfType::String)?; + let name: String = r.tag(b"NAME")?; + let values_count: usize = r.until_list_typed(b"VALU", TdfType::String)?; if values_count < 1 { continue; } - let value: String = reader.read_string()?; + let value: String = String::deserialize_owned(r)?; if values_count > 1 { for _ in 1..rule_count { - reader.skip_blob()?; + Blob::skip(r)?; } } - reader.skip_group()?; + GroupSlice::deserialize_content_skip(r)?; rules.push((name, value)); } Ok(Self { @@ -183,50 +140,44 @@ impl Decodable for MatchmakingRequest { /// Structure of the matchmaking response. This just contains /// what normally would be a unique matchmaking ID but in this case /// its just the session ID. +#[derive(TdfSerialize)] pub struct MatchmakingResponse { /// The current session ID + #[tdf(tag = "MSID")] pub id: SessionID, } -impl Encodable for MatchmakingResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"MSID", self.id); - } -} - +#[derive(TdfDeserialize)] pub struct GetGameDataRequest { + #[tdf(tag = "GLST")] pub game_list: Vec, } -impl Decodable for GetGameDataRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let game_list: Vec = reader.tag(b"GLST")?; - Ok(Self { game_list }) - } -} - +#[derive(TdfDeserialize)] pub struct JoinGameRequest { - /// The join target - pub target_id: PlayerID, + #[tdf(tag = "USER")] + pub user: JoinGameRequestUser, } -impl Decodable for JoinGameRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - reader.until_tag(b"USER", TdfType::Group)?; - let target_id: PlayerID = reader.tag(b"ID")?; - Ok(Self { target_id }) - } +#[derive(TdfDeserialize, TdfTyped)] +#[tdf(group)] +pub struct JoinGameRequestUser { + #[tdf(tag = "ID")] + pub id: PlayerID, } +#[derive(TdfSerialize)] pub struct JoinGameResponse { + #[tdf(tag = "GID")] pub game_id: GameID, + #[tdf(tag = "JGS")] + pub state: JoinGameState, } -impl Encodable for JoinGameResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u32(b"GID", self.game_id); - - // TODO: Join states: JOINED_GAME = 0, IN_QUEUE = 1, GROUP_PARTIALLY_JOINED = 2 - writer.tag_zero(b"JGS"); - } +#[derive(TdfSerialize, TdfTyped, Copy, Clone)] +#[repr(u8)] +pub enum JoinGameState { + JoinedGame = 0, + // InQueue = 1, + // GroupPartiallyJoined = 2, } diff --git a/src/session/models/messaging.rs b/src/session/models/messaging.rs index 1a82c7b8..4fe684e9 100644 --- a/src/session/models/messaging.rs +++ b/src/session/models/messaging.rs @@ -1,22 +1,15 @@ -use crate::utils::{ - components::{Components, UserSessions}, - types::PlayerID, -}; -use blaze_pk::{codec::Encodable, packet::PacketComponents, tag::TdfType, writer::TdfWriter}; +use crate::utils::{components::user_sessions::PLAYER_TYPE, types::PlayerID}; +use tdf::{ObjectId, TdfSerialize}; /// Structure of the response to a fetch messages request. Which tells /// the client how many messages to expect +#[derive(TdfSerialize)] pub struct FetchMessageResponse { /// The total number of messages to expect + #[tdf(tag = "MCNT")] pub count: usize, } -impl Encodable for FetchMessageResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_usize(b"MCNT", self.count); - } -} - /// Structure of a message notification packet pub struct MessageNotify { /// The ID of the player the message is for @@ -25,30 +18,24 @@ pub struct MessageNotify { pub message: String, } -impl Encodable for MessageNotify { - fn encode(&self, writer: &mut TdfWriter) { - let ref_value: (u16, u16) = Components::UserSessions(UserSessions::SetSession).values(); - let player_ref: (u16, u16, u32) = (ref_value.0, ref_value.1, self.player_id); - - writer.tag_u8(b"FLAG", 0x1); - writer.tag_u8(b"MGID", 0x1); - writer.tag_str(b"NAME", &self.message); - - writer.group(b"PYLD", |writer| { - { - writer.tag_map_start(b"ATTR", TdfType::String, TdfType::String, 1); - writer.write_str("B0000"); - writer.write_str("160"); - } - - writer.tag_u8(b"FLAG", 0x1); - writer.tag_u8(b"STAT", 0x0); - writer.tag_u8(b"TAG", 0x0); - writer.tag_value(b"TARG", &player_ref); - writer.tag_u8(b"TYPE", 0x0); +impl TdfSerialize for MessageNotify { + fn serialize(&self, w: &mut S) { + let player_ref = ObjectId::new(PLAYER_TYPE, self.player_id as u64); + + w.tag_u8(b"FLAG", 0x1); + w.tag_u8(b"MGID", 0x1); + w.tag_str(b"NAME", &self.message); + + w.group(b"PYLD", |w| { + w.tag_map_tuples(b"ATTR", &[("B0000", "160")]); + w.tag_u8(b"FLAG", 0x1); + w.tag_u8(b"STAT", 0x0); + w.tag_u8(b"TAG", 0x0); + w.tag_ref(b"TARG", &player_ref); + w.tag_u8(b"TYPE", 0x0); }); - writer.tag_value(b"SRCE", &player_ref); - writer.tag_zero(b"TIME"); + w.tag_ref(b"SRCE", &player_ref); + w.tag_zero(b"TIME"); } } diff --git a/src/session/models/other.rs b/src/session/models/other.rs index 038484da..1b632796 100644 --- a/src/session/models/other.rs +++ b/src/session/models/other.rs @@ -1,39 +1,44 @@ -use blaze_pk::{codec::Encodable, tag::TdfType}; +use tdf::{ObjectId, TdfSerialize, TdfType}; + +use crate::utils::components::association_lists::ASSOC_LIST_REF; /// Structure for the default response to offline game reporting pub struct GameReportResponse; -impl Encodable for GameReportResponse { - fn encode(&self, writer: &mut blaze_pk::writer::TdfWriter) { - writer.tag_var_int_list_empty(b"DATA"); - writer.tag_zero(b"EROR"); - writer.tag_zero(b"FNL"); - writer.tag_zero(b"GHID"); - writer.tag_zero(b"GRID"); +impl TdfSerialize for GameReportResponse { + fn serialize(&self, w: &mut S) { + w.tag_var_int_list_empty(b"DATA"); + w.tag_zero(b"EROR"); + w.tag_zero(b"FNL"); + w.tag_zero(b"GHID"); + w.tag_zero(b"GRID"); } } /// Structure for the default response to assocated lists pub struct AssocListResponse; -impl Encodable for AssocListResponse { - fn encode(&self, writer: &mut blaze_pk::writer::TdfWriter) { - writer.tag_list_start(b"LMAP", TdfType::Group, 1); +impl TdfSerialize for AssocListResponse { + fn serialize(&self, w: &mut S) { + w.tag_list_start(b"LMAP", TdfType::Group, 1); + w.group_body(|w| { + w.group(b"INFO", |w| { + w.tag_alt( + b"BOID", + ObjectId::new(ASSOC_LIST_REF, 0x74b09c4 /* ID of friends list? */), + ); + w.tag_u8(b"FLGS", 4); - writer.group(b"INFO", |writer| { - writer.tag_triple(b"BOID", (0x19, 0x1, 0x74b09c4)); - writer.tag_u8(b"FLGS", 4); + w.group(b"LID", |w| { + w.tag_str(b"LNM", "friendList"); + w.tag_u8(b"TYPE", 1); + }); - writer.group(b"LID", |writer| { - writer.tag_str(b"LNM", "friendList"); - writer.tag_u8(b"TYPE", 1); + w.tag_u8(b"LMS", 0xC8); + w.tag_u8(b"PRID", 0); }); - - writer.tag_u8(b"LMS", 0xC8); - writer.tag_u8(b"PRID", 0); + w.tag_zero(b"OFRC"); + w.tag_zero(b"TOCT"); }); - writer.tag_zero(b"OFRC"); - writer.tag_zero(b"TOCT"); - writer.tag_group_end(); } } diff --git a/src/session/models/stats.rs b/src/session/models/stats.rs index 452e3334..d01ec2c6 100644 --- a/src/session/models/stats.rs +++ b/src/session/models/stats.rs @@ -1,39 +1,31 @@ -use crate::{services::leaderboard::models::LeaderboardEntry, utils::types::PlayerID}; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::{DecodeError, DecodeResult}, - reader::TdfReader, - tag::TdfType, - writer::TdfWriter, +use tdf::{ + types::var_int::skip_var_int, DecodeError, TdfDeserialize, TdfDeserializeOwned, TdfSerialize, + TdfType, TdfTyped, +}; + +use crate::{ + services::leaderboard::models::LeaderboardEntry, + utils::{components::user_sessions::PLAYER_TYPE, types::PlayerID}, }; /// Structure for the request to retrieve the entity count /// of a leaderboard +#[derive(TdfDeserialize)] pub struct EntityCountRequest { /// The leaderboard name + #[tdf(tag = "NAME")] pub name: String, } -impl Decodable for EntityCountRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let name: String = reader.tag(b"NAME")?; - Ok(Self { name }) - } -} - /// Structure for the entity count response for finding the /// number of entities in a leaderboard section +#[derive(TdfSerialize)] pub struct EntityCountResponse { /// The number of entities in the leaderboard + #[tdf(tag = "CNT")] pub count: usize, } -impl Encodable for EntityCountResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_usize(b"CNT", self.count); - } -} - /// Request for a list of leaderboard entries where the center /// value is the entry for the player with the provided ID /// @@ -55,26 +47,17 @@ impl Encodable for EntityCountResponse { /// "USET": (0, 0, 0) /// } /// ``` +#[derive(TdfDeserialize)] pub struct CenteredLeaderboardRequest { + /// The ID of the player to center on + #[tdf(tag = "CENT")] + pub center: PlayerID, /// The entity count + #[tdf(tag = "COUN")] pub count: usize, /// The leaderboard name + #[tdf(tag = "NAME")] pub name: String, - /// The ID of the player to center on - pub center: PlayerID, -} - -impl Decodable for CenteredLeaderboardRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let center: PlayerID = reader.tag(b"CENT")?; - let count: usize = reader.tag(b"COUN")?; - let name: String = reader.tag(b"NAME")?; - Ok(Self { - center, - count, - name, - }) - } } pub enum LeaderboardResponse<'a> { @@ -86,39 +69,41 @@ pub enum LeaderboardResponse<'a> { Many(&'a [LeaderboardEntry]), } -impl Encodable for LeaderboardEntry { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_str(b"ENAM", &self.player_name); - writer.tag_u32(b"ENID", self.player_id); - writer.tag_usize(b"RANK", self.rank); - let value_str = self.value.to_string(); - writer.tag_str(b"RSTA", &value_str); - writer.tag_zero(b"RWFG"); - writer.tag_union_unset(b"RWST"); - { - writer.tag_list_start(b"STAT", TdfType::String, 1); - writer.write_str(&value_str); - } - writer.tag_zero(b"UATT"); - writer.tag_group_end(); +impl TdfSerialize for LeaderboardEntry { + fn serialize(&self, w: &mut S) { + w.group_body(|w| { + w.tag_str(b"ENAM", &self.player_name); + w.tag_u32(b"ENID", self.player_id); + w.tag_usize(b"RANK", self.rank); + + let value_str = self.value.to_string(); + w.tag_str(b"RSTA", &value_str); + w.tag_zero(b"RWFG"); + w.tag_union_unset(b"RWST"); + + w.tag_list_slice(b"STAT", &[value_str]); + + w.tag_zero(b"UATT"); + }); } } -impl Encodable for LeaderboardResponse<'_> { - fn encode(&self, writer: &mut TdfWriter) { +impl TdfTyped for LeaderboardEntry { + const TYPE: TdfType = TdfType::Group; +} + +impl TdfSerialize for LeaderboardResponse<'_> { + fn serialize(&self, w: &mut S) { match self { Self::Empty => { - writer.tag_list_start(b"LDLS", TdfType::Group, 0); + w.tag_list_empty(b"LDLS", TdfType::Group); } Self::One(value) => { - writer.tag_list_start(b"LDLS", TdfType::Group, 1); - value.encode(writer); + w.tag_list_start(b"LDLS", TdfType::Group, 1); + value.serialize(w); } Self::Many(values) => { - writer.tag_list_start(b"LDLS", TdfType::Group, values.len()); - for value in *values { - value.encode(writer); - } + w.tag_list_slice(b"LDLS", values); } } } @@ -144,24 +129,19 @@ impl Encodable for LeaderboardResponse<'_> { /// "USET": (0, 0, 0), /// } /// ``` +#[derive(TdfDeserialize)] pub struct LeaderboardRequest { /// The entity count + #[tdf(tag = "COUN")] pub count: usize, /// The leaderboard name + #[tdf(tag = "NAME")] pub name: String, /// The rank offset to start at + #[tdf(tag = "STRT")] pub start: usize, } -impl Decodable for LeaderboardRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let count: usize = reader.tag(b"COUN")?; - let name: String = reader.tag(b"NAME")?; - let start: usize = reader.tag(b"STRT")?; - Ok(Self { count, name, start }) - } -} - /// Structure for a request to get a leaderboard only /// containing the details for a specific player /// @@ -189,34 +169,29 @@ pub struct FilteredLeaderboardRequest { pub name: String, } -impl Decodable for FilteredLeaderboardRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let count: usize = reader.until_list(b"IDLS", TdfType::VarInt)?; +impl TdfDeserializeOwned for FilteredLeaderboardRequest { + fn deserialize_owned(r: &mut tdf::TdfDeserializer<'_>) -> tdf::DecodeResult { + let count: usize = r.until_list_typed(b"IDLS", TdfType::VarInt)?; if count < 1 { return Err(DecodeError::Other("Missing player ID for filter")); } - let id: PlayerID = reader.read_u32()?; + let id: PlayerID = PlayerID::deserialize_owned(r)?; for _ in 1..count { - reader.skip_var_int(); + skip_var_int(r)?; } - let name: String = reader.tag(b"NAME")?; + let name: String = r.tag(b"NAME")?; Ok(Self { id, name }) } } /// Structure for a request for a leaderboard group +#[derive(TdfDeserialize)] pub struct LeaderboardGroupRequest { /// The name of the leaderboard group + #[tdf(tag = "NAME")] pub name: String, } -impl Decodable for LeaderboardGroupRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let name: String = reader.tag(b"NAME")?; - Ok(Self { name }) - } -} - /// Structure for a leaderboard group response. pub struct LeaderboardGroupResponse<'a> { pub name: String, @@ -226,41 +201,38 @@ pub struct LeaderboardGroupResponse<'a> { pub gname: &'a str, } -impl Encodable for LeaderboardGroupResponse<'_> { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u8(b"ACSD", 0); - writer.tag_str(b"BNAM", &self.name); - writer.tag_str(b"DESC", &self.desc); - writer.tag_pair(b"ETYP", (0x7802, 0x1)); +impl TdfSerialize for LeaderboardGroupResponse<'_> { + fn serialize(&self, w: &mut S) { + w.tag_u8(b"ACSD", 0); + w.tag_str(b"BNAM", &self.name); + w.tag_str(b"DESC", &self.desc); + w.tag_alt(b"ETYP", PLAYER_TYPE); + { - writer.tag_map_start(b"KSUM", TdfType::String, TdfType::Group, 1); - writer.write_str("accountcountry"); - { - writer.tag_map_start(b"KSVL", TdfType::VarInt, TdfType::VarInt, 1); - writer.write_byte(0); - writer.write_byte(0); - writer.tag_group_end(); - } + w.tag_map_start(b"KSUM", TdfType::String, TdfType::Group, 1); + "accountcountry".serialize(w); + w.group_body(|w| { + w.tag_map_tuples(b"KSVL", &[(0u8, 0u8)]); + }); } - writer.tag_u32(b"LBSZ", 0x7270e0); + w.tag_u32(b"LBSZ", 0x7270e0); { - writer.tag_list_start(b"LIST", TdfType::Group, 1); - { - writer.tag_str(b"CATG", "MassEffectStats"); - writer.tag_str(b"DFLT", "0"); - writer.tag_u8(b"DRVD", 0x0); - writer.tag_str(b"FRMT", "%d"); - writer.tag_str(b"KIND", ""); - writer.tag_str(b"LDSC", self.sdsc); - writer.tag_str(b"META", "W=200, HMC=tableColHeader3, REMC=tableRowEntry3"); - writer.tag_str(b"NAME", self.sname); - writer.tag_str(b"SDSC", self.sdsc); - writer.tag_u8(b"TYPE", 0x0); - writer.tag_group_end(); - } + w.tag_list_start(b"LIST", TdfType::Group, 1); + w.group_body(|w| { + w.tag_str(b"CATG", "MassEffectStats"); + w.tag_str(b"DFLT", "0"); + w.tag_u8(b"DRVD", 0x0); + w.tag_str(b"FRMT", "%d"); + w.tag_str(b"KIND", ""); + w.tag_str(b"LDSC", self.sdsc); + w.tag_str(b"META", "W=200, HMC=tableColHeader3, REMC=tableRowEntry3"); + w.tag_str(b"NAME", self.sname); + w.tag_str(b"SDSC", self.sdsc); + w.tag_u8(b"TYPE", 0x0); + }); } - writer. tag_str(b"META", "RF=@W=150, HMC=tableColHeader1, REMC=tableRowEntry1@ UF=@W=670, HMC=tableColHeader2, REMC=tableRowEntry2@"); - writer.tag_str(b"NAME", self.gname); - writer.tag_str(b"SNAM", self.sname); + w.tag_str(b"META", "RF=@W=150, HMC=tableColHeader1, REMC=tableRowEntry1@ UF=@W=670, HMC=tableColHeader2, REMC=tableRowEntry2@"); + w.tag_str(b"NAME", self.gname); + w.tag_str(b"SNAM", self.sname); } } diff --git a/src/session/models/user_sessions.rs b/src/session/models/user_sessions.rs index 712bd9d0..958eebee 100644 --- a/src/session/models/user_sessions.rs +++ b/src/session/models/user_sessions.rs @@ -1,68 +1,40 @@ use crate::utils::{ - models::{NetGroups, QosNetworkData}, + models::{NetworkAddress, QosNetworkData}, types::PlayerID, }; -use blaze_pk::{ - codec::Decodable, - error::{DecodeError, DecodeResult}, - reader::TdfReader, - types::Union, -}; +use tdf::TdfDeserialize; /// Structure for a request to resume a session using a session token +#[derive(TdfDeserialize)] pub struct ResumeSessionRequest { /// The session token to use + #[tdf(tag = "SKEY")] pub session_token: String, } -impl Decodable for ResumeSessionRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let session_token: String = reader.tag(b"SKEY")?; - Ok(Self { session_token }) - } -} - /// Structure for a request to update the network info of the /// current session +#[derive(TdfDeserialize)] pub struct UpdateNetworkRequest { /// The client address net groups - pub address: NetGroups, + #[tdf(tag = "ADDR")] + pub address: NetworkAddress, /// The client Quality of Service data + #[tdf(tag = "NQOS")] pub qos: QosNetworkData, } -impl Decodable for UpdateNetworkRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let address: NetGroups = match reader.tag::>(b"ADDR")? { - Union::Set { value, .. } => value, - Union::Unset => return Err(DecodeError::Other("Client address was unset")), - }; - let qos: QosNetworkData = reader.tag(b"NQOS")?; - Ok(Self { address, qos }) - } -} - /// Structure for request to update the hardware flags of the /// current session +#[derive(TdfDeserialize)] pub struct HardwareFlagRequest { /// The hardware flag value + #[tdf(tag = "HWFG")] pub hardware_flag: u16, } -impl Decodable for HardwareFlagRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let hardware_flag: u16 = reader.tag(b"HWFG")?; - Ok(Self { hardware_flag }) - } -} - +#[derive(TdfDeserialize)] pub struct LookupRequest { + #[tdf(tag = "ID")] pub player_id: PlayerID, } - -impl Decodable for LookupRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let player_id: PlayerID = reader.tag(b"ID")?; - Ok(Self { player_id }) - } -} diff --git a/src/session/models/util.rs b/src/session/models/util.rs index 81dd9e4c..c15a0f9d 100644 --- a/src/session/models/util.rs +++ b/src/session/models/util.rs @@ -2,15 +2,18 @@ use crate::{ session::SessionHostTarget, utils::{models::Port, types::PlayerID}, }; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeResult, - reader::TdfReader, - tag::TdfType, - types::TdfMap, - writer::TdfWriter, -}; + use std::borrow::Cow; +use tdf::{TdfDeserialize, TdfMap, TdfSerialize, TdfType}; + +#[derive(Debug, Clone)] +#[repr(u16)] +#[allow(unused)] +pub enum UtilError { + SuspendPingTimeTooLarge = 0x12c, + SuspendPingTimeTooSmall = 0x12d, + PingSuspended = 0x12e, +} /// Possibly regions that the telemetry server is disabled for? pub const TELEMTRY_DISA: &str = "AD,AF,AG,AI,AL,AM,AN,AO,AQ,AR,AS,AW,AX,AZ,BA,BB,BD,BF,BH,BI,BJ,BM,BN,BO,BR,BS,BT,BV,BW,BY,BZ,CC,CD,CF,CG,CI,CK,CL,CM,CN,CO,CR,CU,CV,CX,DJ,DM,DO,DZ,EC,EG,EH,ER,ET,FJ,FK,FM,FO,GA,GD,GE,GF,GG,GH,GI,GL,GM,GN,GP,GQ,GS,GT,GU,GW,GY,HM,HN,HT,ID,IL,IM,IN,IO,IQ,IR,IS,JE,JM,JO,KE,KG,KH,KI,KM,KN,KP,KR,KW,KY,KZ,LA,LB,LC,LI,LK,LR,LS,LY,MA,MC,MD,ME,MG,MH,ML,MM,MN,MO,MP,MQ,MR,MS,MU,MV,MW,MY,MZ,NA,NC,NE,NF,NG,NI,NP,NR,NU,OM,PA,PE,PF,PG,PH,PK,PM,PN,PS,PW,PY,QA,RE,RS,RW,SA,SB,SC,SD,SG,SH,SJ,SL,SM,SN,SO,SR,ST,SV,SY,SZ,TC,TD,TF,TG,TH,TJ,TK,TL,TM,TN,TO,TT,TV,TZ,UA,UG,UM,UY,UZ,VA,VC,VE,VG,VN,VU,WF,WS,YE,YT,ZM,ZW,ZZ"; @@ -36,25 +39,25 @@ pub const LOCAL_HTTP_PORT: Port = 42131; /// Structure for encoding the telemetry server details pub struct TelemetryServer; -impl Encodable for TelemetryServer { - fn encode(&self, writer: &mut TdfWriter) { - writer.group(b"TELE", |writer| { +impl TdfSerialize for TelemetryServer { + fn serialize(&self, w: &mut S) { + w.group(b"TELE", |w| { // Last known telemetry addresses: 159.153.235.32, gostelemetry.blaze3.ea.com - writer.tag_str(b"ADRS", "127.0.0.1"); - writer.tag_zero(b"ANON"); - writer.tag_str(b"DISA", TELEMTRY_DISA); - writer.tag_str(b"FILT", "-UION/****"); - writer.tag_u32(b"LOC", 1701727834); - writer.tag_str(b"NOOK", "US,CA,MX"); + w.tag_str(b"ADRS", "127.0.0.1"); + w.tag_zero(b"ANON"); + w.tag_str(b"DISA", TELEMTRY_DISA); + w.tag_str(b"FILT", "-UION/****"); + w.tag_u32(b"LOC", 1701727834); + w.tag_str(b"NOOK", "US,CA,MX"); // Last known telemetry port: 9988 - writer.tag_u16(b"PORT", TELEMETRY_PORT); - writer.tag_u16(b"SDLY", 15000); - writer.tag_str(b"SESS", "pcwdjtOCVpD"); + w.tag_owned(b"PORT", TELEMETRY_PORT); + w.tag_u16(b"SDLY", 15000); + w.tag_str(b"SESS", "pcwdjtOCVpD"); let key: Cow = String::from_utf8_lossy(TELEMETRY_KEY); - writer.tag_str(b"SKEY", &key); - writer.tag_u8(b"SPCT", 75); - writer.tag_str_empty(b"STIM"); + w.tag_str(b"SKEY", &key); + w.tag_u8(b"SPCT", 75); + w.tag_str_empty(b"STIM"); }); } } @@ -66,9 +69,9 @@ const TICKER_KEY: &str = "1,10.23.15.2:8999,masseffect-3-pc,10,50,50,50,50,0,12" /// Structure for encoding the ticker server details pub struct TickerServer; -impl Encodable for TickerServer { - fn encode(&self, writer: &mut TdfWriter) { - writer.group(b"TICK", |writer| { +impl TdfSerialize for TickerServer { + fn serialize(&self, w: &mut S) { + w.group(b"TICK", |writer| { // Last known ticker addresses: 10.23.15.2, 10.10.78.150 writer.tag_str(b"ADRS", "127.0.0.1"); // Last known ticker port: 8999 @@ -88,46 +91,44 @@ pub struct PreAuthResponse { pub host_target: SessionHostTarget, } -impl Encodable for PreAuthResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_zero(b"ANON"); - writer.tag_str(b"ASRC", SRC_VERSION); +impl TdfSerialize for PreAuthResponse { + fn serialize(&self, w: &mut S) { + w.tag_zero(b"ANON"); + w.tag_str(b"ASRC", SRC_VERSION); // This list appears to contain the IDs of the components that the game // uses throughout its lifecycle - writer.tag_slice_list( + w.tag_list_slice( b"CIDS", &[ 0x1, 0x19, 0x4, 0x1c, 0x7, 0x9, 0xf802, 0x7800, 0xf, 0x7801, 0x7802, 0x7803, 0x7805, 0x7806, 0x7d0, ], ); - writer.tag_str_empty(b"CNGN"); + w.tag_str_empty(b"CNGN"); // Double nested map containing configuration options for // ping intervals and VOIP headset update rates - writer.group(b"CONF", |writer| { - writer.tag_map_start(b"CONF", TdfType::String, TdfType::String, 3); - - writer.write_str("pingPeriod"); - writer.write_str(PING_PERIOD); - - writer.write_str("voipHeadsetUpdateRate"); - writer.write_str("1000"); - - // XLSP (Xbox Live Server Platform) - writer.write_str("xlspConnectionIdleTimeout"); - writer.write_str("300"); + w.group(b"CONF", |w| { + w.tag_map_tuples( + b"CONF", + &[ + ("pingPeriod", PING_PERIOD), + ("voipHeadsetUpdateRate", "1000"), + // XLSP (Xbox Live Server Platform) + ("xlspConnectionIdleTimeout", "300"), + ], + ); }); - writer.tag_str(b"INST", "masseffect-3-pc"); - writer.tag_zero(b"MINR"); - writer.tag_str(b"NASP", "cem_ea_id"); - writer.tag_str_empty(b"PILD"); - writer.tag_str(b"PLAT", "pc"); - writer.tag_str_empty(b"PTAG"); + w.tag_str(b"INST", "masseffect-3-pc"); + w.tag_zero(b"MINR"); + w.tag_str(b"NASP", "cem_ea_id"); + w.tag_str_empty(b"PILD"); + w.tag_str(b"PLAT", "pc"); + w.tag_str_empty(b"PTAG"); // Quality Of Service Server details - writer.group(b"QOSS", |writer| { + w.group(b"QOSS", |w| { let (http_host, http_port) = if self.host_target.local_http { ("127.0.0.1", LOCAL_HTTP_PORT) } else { @@ -135,37 +136,38 @@ impl Encodable for PreAuthResponse { }; // Bioware Primary Server - writer.group(b"BWPS", |writer| { - writer.tag_str(b"PSA", http_host); - writer.tag_u16(b"PSP", http_port); - writer.tag_str(b"SNA", "prod-sjc"); + w.group(b"BWPS", |w| { + w.tag_str(b"PSA", http_host); + w.tag_u16(b"PSP", http_port); + w.tag_str(b"SNA", "prod-sjc"); }); - writer.tag_u8(b"LNP", 10); + w.tag_u8(b"LNP", 10); // List of other Quality Of Service servers? Values present in this // list are later included in a ping list { - writer.tag_map_start(b"LTPS", TdfType::String, TdfType::Group, 1); + w.tag_map_start(b"LTPS", TdfType::String, TdfType::Group, 1); // Key for the server - writer.write_str("ea-sjc"); - - // Same as the Bioware primary server - writer.tag_str(b"PSA", http_host); - writer.tag_u16(b"PSP", http_port); - writer.tag_str(b"SNA", "prod-sjc"); - writer.tag_group_end(); + "ea-sjc".serialize(w); + + w.group_body(|w| { + // Same as the Bioware primary server + w.tag_str(b"PSA", http_host); + w.tag_u16(b"PSP", http_port); + w.tag_str(b"SNA", "prod-sjc"); + }); } // Possibly server version ID (1161889797) - writer.tag_u32(b"SVID", 0x45410805); + w.tag_u32(b"SVID", 0x45410805); }); // Server src version - writer.tag_str(b"RSRC", SRC_VERSION); + w.tag_str(b"RSRC", SRC_VERSION); // Server blaze version - writer.tag_str(b"SVER", BLAZE_VERSION) + w.tag_str(b"SVER", BLAZE_VERSION) } } @@ -179,111 +181,79 @@ pub struct PostAuthResponse { pub player_id: PlayerID, } -impl Encodable for PostAuthResponse { - fn encode(&self, writer: &mut TdfWriter) { +impl TdfSerialize for PostAuthResponse { + fn serialize(&self, w: &mut S) { // Player Sync Service server details - writer.group(b"PSS", |writer| { - writer.tag_str(b"ADRS", "playersyncservice.ea.com"); - writer.tag_empty_blob(b"CSIG"); - writer.tag_str(b"PJID", SRC_VERSION); - writer.tag_u16(b"PORT", 443); - writer.tag_u8(b"RPRT", 0xF); - writer.tag_u8(b"TIID", 0); + w.group(b"PSS", |w| { + w.tag_str(b"ADRS", "playersyncservice.ea.com"); + w.tag_blob_empty(b"CSIG"); + w.tag_str(b"PJID", SRC_VERSION); + w.tag_u16(b"PORT", 443); + w.tag_u8(b"RPRT", 0xF); + w.tag_u8(b"TIID", 0); }); // Ticker & Telemtry server options - self.telemetry.encode(writer); - self.ticker.encode(writer); + self.telemetry.serialize(w); + self.ticker.serialize(w); // User options - writer.group(b"UROP", |writer| { - writer.tag_u8(b"TMOP", 1); - writer.tag_u32(b"UID", self.player_id); + w.group(b"UROP", |w| { + w.tag_u8(b"TMOP", 1); + w.tag_u32(b"UID", self.player_id); }); } } /// Structure for the response to a ping request +#[derive(TdfSerialize)] pub struct PingResponse { /// The number of seconds elapsed since the Unix Epoc + #[tdf(tag = "STIM")] pub server_time: u64, } -impl Encodable for PingResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u64(b"STIM", self.server_time) - } -} - /// Structure for the request to fetch a specific config +#[derive(TdfDeserialize)] pub struct FetchConfigRequest { /// The ID for the config + #[tdf(tag = "CFID")] pub id: String, } -impl Decodable for FetchConfigRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let id: String = reader.tag(b"CFID")?; - Ok(Self { id }) - } -} - /// Structure for the response to fetching a config +#[derive(TdfSerialize)] pub struct FetchConfigResponse { /// The configuration map + #[tdf(tag = "CONF")] pub config: TdfMap, } -impl Encodable for FetchConfigResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"CONF", &self.config) - } -} - /// Structure for the suspend user ping request +#[derive(TdfDeserialize)] pub struct SuspendPingRequest { /// The suspend ping value + #[tdf(tag = "TVAL")] pub value: u32, } -impl Decodable for SuspendPingRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let value: u32 = reader.tag(b"TVAL")?; - Ok(Self { value }) - } -} - /// Structure for the request to update the settings for /// the current player + +#[derive(TdfDeserialize)] pub struct SettingsSaveRequest { - /// The key to update - pub key: String, /// The new value for the key + #[tdf(tag = "DATA")] pub value: String, -} -impl Decodable for SettingsSaveRequest { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let value: String = reader.tag(b"DATA")?; - let key: String = reader.tag(b"KEY")?; - Ok(Self { key, value }) - } + /// The key to update + #[tdf(tag = "KEY")] + pub key: String, } /// Structure for the response to loading all the settings +#[derive(TdfDeserialize, TdfSerialize)] pub struct SettingsResponse { /// The settings map + #[tdf(tag = "SMAP")] pub settings: TdfMap, } - -impl Encodable for SettingsResponse { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"SMAP", &self.settings); - } -} - -impl Decodable for SettingsResponse { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let settings: TdfMap = reader.tag(b"SMAP")?; - Ok(Self { settings }) - } -} diff --git a/src/session/packet.rs b/src/session/packet.rs new file mode 100644 index 00000000..0ee8b0f6 --- /dev/null +++ b/src/session/packet.rs @@ -0,0 +1,422 @@ +#![allow(unused)] + +use crate::utils::components::{get_command_name, get_component_name}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use std::{fmt::Debug, sync::Arc}; +use std::{io, ops::Deref}; +use tdf::{ + serialize_vec, DecodeResult, TdfDeserialize, TdfDeserializer, TdfSerialize, TdfStringifier, +}; +use tokio_util::codec::{Decoder, Encoder}; + +/// The different types of packets +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum PacketType { + /// ID counted request packets (0x00) + Request = 0x00, + /// Packets responding to requests (0x10) + Response = 0x10, + /// Unique packets coming from the server (0x20) + Notify = 0x20, + /// Error packets (0x30) + Error = 0x30, +} + +/// From u8 implementation to convert bytes back into +/// PacketTypes +impl From for PacketType { + fn from(value: u8) -> Self { + match value { + 0x00 => PacketType::Request, + 0x10 => PacketType::Response, + 0x20 => PacketType::Notify, + 0x30 => PacketType::Error, + // Default type fallback to request + _ => PacketType::Request, + } + } +} + +/// Structure of packet header which comes before the +/// packet content and describes it. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct PacketHeader { + /// The component of this packet + pub component: u16, + /// The command of this packet + pub command: u16, + /// A possible error this packet contains (zero is none) + pub error: u16, + /// The type of this packet + pub ty: PacketType, + /// The unique ID of this packet (Notify packets this is just zero) + pub id: u16, +} + +impl PacketHeader { + /// Creates a notify header for the provided component and command + /// + /// `component` The component to use + /// `command` The command to use + pub const fn notify(component: u16, command: u16) -> Self { + Self { + component, + command, + error: 0, + ty: PacketType::Notify, + id: 0, + } + } + + /// Creates a request header for the provided id, component + /// and command + /// + /// `id` The packet ID + /// `component` The component to use + /// `command` The command to use + pub const fn request(id: u16, component: u16, command: u16) -> Self { + Self { + component, + command, + error: 0, + ty: PacketType::Request, + id, + } + } + + /// Creates a response to the provided packet header by + /// changing the type of the header + pub const fn response(&self) -> Self { + self.with_type(PacketType::Response) + } + + /// Copies the header contents changing its Packet Type + /// + /// `ty` The new packet type + pub const fn with_type(&self, ty: PacketType) -> Self { + Self { + component: self.component, + command: self.command, + error: self.error, + ty, + id: self.id, + } + } + + /// Copies the header contents changing its Packet Type + pub const fn with_error(&self, error: u16) -> Self { + Self { + component: self.component, + command: self.command, + error, + ty: PacketType::Error, + id: self.id, + } + } + + /// Checks if the component and command of this packet header matches + /// that of the other packet header + /// + /// `other` The packet header to compare to + pub fn path_matches(&self, other: &PacketHeader) -> bool { + self.component.eq(&other.component) && self.command.eq(&other.command) + } + + /// Encodes the contents of this header appending to the + /// output source + /// + /// `dst` The dst to append the bytes to + /// `length` The length of the content after the header + pub fn write(&self, dst: &mut BytesMut, length: usize) { + let is_extended = length > 0xFFFF; + dst.put_u16(length as u16); + dst.put_u16(self.component); + dst.put_u16(self.command); + dst.put_u16(self.error); + dst.put_u8(self.ty as u8); + dst.put_u8(if is_extended { 0x10 } else { 0x00 }); + dst.put_u16(self.id); + if is_extended { + dst.put_u8(((length & 0xFF000000) >> 24) as u8); + dst.put_u8(((length & 0x00FF0000) >> 16) as u8); + } + } + + /// Attempts to read the packet header from the provided + /// source bytes returning None if there aren't enough bytes + /// + /// `src` The bytes to read from + pub fn read(src: &mut BytesMut) -> Option<(PacketHeader, usize)> { + if src.len() < 12 { + return None; + } + + let mut length = src.get_u16() as usize; + let component = src.get_u16(); + let command = src.get_u16(); + let error = src.get_u16(); + let ty = src.get_u8(); + // If we encounter 0x10 here then the packet contains extended length + // bytes so its longer than a u16::MAX length + let is_extended = src.get_u8() == 0x10; + let id = src.get_u16(); + + if is_extended { + // We need another two bytes for the extended length + if src.len() < 2 { + return None; + } + length += src.get_u16() as usize; + } + + let ty = PacketType::from(ty); + let header = PacketHeader { + component, + command, + error, + ty, + id, + }; + Some((header, length)) + } +} + +/// Structure for Blaze packets contains the contents of the packet +/// and the header for identification. +/// +/// Packets can be cloned with little memory usage increase because +/// the content is stored as Bytes. +#[derive(Debug, Clone)] +pub struct Packet { + /// The packet header + pub header: PacketHeader, + /// The packet encoded byte contents + pub contents: Bytes, +} + +fn serialize_bytes(value: &V) -> Bytes +where + V: TdfSerialize, +{ + Bytes::from(serialize_vec(value)) +} + +impl Packet { + /// Creates a new packet from the provided header and contents + pub const fn new(header: PacketHeader, contents: Bytes) -> Self { + Self { header, contents } + } + + /// Creates a new packet from the provided header with empty content + #[inline] + pub const fn new_empty(header: PacketHeader) -> Self { + Self::new(header, Bytes::new()) + } + + #[inline] + pub const fn new_request(id: u16, component: u16, command: u16, contents: Bytes) -> Packet { + Self::new(PacketHeader::request(id, component, command), contents) + } + + #[inline] + pub const fn new_response(packet: &Packet, contents: Bytes) -> Self { + Self::new(packet.header.response(), contents) + } + + #[inline] + pub const fn new_error(packet: &Packet, error: u16, contents: Bytes) -> Self { + Self::new(packet.header.with_error(error), contents) + } + + #[inline] + pub const fn new_notify(component: u16, command: u16, contents: Bytes) -> Packet { + Self::new(PacketHeader::notify(component, command), contents) + } + + #[inline] + pub const fn request_empty(id: u16, component: u16, command: u16) -> Packet { + Self::new_empty(PacketHeader::request(id, component, command)) + } + + #[inline] + pub const fn response_empty(packet: &Packet) -> Self { + Self::new_empty(packet.header.response()) + } + + #[inline] + pub const fn error_empty(packet: &Packet, error: u16) -> Packet { + Self::new_empty(packet.header.with_error(error)) + } + + #[inline] + pub const fn notify_empty(component: u16, command: u16) -> Packet { + Self::new_empty(PacketHeader::notify(component, command)) + } + + #[inline] + pub fn response(packet: &Packet, contents: V) -> Self + where + V: TdfSerialize, + { + Self::new_response(packet, serialize_bytes(&contents)) + } + + #[inline] + pub fn error(packet: &Packet, error: u16, contents: V) -> Self + where + V: TdfSerialize, + { + Self::new_error(packet, error, serialize_bytes(&contents)) + } + + #[inline] + pub fn notify(component: u16, command: u16, contents: V) -> Packet + where + V: TdfSerialize, + { + Self::new_notify(component, command, serialize_bytes(&contents)) + } + + #[inline] + pub fn request(id: u16, component: u16, command: u16, contents: V) -> Packet + where + V: TdfSerialize, + { + Self::new_request(id, component, command, serialize_bytes(&contents)) + } + + /// Attempts to deserialize the packet contents as the provided type + pub fn deserialize<'de, V>(&'de self) -> DecodeResult + where + V: TdfDeserialize<'de>, + { + let mut r = TdfDeserializer::new(&self.contents); + V::deserialize(&mut r) + } + + pub fn read(src: &mut BytesMut) -> Option { + let (header, length) = PacketHeader::read(src)?; + + if src.len() < length { + return None; + } + + let contents = src.split_to(length); + Some(Self { + header, + contents: contents.freeze(), + }) + } + + pub fn write(&self, dst: &mut BytesMut) { + let contents = &self.contents; + self.header.write(dst, contents.len()); + dst.extend_from_slice(contents); + } +} + +/// Tokio codec for encoding and decoding packets +pub struct PacketCodec; + +impl Decoder for PacketCodec { + type Error = io::Error; + type Item = Packet; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let mut read_src = src.clone(); + let result = Packet::read(&mut read_src); + + if result.is_some() { + *src = read_src; + } + + Ok(result) + } +} + +impl Encoder for PacketCodec { + type Error = io::Error; + + fn encode(&mut self, item: Packet, dst: &mut BytesMut) -> Result<(), Self::Error> { + item.write(dst); + Ok(()) + } +} + +/// Wrapper over a packet structure to provde debug logging +/// with names resolved for the component +pub struct PacketDebug<'a> { + /// Reference to the packet itself + pub packet: &'a Packet, + + /// Decide whether to display the contents of the packet + pub minified: bool, +} + +impl<'a> Debug for PacketDebug<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Append basic header information + let header = &self.packet.header; + + let component_name = get_component_name(header.component); + let command_name = get_command_name( + header.component, + header.command, + matches!(&header.ty, PacketType::Notify), + ); + + match (component_name, command_name) { + (Some(component), Some(command)) => { + writeln!(f, "Component: {}({})", component, command)?; + } + (Some(component), None) => { + writeln!(f, "Component: {}({:#06x})", component, header.command)?; + } + _ => { + writeln!( + f, + "Component: {:#06x}({:#06x})", + header.component, header.command + )?; + } + } + + writeln!(f, "Type: {:?}", header.ty)?; + + if !matches!(&header.ty, PacketType::Notify) { + writeln!(f, "ID: {}", &header.id)?; + } + + if let PacketType::Error = &header.ty { + writeln!(f, "Error: {:#06x}", &header.error)?; + } + + // Skip remaining if the message shouldn't contain its content + if self.minified { + return Ok(()); + } + + let mut r = TdfDeserializer::new(&self.packet.contents); + let mut out = String::new(); + out.push_str("{\n"); + let mut str = TdfStringifier::new(r, &mut out); + + // Stringify the content or append error instead + if !str.stringify() { + writeln!(f, "Content Error: Content was malformed or not parsible")?; + writeln!(f, "Partial Content: {}", out)?; + writeln!(f, "Raw: {:?}", &self.packet.contents)?; + return Ok(()); + } + + if out.len() == 2 { + // Remove new line if nothing else was appended + out.pop(); + } + + out.push('}'); + + write!(f, "Content: {}", out) + } +} diff --git a/src/session/router.rs b/src/session/router.rs new file mode 100644 index 00000000..840b55b4 --- /dev/null +++ b/src/session/router.rs @@ -0,0 +1,461 @@ +//! Router implementation for routing packet components to different functions +//! and automatically decoding the packet contents to the function type + +use super::{ + models::errors::BlazeError, + packet::{Packet, PacketHeader}, + SessionLink, +}; +use crate::{ + session::models::errors::GlobalError, + utils::{ + components::{component_key, ComponentKey}, + types::BoxFuture, + }, +}; +use bytes::Bytes; +use log::error; +use std::{ + any::{Any, TypeId}, + collections::HashMap, + convert::Infallible, + future::{ready, Future}, + hash::{BuildHasherDefault, Hasher}, + marker::PhantomData, + sync::Arc, +}; +use tdf::{serialize_vec, TdfDeserialize, TdfDeserializer, TdfSerialize}; + +pub trait Handler: Send + Sync + 'static { + fn handle(&self, req: PacketRequest) -> BoxFuture<'_, Packet>; +} + +/// Wrapper around [Handler] that stores the required associated +/// generic types allowing it to have its typed erased using [ErasedHandler] +struct HandlerRoute { + /// The wrapped handler + handler: H, + /// The associated type info + _marker: PhantomData Res>, +} + +/// Wrapper around [Handler] that erasings the associated generic types +/// so that it can be stored within the [Router] +trait ErasedHandler: Send + Sync { + fn handle(&self, req: PacketRequest) -> BoxFuture<'_, Packet>; +} + +/// Erased handler implementation for all [Handler] implementations using [HandlerRoute] +impl ErasedHandler for HandlerRoute +where + H: Handler, + Args: 'static, + Res: 'static, +{ + #[inline] + fn handle(&self, req: PacketRequest) -> BoxFuture<'_, Packet> { + self.handler.handle(req) + } +} + +/// +pub struct PacketRequest { + pub state: SessionLink, + pub packet: Packet, + pub extensions: Arc, +} + +impl PacketRequest { + pub fn extension(&self) -> Option<&T> { + self.extensions + .get(&TypeId::of::()) + .and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) + } +} + +type AnyMap = HashMap, BuildHasherDefault>; + +pub struct BlazeRouterBuilder { + /// Map for looking up a route based on the component key + routes: HashMap, BuildHasherDefault>, + extensions: AnyMap, +} + +impl BlazeRouterBuilder { + pub fn new() -> Self { + Self { + routes: Default::default(), + extensions: Default::default(), + } + } + + pub fn add_extension(&mut self, val: T) -> Option { + self.extensions + .insert(TypeId::of::(), Box::new(val)) + .and_then(|boxed| { + (boxed as Box) + .downcast() + .ok() + .map(|boxed| *boxed) + }) + } + + pub fn route(&mut self, component: u16, command: u16, route: impl Handler) + where + Args: 'static, + Res: 'static, + { + self.routes.insert( + component_key(component, command), + Box::new(HandlerRoute { + handler: route, + _marker: PhantomData, + }), + ); + } + + pub fn build(self) -> Arc { + Arc::new(BlazeRouter { + routes: self.routes, + extensions: Arc::new(self.extensions), + }) + } +} + +pub struct BlazeRouter { + /// Map for looking up a route based on the component key + routes: HashMap, BuildHasherDefault>, + extensions: Arc, +} + +impl BlazeRouter { + pub fn handle( + &self, + state: SessionLink, + packet: Packet, + ) -> Result, Packet> { + let route = match self.routes.get(&component_key( + packet.header.component, + packet.header.command, + )) { + Some(value) => value, + None => return Err(packet), + }; + + Ok(route.handle(PacketRequest { + state, + packet, + extensions: self.extensions.clone(), + })) + } +} + +/// "Hasher" used by the router map that just directly stores the integer value +/// from the component key as no hashing is required +#[derive(Default)] +pub struct ComponentKeyHasher(u32); + +impl Hasher for ComponentKeyHasher { + fn finish(&self) -> u64 { + self.0 as u64 + } + + fn write(&mut self, _bytes: &[u8]) { + panic!("Attempted to use component key hasher to hash bytes") + } + + fn write_u32(&mut self, i: u32) { + self.0 = i; + } +} + +// With TypeIds as keys, there's no need to hash them. They are already hashes +// themselves, coming from the compiler. The IdHasher just holds the u64 of +// the TypeId, and then returns it, instead of doing any bit fiddling. +#[derive(Default)] +pub struct IdHasher(u64); + +impl Hasher for IdHasher { + fn write(&mut self, _: &[u8]) { + panic!("Attempted to use id hasher to hash bytes") + } + + #[inline] + fn write_u64(&mut self, id: u64) { + self.0 = id; + } + + #[inline] + fn finish(&self) -> u64 { + self.0 + } +} + +pub trait FromPacketRequest: Sized { + type Rejection: IntoPacketResponse; + + fn from_packet_request<'a>( + req: &'a PacketRequest, + ) -> BoxFuture<'a, Result> + where + Self: 'a; +} + +/// Wrapper for providing deserialization [FromPacketRequest] and +/// serialization [IntoPacketResponse] for TDF contents +pub struct Blaze(pub V); + +/// Wrapper for providing deserialization [FromPacketRequest] and +/// serialization [IntoPacketResponse] for TDF contents +/// +/// Stores the packet header so that it can be used for generating +/// responses +pub struct BlazeWithHeader { + pub req: V, + pub header: PacketHeader, +} + +/// [Blaze] tdf type for contents that have already been +/// serialized ahead of time +pub struct RawBlaze(Bytes); + +pub struct Extension(pub T); + +impl FromPacketRequest for Extension +where + T: Clone + Send + Sync + 'static, +{ + type Rejection = BlazeError; + + fn from_packet_request<'a>( + req: &'a PacketRequest, + ) -> BoxFuture<'a, Result> + where + Self: 'a, + { + Box::pin(ready( + req.extension() + .ok_or_else(|| { + error!( + "Attempted to extract missing extension {}", + std::any::type_name::() + ); + GlobalError::System.into() + }) + .cloned() + .map(Extension), + )) + } +} + +impl From for RawBlaze +where + T: TdfSerialize, +{ + fn from(value: T) -> Self { + let bytes = serialize_vec(&value); + let bytes = Bytes::from(bytes); + RawBlaze(bytes) + } +} + +impl FromPacketRequest for Blaze +where + for<'a> V: TdfDeserialize<'a> + Send + 'a, +{ + type Rejection = BlazeError; + + fn from_packet_request<'a>( + req: &'a PacketRequest, + ) -> BoxFuture<'a, Result> + where + Self: 'a, + { + Box::pin(ready( + req.packet + .deserialize::<'a, V>() + .map_err(|err| { + error!("Error while decoding packet: {:?}", err); + GlobalError::System.into() + }) + .map(Blaze), + )) + } +} + +impl BlazeWithHeader { + pub fn response(&self, res: E) -> Packet + where + E: TdfSerialize, + { + Packet { + header: self.header.response(), + contents: Bytes::from(serialize_vec(&res)), + } + } +} + +impl FromPacketRequest for BlazeWithHeader +where + for<'a> V: TdfDeserialize<'a> + Send + 'a, +{ + type Rejection = BlazeError; + + fn from_packet_request<'a>( + req: &'a PacketRequest, + ) -> BoxFuture<'a, Result> + where + Self: 'a, + { + let mut r = TdfDeserializer::new(&req.packet.contents); + + Box::pin(ready( + V::deserialize(&mut r) + .map(|value| BlazeWithHeader { + req: value, + header: req.packet.header, + }) + .map_err(|err| { + error!("Error while decoding packet: {:?}", err); + GlobalError::System.into() + }), + )) + } +} + +impl FromPacketRequest for SessionLink { + type Rejection = Infallible; + + fn from_packet_request<'a>( + req: &'a PacketRequest, + ) -> BoxFuture<'a, Result> + where + Self: 'a, + { + let state = req.state.clone(); + Box::pin(ready(Ok(state))) + } +} + +pub trait IntoPacketResponse: 'static { + fn into_response(self, req: &Packet) -> Packet; +} + +impl IntoPacketResponse for () { + fn into_response(self, req: &Packet) -> Packet { + Packet::response_empty(req) + } +} + +impl IntoPacketResponse for Infallible { + fn into_response(self, _: &Packet) -> Packet { + // Infallible can never be constructed so this can never happen + unreachable!() + } +} + +impl IntoPacketResponse for Packet { + fn into_response(self, _req: &Packet) -> Packet { + self + } +} + +impl IntoPacketResponse for Blaze +where + V: TdfSerialize + 'static, +{ + fn into_response(self, req: &Packet) -> Packet { + Packet::response(req, self.0) + } +} + +impl IntoPacketResponse for RawBlaze { + fn into_response(self, req: &Packet) -> Packet { + Packet::new_response(req, self.0) + } +} + +impl IntoPacketResponse for Result +where + T: IntoPacketResponse, + E: IntoPacketResponse, +{ + fn into_response(self, req: &Packet) -> Packet { + match self { + Ok(value) => value.into_response(req), + Err(value) => value.into_response(req), + } + } +} + +impl IntoPacketResponse for Option +where + V: IntoPacketResponse, +{ + fn into_response(self, req: &Packet) -> Packet { + match self { + Some(value) => value.into_response(req), + None => Packet::response_empty(req), + } + } +} + +// Macro for expanding a macro for every tuple variant +#[rustfmt::skip] +macro_rules! all_the_tuples { + ($name:ident) => { + $name!([]); + $name!([T1]); + $name!([T1, T2]); + $name!([T1, T2, T3]); + $name!([T1, T2, T3, T4]); + $name!([T1, T2, T3, T4, T5]); + $name!([T1, T2, T3, T4, T5, T6]); + $name!([T1, T2, T3, T4, T5, T6, T7]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]); + }; +} + +// Macro for implementing a handler for a tuple of arguments +macro_rules! impl_handler { + ( + [$($ty:ident),*] + ) => { + + #[allow(non_snake_case, unused_mut)] + impl Handler<($($ty,)*), Res> for Fun + where + Fun: Fn($($ty),*) -> Fut + Send + Sync + 'static, + Fut: Future + Send, + $( $ty: FromPacketRequest + Send, )* + Res: IntoPacketResponse, + { + fn handle(&self, req: PacketRequest) -> BoxFuture<'_, Packet> + { + Box::pin(async move { + let req = req; + $( + + let $ty = match $ty::from_packet_request(&req).await { + Ok(value) => value, + Err(rejection) => return rejection.into_response(&req.packet), + }; + )* + + let res = self($($ty),* ).await; + res.into_response(&req.packet) + }) + } + } + }; +} + +// Implement a handler for every tuple +all_the_tuples!(impl_handler); diff --git a/src/session/routes/auth.rs b/src/session/routes/auth.rs index 8252fd97..70a3dc35 100644 --- a/src/session/routes/auth.rs +++ b/src/session/routes/auth.rs @@ -1,132 +1,135 @@ use crate::{ + config::RuntimeConfig, database::{entities::Player, DatabaseConnection}, - services::{retriever::GetOriginFlow, tokens::Tokens, Services}, + services::{ + retriever::{GetOriginFlow, Retriever}, + sessions::{CreateTokenMessage, Sessions, VerifyError, VerifyTokenMessage}, + }, session::{ models::{ auth::*, - errors::{ServerError, ServerResult}, + errors::{GlobalError, ServerResult}, }, + router::{Blaze, Extension}, GetPlayerIdMessage, GetPlayerMessage, SessionLink, SetPlayerMessage, }, - state::App, utils::hashing::{hash_password, verify_password}, }; use email_address::EmailAddress; +use interlink::prelude::Link; use log::{debug, error}; -use std::borrow::Cow; +use std::{borrow::Cow, sync::Arc}; use tokio::fs::read_to_string; pub async fn handle_login( - session: &mut SessionLink, - req: LoginRequest, -) -> ServerResult { - let db: &DatabaseConnection = App::database(); - + session: SessionLink, + Extension(db): Extension, + Extension(sessions): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { let LoginRequest { email, password } = &req; // Ensure the email is actually valid if !EmailAddress::is_valid(email) { - return Err(ServerError::InvalidEmail); + return Err(AuthenticationError::InvalidEmail.into()); } // Find a non origin player with that email - let player: Player = Player::by_email(db, email) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::EmailNotFound)?; + let player: Player = Player::by_email(&db, email) + .await? + .ok_or(AuthenticationError::InvalidUser)?; // Get the attached password (Passwordless accounts fail as invalid) let player_password: &str = player .password .as_ref() - .ok_or(ServerError::InvalidAccount)?; + .ok_or(AuthenticationError::InvalidUser)?; // Ensure passwords match if !verify_password(password, player_password) { - return Err(ServerError::WrongPassword); + return Err(AuthenticationError::InvalidPassword.into()); } // Update the session stored player - session - .send(SetPlayerMessage(Some(player.clone()))) - .await - .map_err(|_| ServerError::ServerUnavailable)?; + session.send(SetPlayerMessage(Some(player.clone()))).await?; - let session_token: String = Tokens::service_claim(player.id); + let session_token: String = sessions.send(CreateTokenMessage(player.id)).await?; - Ok(AuthResponse { + Ok(Blaze(AuthResponse { player, session_token, silent: false, - }) + })) } pub async fn handle_silent_login( - session: &mut SessionLink, - req: SilentLoginRequest, -) -> ServerResult { - let db: &DatabaseConnection = App::database(); - + session: SessionLink, + Extension(db): Extension, + Extension(sessions): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { // Verify the authentication token - let player: Player = Tokens::service_verify(db, &req.token) - .await - .map_err(|_| ServerError::InvalidSession)?; + let player_id = sessions + .send(VerifyTokenMessage(req.token.clone())) + .await? + .map_err(|err| match err { + VerifyError::Expired => AuthenticationError::ExpiredToken, + VerifyError::Invalid => AuthenticationError::InvalidToken, + })?; + + let player = Player::by_id(&db, player_id) + .await? + .ok_or(AuthenticationError::InvalidToken)?; // Update the session stored player - session - .send(SetPlayerMessage(Some(player.clone()))) - .await - .map_err(|_| ServerError::ServerUnavailable)?; + session.send(SetPlayerMessage(Some(player.clone()))).await?; - Ok(AuthResponse { + Ok(Blaze(AuthResponse { player, session_token: req.token, silent: true, - }) + })) } pub async fn handle_origin_login( - session: &mut SessionLink, - req: OriginLoginRequest, -) -> ServerResult { - let db: &DatabaseConnection = App::database(); - - let services: &Services = App::services(); - + session: SessionLink, + Extension(db): Extension, + Extension(config): Extension>, + Extension(sessions): Extension>, + Extension(retriever): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { // Obtain an origin flow - let mut flow = match services.retriever.send(GetOriginFlow).await { + let mut flow = match retriever.send(GetOriginFlow).await { Ok(Ok(value)) => value, Ok(Err(err)) => { error!("Failed to obtain origin flow: {}", err); - return Err(ServerError::ServerUnavailable); + return Err(GlobalError::System.into()); } Err(err) => { error!("Unable to access retriever service: {}", err); - return Err(ServerError::ServerUnavailable); + return Err(GlobalError::System.into()); } }; - let player: Player = match flow.login(db, req.token).await { + let player: Player = match flow.login(&db, req.token, &config).await { Ok(value) => value, Err(err) => { error!("Failed to login with origin: {}", err); - return Err(ServerError::ServerUnavailable); + return Err(GlobalError::System.into()); } }; // Update the session stored player - session - .send(SetPlayerMessage(Some(player.clone()))) - .await - .map_err(|_| ServerError::ServerUnavailable)?; + session.send(SetPlayerMessage(Some(player.clone()))).await?; - let session_token: String = Tokens::service_claim(player.id); + let session_token: String = sessions.send(CreateTokenMessage(player.id)).await?; - Ok(AuthResponse { + Ok(Blaze(AuthResponse { player, session_token, silent: true, - }) + })) } /// Handles logging out by the client this removes any current player data from the @@ -137,7 +140,7 @@ pub async fn handle_origin_login( /// ID: 8 /// Content: {} /// ``` -pub async fn handle_logout(session: &mut SessionLink) { +pub async fn handle_logout(session: SessionLink) { let _ = session.send(SetPlayerMessage(None)).await; } @@ -213,14 +216,14 @@ static ENTITLEMENTS: &[Entitlement; 34] = &[ /// } /// ``` pub async fn handle_list_entitlements( - req: ListEntitlementsRequest, -) -> Option { + Blaze(req): Blaze, +) -> Option> { let tag: String = req.tag; if !tag.is_empty() { return None; } - Some(ListEntitlementsResponse { list: ENTITLEMENTS }) + Some(Blaze(ListEntitlementsResponse { list: ENTITLEMENTS })) } /// Handles logging into a persona. This system doesn't implement the persona system so @@ -233,13 +236,12 @@ pub async fn handle_list_entitlements( /// "PMAM": "Jacobtread" /// } /// ``` -pub async fn handle_login_persona(session: &mut SessionLink) -> ServerResult { +pub async fn handle_login_persona(session: SessionLink) -> ServerResult> { let player: Player = session .send(GetPlayerMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; - Ok(PersonaResponse { player }) + .await? + .ok_or(GlobalError::AuthenticationRequired)?; + Ok(Blaze(PersonaResponse { player })) } /// Handles forgot password requests. This normally would send a forgot password @@ -253,7 +255,7 @@ pub async fn handle_login_persona(session: &mut SessionLink) -> ServerResult ServerResult<()> { +pub async fn handle_forgot_password(Blaze(req): Blaze) -> ServerResult<()> { debug!("Password reset request (Email: {})", req.email); Ok(()) } @@ -292,26 +294,22 @@ pub async fn handle_forgot_password(req: ForgotPasswordRequest) -> ServerResult< /// ``` /// pub async fn handle_create_account( - session: &mut SessionLink, - req: CreateAccountRequest, -) -> ServerResult { + session: SessionLink, + Extension(db): Extension, + Extension(config): Extension>, + Extension(sessions): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { let email = req.email; if !EmailAddress::is_valid(&email) { - return Err(ServerError::InvalidEmail); + return Err(AuthenticationError::InvalidEmail.into()); } - let db = App::database(); - - match Player::by_email(db, &email).await { + match Player::by_email(&db, &email).await? { // Continue normally for non taken emails - Ok(None) => {} + None => {} // Handle email address is already in use - Ok(Some(_)) => return Err(ServerError::EmailAlreadyInUse), - // Handle database error while checking taken - Err(err) => { - error!("Unable to check if email '{email}' is already taken: {err:?}"); - return Err(ServerError::ServerUnavailable); - } + Some(_) => return Err(AuthenticationError::Exists.into()), } // Hash the proivded plain text password using Argon2 @@ -319,7 +317,7 @@ pub async fn handle_create_account( Ok(value) => value, Err(err) => { error!("Failed to hash password for creating account: {err:?}"); - return Err(ServerError::ServerUnavailable); + return Err(GlobalError::System.into()); } }; @@ -327,32 +325,20 @@ pub async fn handle_create_account( let display_name: String = email.chars().take(99).collect::(); // Create a new player - let player: Player = match Player::create(db, email, display_name, Some(hashed_password)).await - { - Ok(value) => value, - Err(err) => { - error!("Failed to create player: {err:?}"); - return Err(ServerError::ServerUnavailable); - } - }; + let player: Player = + Player::create(&db, email, display_name, Some(hashed_password), &config).await?; // Failing to set the player likely the player disconnected or // the server is shutting down - if session - .send(SetPlayerMessage(Some(player.clone()))) - .await - .is_err() - { - return Err(ServerError::ServerUnavailable); - } + session.send(SetPlayerMessage(Some(player.clone()))).await?; - let session_token = Tokens::service_claim(player.id); + let session_token = sessions.send(CreateTokenMessage(player.id)).await?; - Ok(AuthResponse { + Ok(Blaze(AuthResponse { player, session_token, silent: false, - }) + })) } /// Expected to be getting information about the legal docs however the exact meaning @@ -366,8 +352,8 @@ pub async fn handle_create_account( /// "PTFM": "pc" // Platform /// } /// ``` -pub async fn handle_get_legal_docs_info() -> LegalDocsInfo { - LegalDocsInfo +pub async fn handle_get_legal_docs_info() -> Blaze { + Blaze(LegalDocsInfo) } /// ``` @@ -380,17 +366,17 @@ pub async fn handle_get_legal_docs_info() -> LegalDocsInfo { /// "TEXT": 1 /// } /// ``` -pub async fn handle_tos() -> LegalContent { +pub async fn handle_tos() -> Blaze { let content = match read_to_string("data/terms_of_service.html").await { Ok(value) => Cow::Owned(value), Err(_) => Cow::Borrowed("

This is a terms of service placeholder

"), }; - LegalContent { + Blaze(LegalContent { col: 0xdaed, content, path: "webterms/au/en/pc/default/09082020/02042022", - } + }) } /// ``` @@ -403,17 +389,17 @@ pub async fn handle_tos() -> LegalContent { /// "TEXT": 1 /// } /// ``` -pub async fn handle_privacy_policy() -> LegalContent { +pub async fn handle_privacy_policy() -> Blaze { let content = match read_to_string("data/privacy_policy.html").await { Ok(value) => Cow::Owned(value), Err(_) => Cow::Borrowed("

This is a privacy policy placeholder

"), }; - LegalContent { + Blaze(LegalContent { col: 0xc99c, content, path: "webprivacy/au/en/pc/default/08202020/02042022", - } + }) } /// Handles retrieving an authentication token for use with the Galaxy At War HTTP service. @@ -424,13 +410,15 @@ pub async fn handle_privacy_policy() -> LegalContent { /// ID: 35 /// Content: {} /// ``` -pub async fn handle_get_auth_token(session: &mut SessionLink) -> ServerResult { +pub async fn handle_get_auth_token( + session: SessionLink, + Extension(sessions): Extension>, +) -> ServerResult> { let player_id = session .send(GetPlayerIdMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; + .await? + .ok_or(GlobalError::AuthenticationRequired)?; // Create a new token claim for the player to use with the API - let token = Tokens::service_claim(player_id); - Ok(GetTokenResponse { token }) + let token = sessions.send(CreateTokenMessage(player_id)).await?; + Ok(Blaze(GetTokenResponse { token })) } diff --git a/src/session/routes/game_manager.rs b/src/session/routes/game_manager.rs index 6cc06cd9..5a632acd 100644 --- a/src/session/routes/game_manager.rs +++ b/src/session/routes/game_manager.rs @@ -1,79 +1,71 @@ use crate::{ services::{ game::{ - manager::{CreateMessage, GetGameMessage, TryAddMessage, TryAddResult}, + manager::{ + CreateMessage, GameManager, GetGameMessage, ProcessQueueMessage, + QueuePlayerMessage, TryAddMessage, TryAddResult, + }, models::{DatalessContext, GameSetupContext}, AddPlayerMessage, CheckJoinableMessage, GameJoinableState, GamePlayer, GetGameDataMessage, RemovePlayerMessage, SetAttributesMessage, SetSettingMessage, SetStateMessage, UpdateMeshMessage, }, - matchmaking::{CheckGameMessage, QueuePlayerMessage}, - sessions::LookupMessage, + sessions::{LookupMessage, Sessions}, }, session::{ models::{ - errors::{ServerError, ServerResult}, + errors::{GlobalError, ServerResult}, game_manager::*, }, + router::{Blaze, Extension, RawBlaze}, GetGamePlayerMessage, GetPlayerGameMessage, GetPlayerIdMessage, SessionLink, }, - state::App, }; -use blaze_pk::packet::PacketBody; +use interlink::prelude::Link; use log::{debug, info}; use std::sync::Arc; pub async fn handle_join_game( - session: &mut SessionLink, - req: JoinGameRequest, -) -> ServerResult { - let services = App::services(); - + session: SessionLink, + Extension(sessions): Extension>, + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { // Load the session let player: GamePlayer = session .send(GetGamePlayerMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; + .await? + .ok_or(GlobalError::AuthenticationRequired)?; // Lookup the session join target - let session = services - .sessions + let session = sessions .send(LookupMessage { - player_id: req.target_id, + player_id: req.user.id, }) .await; // Ensure there wasn't an error let session = match session { Ok(Some(value)) => value, - _ => return Err(ServerError::InvalidInformation), + _ => return Err(GlobalError::System.into()), }; // Find the game ID for the target session let game_id = session.send(GetPlayerGameMessage {}).await; let game_id = match game_id { Ok(Some(value)) => value, - _ => return Err(ServerError::InvalidInformation), + _ => return Err(GlobalError::System.into()), }; - let game = services - .game_manager - .send(GetGameMessage { game_id }) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)?; + let game = game_manager.send(GetGameMessage { game_id }).await?; let game = match game { Some(value) => value, - None => return Err(ServerError::InvalidInformation), + None => return Err(GameManagerError::InvalidGameId.into()), }; // Check the game is joinable - let join_state = match game.send(CheckJoinableMessage { rule_set: None }).await { - Ok(value) => value, - // Game is no longer available - Err(_) => return Err(ServerError::InvalidInformation), - }; + let join_state = game.send(CheckJoinableMessage { rule_set: None }).await?; // Join the game if let GameJoinableState::Joinable = join_state { @@ -82,32 +74,31 @@ pub async fn handle_join_game( player, context: GameSetupContext::Dataless(DatalessContext::JoinGameSetup), }); - Ok(JoinGameResponse { game_id }) + Ok(Blaze(JoinGameResponse { + game_id, + state: JoinGameState::JoinedGame, + })) } else { - Err(ServerError::InvalidInformation) + Err(GameManagerError::GameFull.into()) } } -pub async fn handle_get_game_data(mut req: GetGameDataRequest) -> ServerResult { - let services = App::services(); - +pub async fn handle_get_game_data( + Blaze(mut req): Blaze, + Extension(game_manager): Extension>, +) -> ServerResult { if req.game_list.is_empty() { - return Err(ServerError::InvalidInformation); + return Err(GlobalError::System.into()); } let game_id = req.game_list.remove(0); - let game = services - .game_manager + let game = game_manager .send(GetGameMessage { game_id }) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)? - .ok_or(ServerError::InvalidInformation)?; + .await? + .ok_or(GameManagerError::InvalidGameId)?; - let body = game - .send(GetGameDataMessage) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)?; + let body = game.send(GetGameDataMessage).await?; Ok(body) } @@ -164,35 +155,27 @@ pub async fn handle_get_game_data(mut req: GetGameDataRequest) -> ServerResult

ServerResult { + session: SessionLink, + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { let player: GamePlayer = session .send(GetGamePlayerMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; - let services = App::services(); + .await? + .ok_or(GlobalError::AuthenticationRequired)?; - let (link, game_id) = match services - .game_manager + let (link, game_id) = game_manager .send(CreateMessage { attributes: req.attributes, setting: req.setting, host: player, }) - .await - { - Ok(value) => value, - Err(_) => return Err(ServerError::ServerUnavailable), - }; + .await?; // Notify matchmaking of the new game - let _ = services - .matchmaking - .do_send(CheckGameMessage { link, game_id }); + let _ = game_manager.do_send(ProcessQueueMessage { link, game_id }); - Ok(CreateGameResponse { game_id }) + Ok(Blaze(CreateGameResponse { game_id })) } /// Handles changing the attributes of the game with the provided ID @@ -215,22 +198,21 @@ pub async fn handle_create_game( /// "GID": 1 /// } /// ``` -pub async fn handle_set_attributes(req: SetAttributesRequest) -> ServerResult<()> { - let services = App::services(); - let link = services - .game_manager +pub async fn handle_set_attributes( + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) -> ServerResult<()> { + let link = game_manager .send(GetGameMessage { game_id: req.game_id, }) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)?; + .await?; if let Some(link) = link { link.send(SetAttributesMessage { attributes: req.attributes, }) - .await - .map_err(|_| ServerError::InvalidInformation)?; + .await?; } Ok(()) @@ -246,20 +228,18 @@ pub async fn handle_set_attributes(req: SetAttributesRequest) -> ServerResult<() /// "GSTA": 130 /// } /// ``` -pub async fn handle_set_state(req: SetStateRequest) -> ServerResult<()> { - let services = App::services(); - let link = services - .game_manager +pub async fn handle_set_state( + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) -> ServerResult<()> { + let link = game_manager .send(GetGameMessage { game_id: req.game_id, }) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)?; + .await?; if let Some(link) = link { - link.send(SetStateMessage { state: req.state }) - .await - .map_err(|_| ServerError::InvalidInformation)?; + link.send(SetStateMessage { state: req.state }).await?; } Ok(()) @@ -275,22 +255,21 @@ pub async fn handle_set_state(req: SetStateRequest) -> ServerResult<()> { /// "GSET": 285 /// } /// ``` -pub async fn handle_set_setting(req: SetSettingRequest) -> ServerResult<()> { - let services = App::services(); - let link = services - .game_manager +pub async fn handle_set_setting( + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) -> ServerResult<()> { + let link = game_manager .send(GetGameMessage { game_id: req.game_id, }) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)?; + .await?; if let Some(link) = link { link.send(SetSettingMessage { setting: req.setting, }) - .await - .map_err(|_| ServerError::InvalidInformation)?; + .await?; } Ok(()) @@ -309,10 +288,11 @@ pub async fn handle_set_setting(req: SetSettingRequest) -> ServerResult<()> { /// "REAS": 6 /// } /// ``` -pub async fn handle_remove_player(req: RemovePlayerRequest) { - let services = App::services(); - let game = match services - .game_manager +pub async fn handle_remove_player( + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) { + let game = match game_manager .send(GetGameMessage { game_id: req.game_id, }) @@ -347,29 +327,25 @@ pub async fn handle_remove_player(req: RemovePlayerRequest) { /// } /// ``` pub async fn handle_update_mesh_connection( - session: &mut SessionLink, - req: UpdateMeshRequest, + session: SessionLink, + Extension(game_manager): Extension>, + Blaze(mut req): Blaze, ) -> ServerResult<()> { - let id = match session.send(GetPlayerIdMessage).await { - Ok(Some(value)) => value, - Ok(None) => return Err(ServerError::FailedNoLoginAction), - Err(_) => return Err(ServerError::ServerUnavailable), + let id = match session.send(GetPlayerIdMessage).await? { + Some(value) => value, + None => return Err(GlobalError::AuthenticationRequired.into()), }; - let target = match req.target { + let target = match req.targets.pop() { Some(value) => value, None => return Ok(()), }; - let services = App::services(); - - let link = services - .game_manager + let link = game_manager .send(GetGameMessage { game_id: req.game_id, }) - .await - .map_err(|_| ServerError::ServerUnavailableFinal)?; + .await?; let link = match link { Some(value) => value, @@ -509,48 +485,36 @@ pub async fn handle_update_mesh_connection( /// } /// ``` pub async fn handle_start_matchmaking( - session: &mut SessionLink, - req: MatchmakingRequest, -) -> ServerResult { + session: SessionLink, + Extension(game_manager): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { let player: GamePlayer = session .send(GetGamePlayerMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; + .await? + .ok_or(GlobalError::AuthenticationRequired)?; let session_id = player.player.id; info!("Player {} started matchmaking", player.player.display_name); - let services = App::services(); - let rule_set = Arc::new(req.rules); - let result = match services - .game_manager + let result = game_manager .send(TryAddMessage { player, rule_set: rule_set.clone(), }) - .await - { - Ok(value) => value, - Err(_) => return Err(ServerError::ServerUnavailable), - }; + .await?; // If adding failed attempt to queue instead if let TryAddResult::Failure(player) = result { - if services - .matchmaking + game_manager .send(QueuePlayerMessage { player, rule_set }) - .await - .is_err() - { - return Err(ServerError::ServerUnavailable); - } + .await?; } - Ok(MatchmakingResponse { id: session_id }) + Ok(Blaze(MatchmakingResponse { id: session_id })) } /// Handles cancelling matchmaking for the current session removing @@ -563,11 +527,10 @@ pub async fn handle_start_matchmaking( /// "MSID": 1 /// } /// ``` -pub async fn handle_cancel_matchmaking(session: &mut SessionLink) { +pub async fn handle_cancel_matchmaking(session: SessionLink) { session .exec(|session, _| { - let services = App::services(); - session.remove_games(services); + session.remove_games(); }) .await .ok(); diff --git a/src/session/routes/messaging.rs b/src/session/routes/messaging.rs index 2f38c4ca..4b7e7795 100644 --- a/src/session/routes/messaging.rs +++ b/src/session/routes/messaging.rs @@ -1,9 +1,14 @@ use crate::{ - session::{models::messaging::*, GetPlayerMessage, PushExt, SessionLink}, - state::App, - utils::components::{Components as C, Messaging as M}, + config::{RuntimeConfig, VERSION}, + session::{ + models::messaging::*, + packet::Packet, + router::{Blaze, Extension}, + GetPlayerMessage, PushExt, SessionLink, + }, + utils::components::messaging, }; -use blaze_pk::packet::Packet; +use std::sync::Arc; /// Handles requests from the client to fetch the server messages. The initial response contains /// the amount of messages and then each message is sent using a SendMessage notification. @@ -25,20 +30,27 @@ use blaze_pk::packet::Packet; /// } /// ``` /// -pub async fn handle_fetch_messages(session: &mut SessionLink) -> FetchMessageResponse { +pub async fn handle_fetch_messages( + session: SessionLink, + Extension(config): Extension>, +) -> Blaze { // Request a copy of the player data let Ok(Some(player)) = session.send(GetPlayerMessage).await else { // Not authenticated return empty count - return FetchMessageResponse { count: 0 }; + return Blaze(FetchMessageResponse { count: 0 }); }; // Message with player name replaced - let message: String = App::config() + let mut message: String = config .menu_message + .replace("{v}", VERSION) .replace("{n}", &player.display_name); + // Line terminator for the end of the message + message.push(char::from(0x0A)); let notify = Packet::notify( - C::Messaging(M::SendMessage), + messaging::COMPONENT, + messaging::SEND_MESSAGE, MessageNotify { message, player_id: player.id, @@ -46,5 +58,5 @@ pub async fn handle_fetch_messages(session: &mut SessionLink) -> FetchMessageRes ); session.push(notify); - FetchMessageResponse { count: 1 } + Blaze(FetchMessageResponse { count: 1 }) } diff --git a/src/session/routes/mod.rs b/src/session/routes/mod.rs index 89eadb2c..e93295eb 100644 --- a/src/session/routes/mod.rs +++ b/src/session/routes/mod.rs @@ -1,7 +1,5 @@ -use crate::utils::components::{self, Components as C}; - -use super::SessionLink; -use blaze_pk::router::Router; +use super::router::BlazeRouterBuilder; +use crate::utils::components; mod auth; mod game_manager; @@ -17,109 +15,111 @@ mod util; /// rustfmt is disabled because it messes up the neat formatting of the /// route additions #[rustfmt::skip] -pub fn router() -> Router { - let mut router = Router::new(); +pub fn router() -> BlazeRouterBuilder { + + + let mut builder = BlazeRouterBuilder::new(); + // Authentication { use auth::*; - use components::Authentication as A; - - router.route(C::Authentication(A::Logout), handle_logout); - router.route(C::Authentication(A::SilentLogin), handle_silent_login); - router.route(C::Authentication(A::OriginLogin), handle_origin_login); - router.route(C::Authentication(A::Login), handle_login); - router.route(C::Authentication(A::LoginPersona), handle_login_persona); - router.route(C::Authentication(A::ListUserEntitlements2), handle_list_entitlements); - router.route(C::Authentication(A::CreateAccount),handle_create_account); - router.route(C::Authentication(A::PasswordForgot), handle_forgot_password); - router.route(C::Authentication(A::GetLegalDocsInfo), handle_get_legal_docs_info); - router.route(C::Authentication(A::GetTermsOfServiceConent), handle_tos); - router.route(C::Authentication(A::GetPrivacyPolicyContent), handle_privacy_policy); - router.route(C::Authentication(A::GetAuthToken), handle_get_auth_token); + use components::authentication as a; + + builder.route(a::COMPONENT, a::LOGOUT, handle_logout); + builder.route(a::COMPONENT, a::SILENT_LOGIN, handle_silent_login); + builder.route(a::COMPONENT, a::ORIGIN_LOGIN, handle_origin_login); + builder.route(a::COMPONENT, a::LOGIN, handle_login); + builder.route(a::COMPONENT, a::LOGIN_PERSONA, handle_login_persona); + builder.route(a::COMPONENT, a::LIST_USER_ENTITLEMENTS_2, handle_list_entitlements); + builder.route(a::COMPONENT, a::CREATE_ACCOUNT,handle_create_account); + builder.route(a::COMPONENT, a::PASSWORD_FORGOT, handle_forgot_password); + builder.route(a::COMPONENT, a::GET_LEGAL_DOCS_INFO, handle_get_legal_docs_info); + builder.route(a::COMPONENT, a::GET_TERMS_OF_SERVICE_CONTENT, handle_tos); + builder.route(a::COMPONENT, a::GET_PRIVACY_POLICY_CONTENT, handle_privacy_policy); + builder.route(a::COMPONENT, a::GET_AUTH_TOKEN, handle_get_auth_token); } // Game Manager { use game_manager::*; - use components::GameManager as G; - - router.route(C::GameManager(G::CreateGame), handle_create_game); - router.route(C::GameManager(G::AdvanceGameState), handle_set_state); - router.route(C::GameManager(G::SetGameSettings), handle_set_setting); - router.route(C::GameManager(G::SetGameAttributes), handle_set_attributes); - router.route(C::GameManager(G::RemovePlayer), handle_remove_player); - router.route(C::GameManager(G::RemovePlayer), handle_remove_player); - router.route(C::GameManager(G::UpdateMeshConnection),handle_update_mesh_connection); - router.route(C::GameManager(G::StartMatchmaking),handle_start_matchmaking); - router.route(C::GameManager(G::CancelMatchmaking),handle_cancel_matchmaking); - router.route(C::GameManager(G::GetGameDataFromID), handle_get_game_data); - router.route(C::GameManager(G::JoinGame), handle_join_game); + use components::game_manager as g; + + builder.route(g::COMPONENT, g::CREATE_GAME, handle_create_game); + builder.route(g::COMPONENT, g::ADVANCE_GAME_STATE, handle_set_state); + builder.route(g::COMPONENT, g::SET_GAME_SETTINGS, handle_set_setting); + builder.route(g::COMPONENT, g::SET_GAME_ATTRIBUTES, handle_set_attributes); + builder.route(g::COMPONENT, g::REMOVE_PLAYER, handle_remove_player); + builder.route(g::COMPONENT, g::UPDATE_MESH_CONNECTION,handle_update_mesh_connection); + builder.route(g::COMPONENT, g::START_MATCHMAKING,handle_start_matchmaking); + builder.route(g::COMPONENT, g::CANCEL_MATCHMAKING,handle_cancel_matchmaking); + builder.route(g::COMPONENT, g::GET_GAME_DATA_FROM_ID, handle_get_game_data); + builder.route(g::COMPONENT, g::JOIN_GAME, handle_join_game); } // Stats { use stats::*; - use components::Stats as S; + use components::stats as s; - router.route(C::Stats(S::GetLeaderboardEntityCount),handle_leaderboard_entity_count); - router.route(C::Stats(S::GetLeaderboard), handle_normal_leaderboard); - router.route(C::Stats(S::GetCenteredLeaderboard),handle_centered_leaderboard); - router.route(C::Stats(S::GetFilteredLeaderboard),handle_filtered_leaderboard); - router.route(C::Stats(S::GetLeaderboardGroup), handle_leaderboard_group); + builder.route(s::COMPONENT, s::GET_LEADERBOARD_ENTITY_COUNT, handle_leaderboard_entity_count); + builder.route(s::COMPONENT, s::GET_LEADERBOARD, handle_normal_leaderboard); + builder.route(s::COMPONENT, s::GET_CENTERED_LEADERBOARD,handle_centered_leaderboard); + builder.route(s::COMPONENT, s::GET_FILTERED_LEADERBOARD,handle_filtered_leaderboard); + builder.route(s::COMPONENT, s::GET_LEADERBOARD_GROUP, handle_leaderboard_group); } // Util { use util::*; - use components::Util as U; - - router.route(C::Util(U::PreAuth), handle_pre_auth); - router.route(C::Util(U::PostAuth), handle_post_auth); - router.route(C::Util(U::Ping), handle_ping); - router.route(C::Util(U::FetchClientConfig), handle_fetch_client_config); - router.route(C::Util(U::SuspendUserPing), handle_suspend_user_ping); - router.route(C::Util(U::UserSettingsSave), handle_user_settings_save); - router.route(C::Util(U::GetTelemetryServer), handle_get_telemetry_server); - router.route(C::Util(U::GetTickerServer), handle_get_ticker_server); - router.route(C::Util(U::UserSettingsLoadAll), handle_load_settings); + use components::util as u; + + builder.route(u::COMPONENT, u::PRE_AUTH, handle_pre_auth); + builder.route(u::COMPONENT, u::POST_AUTH, handle_post_auth); + builder.route(u::COMPONENT, u::PING, handle_ping); + builder.route(u::COMPONENT, u::FETCH_CLIENT_CONFIG, handle_fetch_client_config); + builder.route(u::COMPONENT, u::SUSPEND_USER_PING, handle_suspend_user_ping); + builder.route(u::COMPONENT, u::USER_SETTINGS_SAVE, handle_user_settings_save); + builder.route(u::COMPONENT, u::GET_TELEMETRY_SERVER, handle_get_telemetry_server); + builder.route(u::COMPONENT, u::GET_TICKER_SERVER, handle_get_ticker_server); + builder.route(u::COMPONENT, u::USER_SETTINGS_LOAD_ALL, handle_load_settings); } // Messaging { use messaging::*; - use components::Messaging as M; + use components::messaging as m; - router.route(C::Messaging(M::FetchMessages), handle_fetch_messages); + builder.route(m::COMPONENT, m::FETCH_MESSAGES, handle_fetch_messages); } // User Sessions { use user_sessions::*; - use components::UserSessions as U; + use components::user_sessions as u; - router.route(C::UserSessions(U::ResumeSession), handle_resume_session); - router.route(C::UserSessions(U::UpdateNetworkInfo), handle_update_network); - router.route( C::UserSessions(U::UpdateHardwareFlags), handle_update_hardware_flag); - router.route(C::UserSessions(U::LookupUser), handle_lookup_user); + builder.route(u::COMPONENT, u::RESUME_SESSION, handle_resume_session); + builder.route(u::COMPONENT, u::UPDATE_NETWORK_INFO, handle_update_network); + builder.route(u::COMPONENT, u::UPDATE_HARDWARE_FLAGS, handle_update_hardware_flag); + builder.route(u::COMPONENT, u::LOOKUP_USER, handle_lookup_user); } // Game Reporting { use other::*; - use components::GameReporting as G; + use components::game_reporting as g; - router.route(C::GameReporting(G::SubmitOfflineGameReport),handle_submit_offline); + builder.route(g::COMPONENT, g::SUBMIT_OFFLINE_GAME_REPORT,handle_submit_offline); } // Association Lists { use other::*; - use components::AssociationLists as A; + use components::association_lists as a; - router.route(C::AssociationLists(A::GetLists), handle_get_lists); + builder.route(a::COMPONENT, a::GET_LISTS, handle_get_lists); } - router + builder } diff --git a/src/session/routes/other.rs b/src/session/routes/other.rs index e7dd23e2..21b2df46 100644 --- a/src/session/routes/other.rs +++ b/src/session/routes/other.rs @@ -1,8 +1,7 @@ use crate::{ - session::{models::other::*, PushExt, SessionLink}, - utils::components::{Components as C, GameReporting as G}, + session::{models::other::*, packet::Packet, router::Blaze, PushExt, SessionLink}, + utils::components::game_reporting, }; -use blaze_pk::packet::Packet; /// Handles submission of offline game reports from clients. /// @@ -29,9 +28,10 @@ use blaze_pk::packet::Packet; /// "GTYP": "massEffectReport" /// } /// ``` -pub async fn handle_submit_offline(session: &mut SessionLink) { +pub async fn handle_submit_offline(session: SessionLink) { session.push(Packet::notify( - C::GameReporting(G::GameReportSubmitted), + game_reporting::COMPONENT, + game_reporting::GAME_REPORT_SUBMITTED, GameReportResponse, )); } @@ -58,6 +58,6 @@ pub async fn handle_submit_offline(session: &mut SessionLink) { /// "OFRC": 0 /// } /// ``` -pub async fn handle_get_lists() -> AssocListResponse { - AssocListResponse +pub async fn handle_get_lists() -> Blaze { + Blaze(AssocListResponse) } diff --git a/src/session/routes/stats.rs b/src/session/routes/stats.rs index 73674601..4e3b5998 100644 --- a/src/session/routes/stats.rs +++ b/src/session/routes/stats.rs @@ -1,17 +1,22 @@ use crate::{ - services::leaderboard::{models::*, QueryMessage}, - session::models::{ - errors::{ServerError, ServerResult}, - stats::*, + services::leaderboard::{models::*, Leaderboard, QueryMessage}, + session::{ + models::{errors::ServerResult, stats::*}, + packet::Packet, + router::{Blaze, BlazeWithHeader, Extension}, }, - state::App, }; -use blaze_pk::packet::{Request, Response}; +use interlink::prelude::Link; +use sea_orm::DatabaseConnection; use std::sync::Arc; -pub async fn handle_normal_leaderboard(req: Request) -> ServerResult { - let query = &*req; - let group = get_group(&query.name).await?; +pub async fn handle_normal_leaderboard( + Extension(leaderboard): Extension>, + Extension(db): Extension, + req: BlazeWithHeader, +) -> ServerResult { + let query = &req.req; + let group = get_group(db, leaderboard, &query.name).await?; let response = match group.get_normal(query.start, query.count) { Some((values, _)) => LeaderboardResponse::Many(values), None => LeaderboardResponse::Empty, @@ -20,10 +25,12 @@ pub async fn handle_normal_leaderboard(req: Request) -> Serv } pub async fn handle_centered_leaderboard( - req: Request, -) -> ServerResult { - let query = &*req; - let group = get_group(&query.name).await?; + Extension(leaderboard): Extension>, + Extension(db): Extension, + req: BlazeWithHeader, +) -> ServerResult { + let query = &req.req; + let group = get_group(db, leaderboard, &query.name).await?; let response = match group.get_centered(query.center, query.count) { Some(values) => LeaderboardResponse::Many(values), None => LeaderboardResponse::Empty, @@ -32,10 +39,12 @@ pub async fn handle_centered_leaderboard( } pub async fn handle_filtered_leaderboard( - req: Request, -) -> ServerResult { - let query = &*req; - let group = get_group(&query.name).await?; + Extension(leaderboard): Extension>, + Extension(db): Extension, + req: BlazeWithHeader, +) -> ServerResult { + let query = &req.req; + let group = get_group(db, leaderboard, &query.name).await?; let response = match group.get_entry(query.id) { Some(value) => LeaderboardResponse::One(value), None => LeaderboardResponse::Empty, @@ -59,21 +68,23 @@ pub async fn handle_filtered_leaderboard( /// } /// ``` pub async fn handle_leaderboard_entity_count( - req: EntityCountRequest, -) -> ServerResult { - let group = get_group(&req.name).await?; + Extension(leaderboard): Extension>, + Extension(db): Extension, + Blaze(req): Blaze, +) -> ServerResult> { + let group = get_group(db, leaderboard, &req.name).await?; let count = group.values.len(); - Ok(EntityCountResponse { count }) + Ok(Blaze(EntityCountResponse { count })) } -async fn get_group(name: &str) -> ServerResult> { - let services = App::services(); - let leaderboard = &services.leaderboard; +async fn get_group( + db: DatabaseConnection, + leaderboard: Link, + name: &str, +) -> ServerResult> { let ty = LeaderboardType::from_value(name); - leaderboard - .send(QueryMessage(ty)) - .await - .map_err(|_| ServerError::ServerUnavailableFinal) + let result = leaderboard.send(QueryMessage(ty, db)).await?; + Ok(result) } fn get_locale_name(code: &str) -> &str { @@ -103,8 +114,8 @@ fn get_locale_name(code: &str) -> &str { /// } /// ``` pub async fn handle_leaderboard_group( - req: LeaderboardGroupRequest, -) -> Option> { + Blaze(req): Blaze, +) -> Option>> { let name = req.name; let is_n7 = name.starts_with("N7Rating"); if !is_n7 && !name.starts_with("ChallengePoints") { @@ -132,5 +143,5 @@ pub async fn handle_leaderboard_group( gname: "ME3ChallengePoints", } }; - Some(group) + Some(Blaze(group)) } diff --git a/src/session/routes/user_sessions.rs b/src/session/routes/user_sessions.rs index 21725b76..66b21564 100644 --- a/src/session/routes/user_sessions.rs +++ b/src/session/routes/user_sessions.rs @@ -1,20 +1,21 @@ +use interlink::prelude::Link; +use sea_orm::DatabaseConnection; + use crate::{ database::entities::Player, - services::{sessions::LookupMessage, tokens::Tokens}, + services::sessions::{LookupMessage, Sessions, VerifyError, VerifyTokenMessage}, session::{ models::{ - auth::AuthResponse, - errors::{ServerError, ServerResult}, + auth::{AuthResponse, AuthenticationError}, + errors::{GlobalError, ServerResult}, user_sessions::*, }, + router::{Blaze, Extension}, GetLookupMessage, GetSocketAddrMessage, HardwareFlagMessage, LookupResponse, NetworkInfoMessage, SessionLink, SetPlayerMessage, }, - state::App, - utils::models::NetAddress, + utils::models::NetworkAddress, }; -use log::error; -use std::net::SocketAddr; /// Attempts to lookup another authenticated session details /// @@ -31,12 +32,12 @@ use std::net::SocketAddr; /// "NAME": "", /// } /// ``` -pub async fn handle_lookup_user(req: LookupRequest) -> ServerResult { - let services = App::services(); - +pub async fn handle_lookup_user( + Blaze(req): Blaze, + Extension(sessions): Extension>, +) -> ServerResult> { // Lookup the session - let session = services - .sessions + let session = sessions .send(LookupMessage { player_id: req.player_id, }) @@ -45,17 +46,17 @@ pub async fn handle_lookup_user(req: LookupRequest) -> ServerResult value, - _ => return Err(ServerError::InvalidInformation), + _ => return Err(GlobalError::System.into()), }; // Get the lookup response from the session let response = session.send(GetLookupMessage {}).await; let response = match response { Ok(Some(value)) => value, - _ => return Err(ServerError::InvalidInformation), + _ => return Err(GlobalError::System.into()), }; - Ok(response) + Ok(Blaze(response)) } /// Attempts to resume an existing session for a player that has the @@ -69,36 +70,35 @@ pub async fn handle_lookup_user(req: LookupRequest) -> ServerResult ServerResult { - let db = App::database(); - + session: SessionLink, + Extension(db): Extension, + Extension(sessions): Extension>, + Blaze(req): Blaze, +) -> ServerResult> { let session_token = req.session_token; - let player: Player = match Tokens::service_verify(db, &session_token).await { - Ok(value) => value, - Err(err) => { - error!("Error while attempt to resume session: {err:?}"); - return Err(ServerError::InvalidSession); - } - }; + // Verify the authentication token + let player_id = sessions + .send(VerifyTokenMessage(session_token.clone())) + .await? + .map_err(|err| match err { + VerifyError::Expired => AuthenticationError::ExpiredToken, + VerifyError::Invalid => AuthenticationError::InvalidToken, + })?; + + let player = Player::by_id(&db, player_id) + .await? + .ok_or(AuthenticationError::InvalidToken)?; // Failing to set the player likely the player disconnected or // the server is shutting down - if session - .send(SetPlayerMessage(Some(player.clone()))) - .await - .is_err() - { - return Err(ServerError::ServerUnavailable); - } + session.send(SetPlayerMessage(Some(player.clone()))).await?; - Ok(AuthResponse { + Ok(Blaze(AuthResponse { player, session_token, silent: true, - }) + })) } /// Handles updating the stored networking information for the current session @@ -130,23 +130,27 @@ pub async fn handle_resume_session( /// } /// } /// ``` -pub async fn handle_update_network(session: &mut SessionLink, mut req: UpdateNetworkRequest) { - let ext = &mut req.address.external; +pub async fn handle_update_network( + session: SessionLink, + Blaze(mut req): Blaze, +) { + if let NetworkAddress::AddressPair(pair) = &mut req.address { + let ext = &mut pair.external; - // If address is missing - if ext.0 .0.is_unspecified() { - // Obtain socket address from session - if let Ok(SocketAddr::V4(addr)) = session.send(GetSocketAddrMessage).await { - let ip = addr.ip(); - // Replace address with new address and port with same as local port - ext.0 = NetAddress(*ip); - ext.1 = req.address.internal.1; + // If address is missing + if ext.addr.is_unspecified() { + // Obtain socket address from session + if let Ok(addr) = session.send(GetSocketAddrMessage).await { + // Replace address with new address and port with same as local port + ext.addr = addr; + ext.port = pair.internal.port; + } } } let _ = session .send(NetworkInfoMessage { - groups: req.address, + address: req.address, qos: req.qos, }) .await; @@ -161,7 +165,10 @@ pub async fn handle_update_network(session: &mut SessionLink, mut req: UpdateNet /// "HWFG": 0 /// } /// ``` -pub async fn handle_update_hardware_flag(session: &mut SessionLink, req: HardwareFlagRequest) { +pub async fn handle_update_hardware_flag( + session: SessionLink, + Blaze(req): Blaze, +) { let _ = session .send(HardwareFlagMessage { value: req.hardware_flag, diff --git a/src/session/routes/util.rs b/src/session/routes/util.rs index 84a8cb0a..f4e535ee 100644 --- a/src/session/routes/util.rs +++ b/src/session/routes/util.rs @@ -1,25 +1,27 @@ use crate::{ + config::VERSION, database::entities::PlayerData, session::{ models::{ - errors::{ServerError, ServerResult}, + errors::{GlobalError, ServerResult}, util::*, }, + router::{Blaze, Extension}, DetailsMessage, GetHostTarget, GetPlayerIdMessage, SessionLink, }, - state::{self, App}, }; use base64ct::{Base64, Encoding}; -use blaze_pk::types::TdfMap; use embeddy::Embedded; use flate2::{write::ZlibEncoder, Compression}; use interlink::prelude::Link; -use log::{error, warn}; +use log::error; +use sea_orm::DatabaseConnection; use std::{ io::Write, path::Path, time::{Duration, SystemTime, UNIX_EPOCH}, }; +use tdf::TdfMap; use tokio::fs::read; /// Handles retrieving the details about the telemetry server @@ -30,8 +32,8 @@ use tokio::fs::read; /// Content: {} /// ``` /// -pub async fn handle_get_telemetry_server() -> TelemetryServer { - TelemetryServer +pub async fn handle_get_telemetry_server() -> Blaze { + Blaze(TelemetryServer) } /// Handles retrieving the details about the ticker server @@ -42,8 +44,8 @@ pub async fn handle_get_telemetry_server() -> TelemetryServer { /// Content: {} /// ``` /// -pub async fn handle_get_ticker_server() -> TickerServer { - TickerServer +pub async fn handle_get_ticker_server() -> Blaze { + Blaze(TickerServer) } /// Handles responding to pre-auth requests which is the first request @@ -77,13 +79,9 @@ pub async fn handle_get_ticker_server() -> TickerServer { /// } /// } /// ``` -pub async fn handle_pre_auth(session: &mut SessionLink) -> ServerResult { - let host_target = match session.send(GetHostTarget {}).await { - Ok(value) => value, - Err(_) => return Err(ServerError::InvalidInformation), - }; - - Ok(PreAuthResponse { host_target }) +pub async fn handle_pre_auth(session: SessionLink) -> ServerResult> { + let host_target = session.send(GetHostTarget {}).await?; + Ok(Blaze(PreAuthResponse { host_target })) } /// Handles post authentication requests. This provides information about other @@ -94,23 +92,22 @@ pub async fn handle_pre_auth(session: &mut SessionLink) -> ServerResult ServerResult { +pub async fn handle_post_auth(session: SessionLink) -> ServerResult> { let player_id = session .send(GetPlayerIdMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; + .await? + .ok_or(GlobalError::AuthenticationRequired)?; // Queue the session details to be sent to this client let _ = session.do_send(DetailsMessage { - link: Link::clone(&*session), + link: Link::clone(&session), }); - Ok(PostAuthResponse { + Ok(Blaze(PostAuthResponse { telemetry: TelemetryServer, ticker: TickerServer, player_id, - }) + })) } /// Handles ping update requests. These are sent by the client at the interval @@ -123,13 +120,13 @@ pub async fn handle_post_auth(session: &mut SessionLink) -> ServerResult PingResponse { +pub async fn handle_ping() -> Blaze { let now = SystemTime::now(); let server_time = now .duration_since(UNIX_EPOCH) .unwrap_or(Duration::ZERO) .as_secs(); - PingResponse { server_time } + Blaze(PingResponse { server_time }) } /// Contents of the entitlements dmap file @@ -154,22 +151,22 @@ const ME3_DIME: &str = include_str!("../../resources/data/dime.xml"); /// } /// ``` pub async fn handle_fetch_client_config( - session: &mut SessionLink, - req: FetchConfigRequest, -) -> ServerResult { + session: SessionLink, + Blaze(req): Blaze, +) -> ServerResult> { let config = match req.id.as_ref() { - "ME3_DATA" => data_config(session).await, + "ME3_DATA" => data_config(&session).await, "ME3_MSG" => messages(), "ME3_ENT" => load_entitlements(), "ME3_DIME" => { let mut map = TdfMap::with_capacity(1); - map.insert("Config", ME3_DIME); + map.insert("Config".to_string(), ME3_DIME.to_string()); map } "ME3_BINI_VERSION" => { let mut map = TdfMap::with_capacity(2); - map.insert("SECTION", "BINI_PC_COMPRESSED"); - map.insert("VERSION", "40128"); + map.insert("SECTION".to_string(), "BINI_PC_COMPRESSED".to_string()); + map.insert("VERSION".to_string(), "40128".to_string()); map } "ME3_BINI_PC_COMPRESSED" => load_coalesced().await?, @@ -182,7 +179,7 @@ pub async fn handle_fetch_client_config( } }; - Ok(FetchConfigResponse { config }) + Ok(Blaze(FetchConfigResponse { config })) } /// Loads the entitlements from the entitlements file and parses @@ -190,7 +187,7 @@ pub async fn handle_fetch_client_config( fn load_entitlements() -> TdfMap { let mut map = TdfMap::::new(); for (key, value) in ME3_ENT.lines().filter_map(|line| line.split_once('=')) { - map.insert(key, value) + map.insert(key.to_string(), value.to_string()); } map } @@ -224,11 +221,11 @@ fn generate_coalesced(bytes: &[u8]) -> ServerResult { let mut encoder = ZlibEncoder::new(Vec::new(), Compression::new(6)); encoder.write_all(bytes).map_err(|_| { error!("Failed to encode coalesced with ZLib (write stage)"); - ServerError::ServerUnavailable + GlobalError::System })?; encoder.finish().map_err(|_| { error!("Failed to encode coalesced with ZLib (finish stage)"); - ServerError::ServerUnavailable + GlobalError::System })? }; @@ -272,13 +269,12 @@ fn create_base64_map(bytes: &[u8]) -> ChunkMap { &encoded[o1..] }; - output.insert(format!("CHUNK_{}", index), slice); + output.insert(format!("CHUNK_{}", index), slice.to_string()); index += 1; } - output.insert("CHUNK_SIZE", CHUNK_LENGTH.to_string()); - output.insert("DATA_SIZE", length.to_string()); - output.order(); + output.insert("CHUNK_SIZE".to_string(), CHUNK_LENGTH.to_string()); + output.insert("DATA_SIZE".to_string(), length.to_string()); output } @@ -321,7 +317,7 @@ fn messages() -> TdfMap { title: Some("Pocket Relay".to_owned()), message: format!( "You are connected to Pocket Relay (v{})", - state::VERSION, + VERSION, ), priority: 1, tracking_id: Some(1), @@ -332,7 +328,6 @@ fn messages() -> TdfMap { intro.append(1, &mut config); - config.order(); config } @@ -396,17 +391,17 @@ impl Message { map.insert(format!("{prefix}image"), image); } - map.insert(format!("{prefix}message"), &self.message); + map.insert(format!("{prefix}message"), self.message.to_string()); for lang in &langs { - map.insert(format!("{prefix}message_{lang}"), &self.message); + map.insert(format!("{prefix}message_{lang}"), self.message.to_string()); } map.insert(format!("{prefix}priority"), self.priority.to_string()); if let Some(title) = &self.title { - map.insert(format!("{prefix}title"), title); + map.insert(format!("{prefix}title"), title.to_string()); for lang in &langs { - map.insert(format!("{prefix}title_{lang}"), title); + map.insert(format!("{prefix}title_{lang}"), title.to_string()); } } @@ -451,22 +446,28 @@ async fn data_config(session: &SessionLink) -> TdfMap { let tele_port = TELEMETRY_PORT; let mut config = TdfMap::with_capacity(15); - config.insert("GAW_SERVER_BASE_URL", format!("{prefix}/")); - config.insert("IMG_MNGR_BASE_URL", format!("{prefix}/content/")); - config.insert("IMG_MNGR_MAX_BYTES", "1048576"); - config.insert("IMG_MNGR_MAX_IMAGES", "5"); - config.insert("JOB_THROTTLE_0", "10000"); - config.insert("JOB_THROTTLE_1", "5000"); - config.insert("JOB_THROTTLE_2", "1000"); - config.insert("MATCH_MAKING_RULES_VERSION", "5"); - config.insert("MULTIPLAYER_PROTOCOL_VERSION", "3"); - config.insert("TEL_DISABLE", TELEMTRY_DISA); - config.insert("TEL_DOMAIN", "pc/masseffect-3-pc-anon"); - config.insert("TEL_FILTER", "-UION/****"); - config.insert("TEL_PORT", tele_port.to_string()); - config.insert("TEL_SEND_DELAY", "15000"); - config.insert("TEL_SEND_PCT", "75"); - config.insert("TEL_SERVER", "127.0.0.1"); + config.insert("GAW_SERVER_BASE_URL".to_string(), format!("{prefix}/")); + config.insert( + "IMG_MNGR_BASE_URL".to_string(), + format!("{prefix}/content/"), + ); + config.insert("IMG_MNGR_MAX_BYTES".to_string(), "1048576".to_string()); + config.insert("IMG_MNGR_MAX_IMAGES".to_string(), "5".to_string()); + config.insert("JOB_THROTTLE_0".to_string(), "10000".to_string()); + config.insert("JOB_THROTTLE_1".to_string(), "5000".to_string()); + config.insert("JOB_THROTTLE_2".to_string(), "1000".to_string()); + config.insert("MATCH_MAKING_RULES_VERSION".to_string(), "5".to_string()); + config.insert("MULTIPLAYER_PROTOCOL_VERSION".to_string(), "3".to_string()); + config.insert("TEL_DISABLE".to_string(), TELEMTRY_DISA.to_string()); + config.insert( + "TEL_DOMAIN".to_string(), + "pc/masseffect-3-pc-anon".to_string(), + ); + config.insert("TEL_FILTER".to_string(), "-UION/****".to_string()); + config.insert("TEL_PORT".to_string(), tele_port.to_string()); + config.insert("TEL_SEND_DELAY".to_string(), "15000".to_string()); + config.insert("TEL_SEND_PCT".to_string(), "75".to_string()); + config.insert("TEL_SERVER".to_string(), "127.0.0.1".to_string()); config } @@ -480,10 +481,10 @@ async fn data_config(session: &SessionLink) -> TdfMap { /// "TVAL": 90000000 /// } /// ``` -pub async fn handle_suspend_user_ping(req: SuspendPingRequest) -> ServerResult<()> { +pub async fn handle_suspend_user_ping(Blaze(req): Blaze) -> ServerResult<()> { match req.value { - 20000000 => Err(ServerError::Suspend12D), - 90000000 => Err(ServerError::Suspend12E), + 20000000 => Err(UtilError::SuspendPingTimeTooSmall.into()), + 90000000 => Err(UtilError::PingSuspended.into()), _ => Ok(()), } } @@ -500,22 +501,17 @@ pub async fn handle_suspend_user_ping(req: SuspendPingRequest) -> ServerResult<( /// } /// ``` pub async fn handle_user_settings_save( - session: &mut SessionLink, - req: SettingsSaveRequest, + session: SessionLink, + Extension(db): Extension, + Blaze(req): Blaze, ) -> ServerResult<()> { let player = session .send(GetPlayerIdMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; - - let db = App::database(); - if let Err(err) = PlayerData::set(db, player, req.key, req.value).await { - warn!("Failed to update player data: {err:?}"); - Err(ServerError::ServerUnavailable) - } else { - Ok(()) - } + .await? + .ok_or(GlobalError::AuthenticationRequired)?; + + PlayerData::set(&db, player, req.key, req.value).await?; + Ok(()) } /// Handles loading all the user details for the current account and sending them to the @@ -526,29 +522,22 @@ pub async fn handle_user_settings_save( /// ID: 23 /// Content: {} /// ``` -pub async fn handle_load_settings(session: &mut SessionLink) -> ServerResult { +pub async fn handle_load_settings( + session: SessionLink, + Extension(db): Extension, +) -> ServerResult> { let player = session .send(GetPlayerIdMessage) - .await - .map_err(|_| ServerError::ServerUnavailable)? - .ok_or(ServerError::FailedNoLoginAction)?; - - let db = App::database(); + .await? + .ok_or(GlobalError::AuthenticationRequired)?; // Load the player data from the database - let data: Vec = match PlayerData::all(db, player).await { - Ok(value) => value, - Err(err) => { - error!("Failed to load player data: {err:?}"); - return Err(ServerError::ServerUnavailable); - } - }; + let data: Vec = PlayerData::all(&db, player).await?; // Encode the player data into a settings map and order it let mut settings = TdfMap::::with_capacity(data.len()); for value in data { - settings.insert(value.key, value.value) + settings.insert(value.key, value.value); } - settings.order(); - Ok(SettingsResponse { settings }) + Ok(Blaze(SettingsResponse { settings })) } diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index f9f19ed9..00000000 --- a/src/state.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crate::{ - config::{Config, RuntimeConfig, ServicesConfig}, - database::{self, DatabaseConnection}, - services::Services, - session::{self, SessionLink}, - utils::{components::Components, logging}, -}; -use blaze_pk::router::Router; -use tokio::join; - -/// The server version extracted from the Cargo.toml -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Global state that is shared throughout the application this -/// will be unset until the value is initialized then it will be -/// set -pub struct App { - /// Connection to the database - pub db: DatabaseConnection, - /// Global services - pub services: Services, - /// Runtime global configuration - pub config: RuntimeConfig, - /// Global session router - pub router: Router, -} - -/// Static global state value -static mut GLOBAL_STATE: Option = None; - -impl App { - /// Initializes the global state updating the value stored in - /// GLOBAL_STATE with a new set state. This function MUST be - /// called before this state is accessed or else the app will - /// panic and must not be called more than once. - pub async fn init(config: Config) { - // Config data passed onto the services - let services_config = ServicesConfig { - retriever: config.retriever, - }; - - // Create menu message - let menu_message = { - // Message with server version variable replaced - let mut message: String = config.menu_message.replace("{v}", VERSION); - - // Line terminator for the end of the message - message.push(char::from(0x0A)); - message - }; - - // Config data persisted to runtime - let runtime_config = RuntimeConfig { - reverse_proxy: config.reverse_proxy, - galaxy_at_war: config.galaxy_at_war, - menu_message, - dashboard: config.dashboard, - }; - - let (db, services, _) = join!( - // Initialize the database - database::init(&runtime_config), - // Initialize the services - Services::init(services_config), - // Display the connection urls message - logging::log_connection_urls(config.port) - ); - - // Initialize session router - let router = session::routes::router(); - - unsafe { - GLOBAL_STATE = Some(App { - db, - services, - config: runtime_config, - router, - }); - } - } - - /// Obtains a static reference to the session router - pub fn router() -> &'static Router { - match unsafe { &GLOBAL_STATE } { - Some(value) => &value.router, - None => panic!("Global state not initialized"), - } - } - - /// Obtains a database connection by cloning the global - /// database pool - pub fn database() -> &'static DatabaseConnection { - match unsafe { &GLOBAL_STATE } { - Some(value) => &value.db, - None => panic!("Global state not initialized"), - } - } - - /// Obtains a static reference to the services - pub fn services() -> &'static Services { - match unsafe { &GLOBAL_STATE } { - Some(value) => &value.services, - None => panic!("Global state not initialized"), - } - } - - /// Obtains a static reference to the runtime config - pub fn config() -> &'static RuntimeConfig { - match unsafe { &GLOBAL_STATE } { - Some(value) => &value.config, - None => panic!("Global state not initialized"), - } - } -} diff --git a/src/utils/components.rs b/src/utils/components.rs index c0b39379..4e2e36a4 100644 --- a/src/utils/components.rs +++ b/src/utils/components.rs @@ -1,508 +1,657 @@ -//! Modules contains the component definitions for the servers used throughout -//! this application. - -use blaze_pk::{PacketComponent, PacketComponents}; - -#[derive(Debug, Hash, PartialEq, Eq, PacketComponents)] -pub enum Components { - #[component(target = 0x1)] - Authentication(Authentication), - #[component(target = 0x4)] - GameManager(GameManager), - #[component(target = 0x5)] - Redirector(Redirector), - #[component(target = 0x7)] - Stats(Stats), - #[component(target = 0x9)] - Util(Util), - #[component(target = 0xF)] - Messaging(Messaging), - #[component(target = 0x19)] - AssociationLists(AssociationLists), - #[component(target = 0x1C)] - GameReporting(GameReporting), - #[component(target = 0x7802)] - UserSessions(UserSessions), +use std::collections::HashMap; + +/// Key created from a component and command +pub type ComponentKey = u32; + +// Very little number of components so lookups are quick enough that HashMap is pointless +static COMPONENT_NAMES: &[(u16, &str)] = &[ + (authentication::COMPONENT, "Authentication"), + (game_manager::COMPONENT, "GameManager"), + (redirector::COMPONENT, "Redirector"), + (stats::COMPONENT, "Stats"), + (util::COMPONENT, "Util"), + (messaging::COMPONENT, "Messaging"), + (association_lists::COMPONENT, "AssociationLists"), + (game_reporting::COMPONENT, "GameReporting"), + (user_sessions::COMPONENT, "UserSessions"), +]; +static mut COMMANDS: Option> = None; +static mut NOTIFICATIONS: Option> = None; + +/// Initializes the stored component state. Should only be +/// called on initial startup +pub fn initialize() { + unsafe { + COMMANDS = Some(commands()); + NOTIFICATIONS = Some(notifications()) + } +} + +pub fn get_component_name(component: u16) -> Option<&'static str> { + COMPONENT_NAMES + .iter() + .find_map(|(c, value)| if component.eq(c) { Some(value) } else { None }) + .copied() } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum Authentication { - #[command(target = 0x14)] - UpdateAccount, - #[command(target = 0x1C)] - UpdateParentalEmail, - #[command(target = 0x1D)] - ListUserEntitlements2, - #[command(target = 0x1E)] - GetAccount, - #[command(target = 0x1F)] - GrantEntitlement, - #[command(target = 0x20)] - ListEntitlements, - #[command(target = 0x21)] - HasEntitlement, - #[command(target = 0x22)] - GetUseCount, - #[command(target = 0x23)] - DecrementUseCount, - #[command(target = 0x24)] - GetAuthToken, - #[command(target = 0x25)] - GetHandoffToken, - #[command(target = 0x26)] - GetPasswordRules, - #[command(target = 0x27)] - GrantEntitlement2, - #[command(target = 0x28)] - Login, - #[command(target = 0x29)] - AcceptTOS, - #[command(target = 0x2A)] - GetTOSInfo, - #[command(target = 0x2B)] - ModifyEntitlement2, - #[command(target = 0x2C)] - ConsumeCode, - #[command(target = 0x2D)] - PasswordForgot, - #[command(target = 0x2E)] - GetTOSContent, - #[command(target = 0x2F)] - GetPrivacyPolicyContent, - #[command(target = 0x30)] - ListPersonalEntitlements2, - #[command(target = 0x32)] - SilentLogin, - #[command(target = 0x33)] - CheckAgeRequirement, - #[command(target = 0x34)] - GetOptIn, - #[command(target = 0x35)] - EnableOptIn, - #[command(target = 0x36)] - DisableOptIn, - #[command(target = 0x3C)] - ExpressLogin, - #[command(target = 0x46)] - Logout, - #[command(target = 0x50)] - CreatePersona, - #[command(target = 0x5A)] - GetPersona, - #[command(target = 0x64)] - ListPersonas, - #[command(target = 0x6E)] - LoginPersona, - #[command(target = 0x78)] - LogoutPersona, - #[command(target = 0x8C)] - DeletePersona, - #[command(target = 0x8D)] - DisablePersona, - #[command(target = 0x8F)] - ListDeviceAccounts, - #[command(target = 0x96)] - XboxCreateAccount, - #[command(target = 0x98)] - OriginLogin, - #[command(target = 0xA0)] - XboxAssociateAccount, - #[command(target = 0xAA)] - XboxLogin, - #[command(target = 0xB4)] - PS3CreateAccount, - #[command(target = 0xBE)] - PS3AssociateAccount, - #[command(target = 0xC8)] - PS3Login, - #[command(target = 0xD2)] - ValidateSessionKey, - #[command(target = 0xE6)] - CreateWalUserSession, - #[command(target = 0xF1)] - AcceptLegalDocs, - #[command(target = 0xF2)] - GetLegalDocsInfo, - #[command(target = 0xF6)] - GetTermsOfServiceConent, - #[command(target = 0x12C)] - DeviceLoginGuest, - #[command(target = 0xA)] - CreateAccount, +pub fn get_command_name(component: u16, command: u16, notify: bool) -> Option<&'static str> { + let key = component_key(component, command); + let map = if notify { + unsafe { NOTIFICATIONS.as_ref() } + } else { + unsafe { COMMANDS.as_ref() } + }; + map.and_then(|value| value.get(&key).copied()) } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum GameManager { - #[command(target = 0x1)] - CreateGame, - #[command(target = 0x2)] - DestroyGame, - #[command(target = 0x3)] - AdvanceGameState, - #[command(target = 0x4)] - SetGameSettings, - #[command(target = 0x5)] - SetPlayerCapacity, - #[command(target = 0x6)] - SetPresenceMode, - #[command(target = 0x7)] - SetGameAttributes, - #[command(target = 0x8)] - SetPlayerAttributes, - #[command(target = 0x9)] - JoinGame, - #[command(target = 0xB)] - RemovePlayer, - #[command(target = 0xD)] - StartMatchmaking, - #[command(target = 0xE)] - CancelMatchmaking, - #[command(target = 0xF)] - FinalizeGameCreation, - #[command(target = 0x11)] - ListGames, - #[command(target = 0x12)] - SetPlayerCustomData, - #[command(target = 0x13)] - ReplayGame, - #[command(target = 0x14)] - ReturnDedicatedServerToPool, - #[command(target = 0x15)] - JoinGameByGroup, - #[command(target = 0x16)] - LeaveGameByGroup, - #[command(target = 0x17)] - MigrateGame, - #[command(target = 0x18)] - UpdateGameHostMigrationStatus, - #[command(target = 0x19)] - ResetDedicatedServe, - #[command(target = 0x1A)] - UpdateGameSession, - #[command(target = 0x1B)] - BanPlayer, - #[command(target = 0x1D)] - UpdateMeshConnection, - #[command(target = 0x1F)] - RemovePlayerFromBannedList, - #[command(target = 0x20)] - ClearBannedList, - #[command(target = 0x21)] - GetBannedList, - #[command(target = 0x26)] - AddQueuedPlayerToGame, - #[command(target = 0x27)] - UpdateGameName, - #[command(target = 0x28)] - EjectHost, - #[command(target = 0x64)] - GetGameListSnapshot, - #[command(target = 0x65)] - GetGameListSubscription, - #[command(target = 0x66)] - DestroyGameList, - #[command(target = 0x67)] - GetFullGameData, - #[command(target = 0x68)] - GetMatchmakingConfig, - #[command(target = 0x69)] - GetGameDataFromID, - #[command(target = 0x6A)] - AddAdminPlayer, - #[command(target = 0x6B)] - RemoveAdminPlayer, - #[command(target = 0x6C)] - SetPlayerTeam, - #[command(target = 0x6D)] - ChangeGameTeamID, - #[command(target = 0x6E)] - MigrateAdminPlayer, - #[command(target = 0x6F)] - GetUserSetGameListSubscription, - #[command(target = 0x70)] - SwapPlayersTeam, - #[command(target = 0x96)] - RegisterDynamicDedicatedServerCreator, - #[command(target = 0x97)] - UnregisterDynamicDedicatedServerCreator, - - #[command(target = 0xA, notify)] - MatchmakingFailed, - #[command(target = 0xC, notify)] - MatchmakingAsyncStatus, - #[command(target = 0xF, notify)] - GameCreated, - #[command(target = 0x10, notify)] - GameRemoved, - #[command(target = 0x14, notify)] - GameSetup, - #[command(target = 0x15, notify)] - PlayerJoining, - #[command(target = 0x16, notify)] - JoiningPlayerInitiateConnections, - #[command(target = 0x17, notify)] - PlayerJoiningQueue, - #[command(target = 0x18, notify)] - PlayerPromotedFromQueue, - #[command(target = 0x19, notify)] - PlayerClaimingReservation, - #[command(target = 0x1E, notify)] - PlayerJoinCompleted, - #[command(target = 0x28, notify)] - PlayerRemoved, - #[command(target = 0x3C, notify)] - HostMigrationFinished, - #[command(target = 0x46, notify)] - HostMigrationStart, - #[command(target = 0x47, notify)] - PlatformHostInitialized, - #[command(target = 0x50, notify)] - GameAttribChange, - #[command(target = 0x5A, notify)] - PlayerAttribChange, - #[command(target = 0x5F, notify)] - PlayerCustomDataChange, - #[command(target = 0x64, notify)] - GameStateChange, - #[command(target = 0x6E, notify)] - GameSettingsChange, - #[command(target = 0x6F, notify)] - GameCapacityChange, - #[command(target = 0x70, notify)] - GameReset, - #[command(target = 0x71, notify)] - GameReportingIDChange, - #[command(target = 0x73, notify)] - GameSessionUpdated, - #[command(target = 0x74, notify)] - GamePlayerStateChange, - #[command(target = 0x75, notify)] - GamePlayerTeamChange, - #[command(target = 0x76, notify)] - GameTeamIDChange, - #[command(target = 0x77, notify)] - ProcesssQueue, - #[command(target = 0x78, notify)] - PrecenseModeChanged, - #[command(target = 0x79, notify)] - GamePlayerQueuePositionChange, - #[command(target = 0xC9, notify)] - GameListUpdate, - #[command(target = 0xCA, notify)] - AdminListChange, - #[command(target = 0xDC, notify)] - CreateDynamicDedicatedServerGame, - #[command(target = 0xE6, notify)] - GameNameChange, +/// Creates an u32 value from the provided component +/// and command merging them into a single u32 +pub const fn component_key(component: u16, command: u16) -> ComponentKey { + ((component as u32) << 16) + command as u32 } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum Redirector { - #[command(target = 0x1)] - GetServerInstance, +pub mod authentication { + pub const COMPONENT: u16 = 0x1; + + // Commands + + pub const CREATE_ACCOUNT: u16 = 0xA; + pub const UPDATE_ACCOUNT: u16 = 0x14; + pub const UPDATE_PARENTAL_EMAIL: u16 = 0x1C; + pub const LIST_USER_ENTITLEMENTS_2: u16 = 0x1D; + pub const GET_ACCOUNT: u16 = 0x1E; + pub const GRANT_ENTITLEMENT: u16 = 0x1F; + pub const LIST_ENTITLEMENTS: u16 = 0x20; + pub const HAS_ENTITLEMENT: u16 = 0x21; + pub const GET_USE_COUNT: u16 = 0x22; + pub const DECREMENT_USE_COUNT: u16 = 0x23; + pub const GET_AUTH_TOKEN: u16 = 0x24; + pub const GET_HANDOFF_TOKEN: u16 = 0x25; + pub const GET_PASSWORD_RULES: u16 = 0x26; + pub const GRANT_ENTITLEMENT_2: u16 = 0x27; + pub const LOGIN: u16 = 0x28; + pub const ACCEPT_TOS: u16 = 0x29; + pub const GET_TOS_INFO: u16 = 0x2A; + pub const MODIFY_ENTITLEMENT_2: u16 = 0x2B; + pub const CONSUME_CODE: u16 = 0x2C; + pub const PASSWORD_FORGOT: u16 = 0x2D; + pub const GET_TOS_CONTENT: u16 = 0x2E; + pub const GET_PRIVACY_POLICY_CONTENT: u16 = 0x2F; + pub const LIST_PERSONAL_ENTITLEMENTS_2: u16 = 0x30; + pub const SILENT_LOGIN: u16 = 0x32; + pub const CHECK_AGE_REQUIREMENT: u16 = 0x33; + pub const GET_OPT_IN: u16 = 0x34; + pub const ENABLE_OPT_IN: u16 = 0x35; + pub const DISABLE_OPT_IN: u16 = 0x36; + pub const EXPRESS_LOGIN: u16 = 0x3C; + pub const LOGOUT: u16 = 0x46; + pub const CREATE_PERSONA: u16 = 0x50; + pub const GET_PERSONA: u16 = 0x5A; + pub const LIST_PERSONAS: u16 = 0x64; + pub const LOGIN_PERSONA: u16 = 0x6E; + pub const LOGOUT_PERSONA: u16 = 0x78; + pub const DELETE_PERSONA: u16 = 0x8C; + pub const DISABLE_PERSONA: u16 = 0x8D; + pub const LIST_DEVICE_ACCOUNTS: u16 = 0x8F; + pub const XBOX_CREATE_ACCOUNT: u16 = 0x96; + pub const ORIGIN_LOGIN: u16 = 0x98; + pub const XBOX_ASSOCIATE_ACCOUNT: u16 = 0xA0; + pub const XBOX_LOGIN: u16 = 0xAA; + pub const PS3_CREATE_ACCOUNT: u16 = 0xB4; + pub const PS3_ASSOCIATE_ACCOUNT: u16 = 0xBE; + pub const PS3_LOGIN: u16 = 0xC8; + pub const VALIDATE_SESSION_KEY: u16 = 0xD2; + pub const CREATE_WAL_USER_SESSION: u16 = 0xE6; + pub const ACCEPT_LEGAL_DOCS: u16 = 0xF1; + pub const GET_LEGAL_DOCS_INFO: u16 = 0xF2; + pub const GET_TERMS_OF_SERVICE_CONTENT: u16 = 0xF6; + pub const DEVICE_LOGIN_GUEST: u16 = 0x12C; } -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum Stats { - #[command(target = 0x1)] - GetStatDecs, - #[command(target = 0x2)] - GetStats, - #[command(target = 0x3)] - GetStatGroupList, - #[command(target = 0x4)] - GetStatGroup, - #[command(target = 0x5)] - GetStatsByGroup, - #[command(target = 0x6)] - GetDateRange, - #[command(target = 0x7)] - GetEntityCount, - #[command(target = 0xA)] - GetLeaderboardGroup, - #[command(target = 0xB)] - GetLeaderboardFolderGroup, - #[command(target = 0xC)] - GetLeaderboard, - #[command(target = 0xD)] - GetCenteredLeaderboard, - #[command(target = 0xE)] - GetFilteredLeaderboard, - #[command(target = 0xF)] - GetKeyScopesMap, - #[command(target = 0x10)] - GetStatsByGroupASync, - #[command(target = 0x11)] - GetLeaderboardTreeAsync, - #[command(target = 0x12)] - GetLeaderboardEntityCount, - #[command(target = 0x13)] - GetStatCategoryList, - #[command(target = 0x14)] - GetPeriodIDs, - #[command(target = 0x15)] - GetLeaderboardRaw, - #[command(target = 0x16)] - GetCenteredLeaderboardRaw, - #[command(target = 0x17)] - GetFilteredLeaderboardRaw, - #[command(target = 0x18)] - ChangeKeyScopeValue, +pub mod game_manager { + use tdf::ObjectType; + + pub const COMPONENT: u16 = 0x4; + + // Components + pub const CREATE_GAME: u16 = 0x1; + pub const DESTROY_GAME: u16 = 0x2; + pub const ADVANCE_GAME_STATE: u16 = 0x3; + pub const SET_GAME_SETTINGS: u16 = 0x4; + pub const SET_PLAYER_CAPACITY: u16 = 0x5; + pub const SET_PRESENCE_MODE: u16 = 0x6; + pub const SET_GAME_ATTRIBUTES: u16 = 0x7; + pub const SET_PLAYER_ATTRIBUTES: u16 = 0x8; + pub const JOIN_GAME: u16 = 0x9; + // 0xA -- + pub const REMOVE_PLAYER: u16 = 0xB; + // 0xC -- + pub const START_MATCHMAKING: u16 = 0xD; + pub const CANCEL_MATCHMAKING: u16 = 0xE; + pub const FINALIZE_GAME_CREATION: u16 = 0xF; + // 0x10 -- + pub const LIST_GAMES: u16 = 0x11; + pub const SET_PLAYER_CUSTOM_DATA: u16 = 0x12; + pub const REPLAY_GAME: u16 = 0x13; + pub const RETURN_DEDICATED_SERVER_TO_POOL: u16 = 0x14; + pub const JOIN_GAME_BY_GROUP: u16 = 0x15; + pub const LEAVE_GAME_BY_GROUP: u16 = 0x16; + pub const MIGRATE_GAME: u16 = 0x17; + pub const UPDATE_GAME_HOST_MIGRATION_STATUS: u16 = 0x18; + pub const RESET_DEDICATED_SERVER: u16 = 0x19; + pub const UPDATE_GAME_SESSION: u16 = 0x1A; + pub const BAN_PLAYER: u16 = 0x1B; + // 0x1C -- + pub const UPDATE_MESH_CONNECTION: u16 = 0x1D; + // 0x1E -- + pub const REMOVE_PLAYER_FROM_BANNED_LIST: u16 = 0x1F; + pub const CLEAR_BANNED_LIST: u16 = 0x20; + pub const GET_BANNED_LIST: u16 = 0x21; + // 0x22-0x25 -- + pub const ADD_QUEUED_PLAYER_TO_GAME: u16 = 0x26; + pub const UPDATE_GAME_NAME: u16 = 0x27; + pub const EJECT_HOST: u16 = 0x28; + // 0x29-0x63 -- + pub const GET_GAME_LIST_SNAPSHOT: u16 = 0x64; + pub const GET_GAME_LIST_SUBSCRIPTION: u16 = 0x65; + pub const DESTROY_GAME_LIST: u16 = 0x66; + pub const GET_FULL_GAME_DATA: u16 = 0x67; + pub const GET_MATCHMAKING_CONFIG: u16 = 0x68; + pub const GET_GAME_DATA_FROM_ID: u16 = 0x69; + pub const ADD_ADMIN_PLAYER: u16 = 0x6A; + pub const REMOVE_ADMIN_PLAYER: u16 = 0x6B; + pub const SET_PLAYER_TEAM: u16 = 0x6C; + pub const CHANGE_GAME_TEAM_ID: u16 = 0x6D; + pub const MIGRATE_ADMIN_PLAYER: u16 = 0x6E; + pub const GET_USER_SET_GAME_LIST_SUBSCRIPTION: u16 = 0x6F; + pub const SWAP_PLAYERS_TEAM: u16 = 0x70; + // 0x71-0x95 -- + pub const REGISTER_DYNAMIC_DEDICATED_SERVER_CREATOR: u16 = 0x96; + pub const UNREGISTER_DYNAMIC_DEDICATED_SERVER_CREATOR: u16 = 0x97; + + // Notifications + pub const MATCHMAKING_FAILED: u16 = 0xA; + // 0xB -- + pub const MATCHMAKING_ASYNC_STATUS: u16 = 0xC; + // 0xD-0xE -- + pub const GAME_CREATED: u16 = 0xF; + pub const GAME_REMOVED: u16 = 0x10; + // 0x11-0x13 -- + pub const GAME_SETUP: u16 = 0x14; + pub const PLAYER_JOINING: u16 = 0x15; + pub const JOINING_PLAYER_INITIATE_CONNECTIONS: u16 = 0x16; + pub const PLAYER_JOINING_QUEUE: u16 = 0x17; + pub const PLAYER_PROMOTED_FROM_QUEUE: u16 = 0x18; + pub const PLAYER_CLAIMING_RESERVATION: u16 = 0x19; + pub const PLAYER_JOIN_COMPLETED: u16 = 0x1E; + // 0x1F-0x27 -- + pub const PLAYER_REMOVED: u16 = 0x28; + // 0x29-0x3B -- + pub const HOST_MIGRATION_FINISHED: u16 = 0x3C; + // 0x3D-0x45 -- + pub const HOST_MIGRATION_START: u16 = 0x46; + pub const PLATFORM_HOST_INITIALIZED: u16 = 0x47; + // 0x48-0x4F -- + pub const GAME_ATTRIB_CHANGE: u16 = 0x50; + // 0x51-0x59 -- + pub const PLAYER_ATTRIB_CHANGE: u16 = 0x5A; + pub const PLAYER_CUSTOM_DATA_CHANGE: u16 = 0x5F; + // 0x60-0x63 -- + pub const GAME_STATE_CHANGE: u16 = 0x64; + // 0x64-0x6D -- + pub const GAME_SETTINGS_CHANGE: u16 = 0x6E; + pub const GAME_CAPACITY_CHANGE: u16 = 0x6F; + pub const GAME_RESET: u16 = 0x70; + pub const GAME_REPORTING_ID_CHANGE: u16 = 0x71; + // 0x72 -- + pub const GAME_SESSION_UPDATED: u16 = 0x73; + pub const GAME_PLAYER_STATE_CHANGE: u16 = 0x74; + pub const GAME_PLAYER_TEAM_CHANGE: u16 = 0x75; + pub const GAME_TEAM_ID_CHANGE: u16 = 0x76; + pub const PROCESS_QUEUE: u16 = 0x77; + pub const PRECENSE_MODE_CHANGED: u16 = 0x78; + pub const GAME_PLAYER_QUEUE_POSITION_CHANGE: u16 = 0x79; + // 0x7A-0xC8 -- + pub const GAME_LIST_UPDATE: u16 = 0xC9; + pub const ADMIN_LIST_CHANGE: u16 = 0xCA; + // 0xCB-0xDB -- + pub const CREATE_DYNAMIC_DEDICATED_SERVER_GAME: u16 = 0xDC; + // 0xDD-0xE5 -- + pub const GAME_NAME_CHANGE: u16 = 0xE6; + + // Object Types + pub const GAME_TYPE: ObjectType = ObjectType::new(COMPONENT, 1); } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum Util { - #[command(target = 0x1)] - FetchClientConfig, - #[command(target = 0x2)] - Ping, - #[command(target = 0x3)] - SetClientData, - #[command(target = 0x4)] - LocalizeStrings, - #[command(target = 0x5)] - GetTelemetryServer, - #[command(target = 0x6)] - GetTickerServer, - #[command(target = 0x7)] - PreAuth, - #[command(target = 0x8)] - PostAuth, - #[command(target = 0xA)] - UserSettingsLoad, - #[command(target = 0xB)] - UserSettingsSave, - #[command(target = 0xC)] - UserSettingsLoadAll, - #[command(target = 0xE)] - DeleteUserSettings, - #[command(target = 0x14)] - FilterForProfanity, - #[command(target = 0x15)] - FetchQOSConfig, - #[command(target = 0x16)] - SetClientMetrics, - #[command(target = 0x17)] - SetConnectionState, - #[command(target = 0x18)] - GetPSSConfig, - #[command(target = 0x19)] - GetUserOptions, - #[command(target = 0x1A)] - SetUserOptions, - #[command(target = 0x1B)] - SuspendUserPing, +pub mod redirector { + pub const COMPONENT: u16 = 0x5; + + pub const GET_SERVER_INSTANCE: u16 = 0x1; } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum Messaging { - #[command(target = 0x2)] - FetchMessages, - #[command(target = 0x3)] - PurgeMessages, - #[command(target = 0x4)] - TouchMessages, - #[command(target = 0x5)] - GetMessages, - #[command(target = 0x1, notify)] - SendMessage, +pub mod stats { + pub const COMPONENT: u16 = 0x7; + + /// Components + pub const GET_STAT_DECS: u16 = 0x1; + pub const GET_STATS: u16 = 0x2; + pub const GET_STAT_GROUP_LIST: u16 = 0x3; + pub const GET_STAT_GROUP: u16 = 0x4; + pub const GET_STATS_BY_GROUP: u16 = 0x5; + pub const GET_DATE_RANGE: u16 = 0x6; + pub const GET_ENTITY_COUNT: u16 = 0x7; + // 0x8-0x9 -- + pub const GET_LEADERBOARD_GROUP: u16 = 0xA; + pub const GET_LEADERBOARD_FOLDER_GROUP: u16 = 0xB; + pub const GET_LEADERBOARD: u16 = 0xC; + pub const GET_CENTERED_LEADERBOARD: u16 = 0xD; + pub const GET_FILTERED_LEADERBOARD: u16 = 0xE; + pub const GET_KEY_SCOPES_MAP: u16 = 0xF; + pub const GET_STATS_BY_GROUP_ASYNC: u16 = 0x10; + pub const GET_LEADERBOARD_TREE_ASYNC: u16 = 0x11; + pub const GET_LEADERBOARD_ENTITY_COUNT: u16 = 0x12; + pub const GET_STAT_CATEGORY_LIST: u16 = 0x13; + pub const GET_PERIOD_IDS: u16 = 0x14; + pub const GET_LEADERBOARD_RAW: u16 = 0x15; + pub const GET_CENTERED_LEADERBOARD_RAW: u16 = 0x16; + pub const GET_FILTERED_LEADERBOARD_RAW: u16 = 0x17; + pub const CHANGE_KEY_SCOPE_VALUE: u16 = 0x18; } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum AssociationLists { - #[command(target = 0x1)] - AddUsersToList, - #[command(target = 0x2)] - RemoveUsersFromList, - #[command(target = 0x3)] - ClearList, - #[command(target = 0x4)] - SetUsersToList, - #[command(target = 0x5)] - GetListForUser, - #[command(target = 0x6)] - GetLists, - #[command(target = 0x7)] - SubscribeToLists, - #[command(target = 0x8)] - UnsubscribeToLists, - #[command(target = 0x9)] - GetConfigListsInfo, +pub mod util { + pub const COMPONENT: u16 = 0x9; + + /// Components + pub const FETCH_CLIENT_CONFIG: u16 = 0x1; + pub const PING: u16 = 0x2; + pub const SET_CLIENT_DATA: u16 = 0x3; + pub const LOCALIZE_STRINGS: u16 = 0x4; + pub const GET_TELEMETRY_SERVER: u16 = 0x5; + pub const GET_TICKER_SERVER: u16 = 0x6; + pub const PRE_AUTH: u16 = 0x7; + pub const POST_AUTH: u16 = 0x8; + // 0x9 -- + pub const USER_SETTINGS_LOAD: u16 = 0xA; + pub const USER_SETTINGS_SAVE: u16 = 0xB; + pub const USER_SETTINGS_LOAD_ALL: u16 = 0xC; + // 0xD -- + pub const DELETE_USER_SETTINGS: u16 = 0xE; + // + pub const FILTER_FOR_PROFANITY: u16 = 0x14; + pub const FETCH_QOS_CONFIG: u16 = 0x15; + pub const SET_CLIENT_METRICS: u16 = 0x16; + pub const SET_CONNECTION_STATE: u16 = 0x17; + pub const GET_PSS_CONFIG: u16 = 0x18; + pub const GET_USER_OPTIONS: u16 = 0x19; + pub const SET_USER_OPTIONS: u16 = 0x1A; + pub const SUSPEND_USER_PING: u16 = 0x1B; } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum GameReporting { - #[command(target = 0x1)] - SubmitGameReport, - #[command(target = 0x2)] - SubmitOfflineGameReport, - #[command(target = 0x3)] - SubmitGameEvents, - #[command(target = 0x4)] - GetGameReportQuery, - #[command(target = 0x5)] - GetGameReportQueriesList, - #[command(target = 0x6)] - GetGameReports, - #[command(target = 0x7)] - GetGameReportView, - #[command(target = 0x8)] - GetGameReportViewInfo, - #[command(target = 0x9)] - GetGameReportViewInfoList, - #[command(target = 0xA)] - GetGameReportTypes, - #[command(target = 0xB)] - UpdateMetrics, - #[command(target = 0xC)] - GetGameReportColumnInfo, - #[command(target = 0xD)] - GetGameReortColummnValues, - #[command(target = 0x64)] - SubmitTrustedMidGameReport, - #[command(target = 0x65)] - SubmitTrustedEndGameReport, - #[command(target = 0x72, notify)] - GameReportSubmitted, +pub mod messaging { + pub const COMPONENT: u16 = 0xF; + + /// Components + pub const FETCH_MESSAGES: u16 = 0x2; + pub const PURGE_MESSAGES: u16 = 0x3; + pub const TOUCH_MESSAGES: u16 = 0x4; + pub const GET_MESSAGES: u16 = 0x5; + + // Notifications + pub const SEND_MESSAGE: u16 = 0x1; } -#[derive(Debug, Hash, PartialEq, Eq, PacketComponent)] -pub enum UserSessions { - #[command(target = 0x8)] - UpdateHardwareFlags, - #[command(target = 0xC)] - LookupUser, - #[command(target = 0xD)] - LookupUsers, - #[command(target = 0xE)] - LookupUsersByPrefix, - #[command(target = 0x14)] - UpdateNetworkInfo, - #[command(target = 0x17)] - LookupUserGeoIPData, - #[command(target = 0x18)] - OverrideUserGeoIPData, - #[command(target = 0x19)] - UpdateUserSessionClientData, - #[command(target = 0x1A)] - SetUserInfoAttribute, - #[command(target = 0x1B)] - ResetUserGeoIPData, - #[command(target = 0x20)] - LookupUserSessionID, - #[command(target = 0x21)] - FetchLastLocaleUsedAndAuthError, - #[command(target = 0x22)] - FetchUserFirstLastAuthTime, - #[command(target = 0x23)] - ResumeSession, - #[command(target = 0x1, notify)] - SetSession, - #[command(target = 0x2, notify)] - SessionDetails, - #[command(target = 0x5, notify)] - UpdateExtendedDataAttribute, - #[command(target = 0x3, notify)] - FetchExtendedData, +pub mod association_lists { + use tdf::ObjectType; + + pub const COMPONENT: u16 = 0x19; + + // Components + pub const ADD_USERS_TO_LIST: u16 = 0x1; + pub const REMOVE_USERS_FROM_LIST: u16 = 0x2; + pub const CLEAR_LIST: u16 = 0x3; + pub const SET_USERS_TO_LIST: u16 = 0x4; + pub const GET_LIST_FOR_USER: u16 = 0x5; + pub const GET_LISTS: u16 = 0x6; + pub const SUBSCRIBE_TO_LISTS: u16 = 0x7; + pub const UNSUBSCRIBE_TO_LISTS: u16 = 0x8; + pub const GET_CONFIG_LISTS_INFO: u16 = 0x9; + + // Object Types + pub const ASSOC_LIST_REF: ObjectType = ObjectType::new(COMPONENT, 1); +} + +pub mod game_reporting { + pub const COMPONENT: u16 = 0x1C; + + // Components + pub const SUBMIT_GAME_REPORT: u16 = 0x1; + pub const SUBMIT_OFFLINE_GAME_REPORT: u16 = 0x2; + pub const SUBMIT_GAME_EVENTS: u16 = 0x3; + pub const GET_GAME_REPORT_QUERY: u16 = 0x4; + pub const GET_GAME_REPORT_QUERIES_LIST: u16 = 0x5; + pub const GET_GAME_REPORTS: u16 = 0x6; + pub const GET_GAME_REPORT_VIEW: u16 = 0x7; + pub const GET_GAME_REPORT_VIEW_INFO: u16 = 0x8; + pub const GET_GAME_REPORT_VIEW_INFO_LIST: u16 = 0x9; + pub const GET_GAME_REPORT_TYPES: u16 = 0xA; + pub const UPDATE_METRICS: u16 = 0xB; + pub const GET_GAME_REPORT_COLUMN_INFO: u16 = 0xC; + pub const GET_GAME_REPORT_COLUMN_VALUES: u16 = 0xD; + // 0xE-0x63 -- + pub const SUBMIT_TRUSTED_MID_GAME_REPORT: u16 = 0x64; + pub const SUBMIT_TRUSTED_END_GAME_REPORT: u16 = 0x65; + + // Notifications + pub const GAME_REPORT_SUBMITTED: u16 = 0x72; +} + +pub mod user_sessions { + use tdf::ObjectType; + + pub const COMPONENT: u16 = 0x7802; + + // Components + pub const UPDATE_HARDWARE_FLAGS: u16 = 0x8; + pub const LOOKUP_USER: u16 = 0xC; + pub const LOOKUP_USERS: u16 = 0xD; + pub const LOOKUP_USERS_BY_PREFIX: u16 = 0xE; + // 0xF-0x13 -- + pub const UPDATE_NETWORK_INFO: u16 = 0x14; + // 0x15-0x16 -- + pub const LOOKUP_USER_GEO_IP_DATA: u16 = 0x17; + pub const OVERRIDE_USER_GEO_IP_DATA: u16 = 0x18; + pub const UPDATE_USER_SESSION_CLIENT_DATA: u16 = 0x19; + pub const SET_USER_INFO_ATTRIBUTE: u16 = 0x1A; + pub const RESET_USER_GEO_IP_DATA: u16 = 0x1B; + // 0x1C-0x1F -- + pub const LOOKUP_USER_SESSION_ID: u16 = 0x20; + pub const FETCH_LAST_LOCALE_USED_AND_AUTH_ERROR: u16 = 0x21; + pub const FETCH_USER_FIRST_LAST_AUTH_TIME: u16 = 0x22; + pub const RESUME_SESSION: u16 = 0x23; + + // Notifications + pub const SET_SESSION: u16 = 0x1; + pub const SESSION_DETAILS: u16 = 0x2; + pub const FETCH_EXTENDED_DATA: u16 = 0x3; + pub const UPDATE_EXTENDED_DATA_ATTRIBUTE: u16 = 0x5; + + // Object Types + pub const PLAYER_TYPE: ObjectType = ObjectType::new(COMPONENT, 1); +} + +#[rustfmt::skip] +fn commands() -> HashMap { + use authentication as a; + use game_manager as g; + use redirector as r; + use stats as s; + use util as u; + use messaging as m; + use association_lists as al; + use game_reporting as gr; + use user_sessions as us; + + [ + // Authentication + (component_key(a::COMPONENT, a::CREATE_ACCOUNT), "CreateAccount"), + (component_key(a::COMPONENT, a::UPDATE_ACCOUNT), "UpdateAccount"), + (component_key(a::COMPONENT, a::UPDATE_PARENTAL_EMAIL), "UpdateParentalEmail"), + (component_key(a::COMPONENT, a::LIST_USER_ENTITLEMENTS_2), "ListUserEntitlements2"), + (component_key(a::COMPONENT, a::GET_ACCOUNT), "GetAccount"), + (component_key(a::COMPONENT, a::GRANT_ENTITLEMENT), "GrantEntitlement"), + (component_key(a::COMPONENT, a::LIST_ENTITLEMENTS), "ListEntitlements"), + (component_key(a::COMPONENT, a::HAS_ENTITLEMENT), "HasEntitlement"), + (component_key(a::COMPONENT, a::GET_USE_COUNT), "GetUseCount"), + (component_key(a::COMPONENT, a::DECREMENT_USE_COUNT), "DecrementUseCount"), + (component_key(a::COMPONENT, a::GET_AUTH_TOKEN), "GetAuthToken"), + (component_key(a::COMPONENT, a::GET_HANDOFF_TOKEN), "GetHandoffToken"), + (component_key(a::COMPONENT, a::GET_PASSWORD_RULES), "GetPasswordRules"), + (component_key(a::COMPONENT, a::GRANT_ENTITLEMENT_2), "GrantEntitlement2"), + (component_key(a::COMPONENT, a::LOGIN), "Login"), + (component_key(a::COMPONENT, a::ACCEPT_TOS), "AcceptTOS"), + (component_key(a::COMPONENT, a::GET_TOS_INFO), "GetTOSInfo"), + (component_key(a::COMPONENT, a::MODIFY_ENTITLEMENT_2), "ModifyEntitlement2"), + (component_key(a::COMPONENT, a::CONSUME_CODE), "ConsumeCode"), + (component_key(a::COMPONENT, a::PASSWORD_FORGOT), "PasswordForgot"), + (component_key(a::COMPONENT, a::GET_TOS_CONTENT), "GetTOSContent"), + (component_key(a::COMPONENT, a::GET_PRIVACY_POLICY_CONTENT), "GetPrivacyPolicyContent"), + (component_key(a::COMPONENT, a::LIST_PERSONAL_ENTITLEMENTS_2), "ListPersonalEntitlements2"), + (component_key(a::COMPONENT, a::SILENT_LOGIN), "SilentLogin"), + (component_key(a::COMPONENT, a::CHECK_AGE_REQUIREMENT), "CheckAgeRequirement"), + (component_key(a::COMPONENT, a::GET_OPT_IN), "GetOptIn"), + (component_key(a::COMPONENT, a::ENABLE_OPT_IN), "EnableOptIn"), + (component_key(a::COMPONENT, a::DISABLE_OPT_IN), "DisableOptIn"), + (component_key(a::COMPONENT, a::EXPRESS_LOGIN), "ExpressLogin"), + (component_key(a::COMPONENT, a::LOGOUT), "Logout"), + (component_key(a::COMPONENT, a::CREATE_PERSONA), "CreatePersona"), + (component_key(a::COMPONENT, a::GET_PERSONA), "GetPersona"), + (component_key(a::COMPONENT, a::LIST_PERSONAS), "ListPersonas"), + (component_key(a::COMPONENT, a::LOGIN_PERSONA), "LoginPersona"), + (component_key(a::COMPONENT, a::LOGOUT_PERSONA), "LogoutPersona"), + (component_key(a::COMPONENT, a::DELETE_PERSONA), "DeletePersona"), + (component_key(a::COMPONENT, a::DISABLE_PERSONA), "DisablePersona"), + (component_key(a::COMPONENT, a::LIST_DEVICE_ACCOUNTS), "ListDeviceAccounts"), + (component_key(a::COMPONENT, a::XBOX_CREATE_ACCOUNT), "XboxCreateAccount"), + (component_key(a::COMPONENT, a::ORIGIN_LOGIN), "OriginLogin"), + (component_key(a::COMPONENT, a::XBOX_ASSOCIATE_ACCOUNT), "XboxAssociateAccount"), + (component_key(a::COMPONENT, a::XBOX_LOGIN), "XboxLogin"), + (component_key(a::COMPONENT, a::PS3_CREATE_ACCOUNT), "PS3CreateAccount"), + (component_key(a::COMPONENT, a::PS3_ASSOCIATE_ACCOUNT), "PS3AssociateAccount"), + (component_key(a::COMPONENT, a::PS3_LOGIN), "PS3Login"), + (component_key(a::COMPONENT, a::VALIDATE_SESSION_KEY), "ValidateSessionKey"), + (component_key(a::COMPONENT, a::CREATE_WAL_USER_SESSION), "CreateWalUserSession"), + (component_key(a::COMPONENT, a::ACCEPT_LEGAL_DOCS), "AcceptLegalDocs"), + (component_key(a::COMPONENT, a::GET_LEGAL_DOCS_INFO), "GetLegalDocsInfo"), + (component_key(a::COMPONENT, a::GET_TERMS_OF_SERVICE_CONTENT), "GetTermsOfServiceContent"), + (component_key(a::COMPONENT, a::DEVICE_LOGIN_GUEST), "DeviceLoginGuest"), + + // Game Manager + (component_key(g::COMPONENT, g::CREATE_GAME), "CreateGame"), + (component_key(g::COMPONENT, g::DESTROY_GAME), "DestroyGame"), + (component_key(g::COMPONENT, g::ADVANCE_GAME_STATE), "AdvanceGameState"), + (component_key(g::COMPONENT, g::SET_GAME_SETTINGS), "SetGameSettings"), + (component_key(g::COMPONENT, g::SET_PLAYER_CAPACITY), "SetPlayerCapacity"), + (component_key(g::COMPONENT, g::SET_PRESENCE_MODE), "SetPresenceMode"), + (component_key(g::COMPONENT, g::SET_GAME_ATTRIBUTES), "SetGameAttributes"), + (component_key(g::COMPONENT, g::SET_PLAYER_ATTRIBUTES), "SetPlayerAttributes"), + (component_key(g::COMPONENT, g::JOIN_GAME), "JoinGame"), + (component_key(g::COMPONENT, g::REMOVE_PLAYER), "RemovePlayer"), + (component_key(g::COMPONENT, g::START_MATCHMAKING), "StartMatchmaking"), + (component_key(g::COMPONENT, g::CANCEL_MATCHMAKING), "CancelMatchmaking"), + (component_key(g::COMPONENT, g::FINALIZE_GAME_CREATION), "FinalizeGameCreation"), + (component_key(g::COMPONENT, g::LIST_GAMES), "ListGames"), + (component_key(g::COMPONENT, g::SET_PLAYER_CUSTOM_DATA), "SetPlayerCustomData"), + (component_key(g::COMPONENT, g::REPLAY_GAME), "ReplayGame"), + (component_key(g::COMPONENT, g::RETURN_DEDICATED_SERVER_TO_POOL), "ReturnDedicatedServerToPool"), + (component_key(g::COMPONENT, g::JOIN_GAME_BY_GROUP), "JoinGameByGroup"), + (component_key(g::COMPONENT, g::LEAVE_GAME_BY_GROUP), "LeaveGameByGroup"), + (component_key(g::COMPONENT, g::MIGRATE_GAME), "MigrateGame"), + (component_key(g::COMPONENT, g::UPDATE_GAME_HOST_MIGRATION_STATUS), "UpdateGameHostMigrationStatus"), + (component_key(g::COMPONENT, g::RESET_DEDICATED_SERVER), "ResetDedicatedServer"), + (component_key(g::COMPONENT, g::UPDATE_GAME_SESSION), "UpdateGameSession"), + (component_key(g::COMPONENT, g::BAN_PLAYER), "BanPlayer"), + (component_key(g::COMPONENT, g::UPDATE_MESH_CONNECTION), "UpdateMeshConnection"), + (component_key(g::COMPONENT, g::REMOVE_PLAYER_FROM_BANNED_LIST), "RemovePlayerFromBannedList"), + (component_key(g::COMPONENT, g::CLEAR_BANNED_LIST), "ClearBannedList"), + (component_key(g::COMPONENT, g::GET_BANNED_LIST), "GetBannedList"), + (component_key(g::COMPONENT, g::ADD_QUEUED_PLAYER_TO_GAME), "AddQueuedPlayerToGame"), + (component_key(g::COMPONENT, g::UPDATE_GAME_NAME), "UpdateGameName"), + (component_key(g::COMPONENT, g::EJECT_HOST), "EjectHost"), + (component_key(g::COMPONENT, g::GET_GAME_LIST_SNAPSHOT), "GetGameListSnapshot"), + (component_key(g::COMPONENT, g::GET_GAME_LIST_SUBSCRIPTION), "GetGameListSubscription"), + (component_key(g::COMPONENT, g::DESTROY_GAME_LIST), "DestroyGameList"), + (component_key(g::COMPONENT, g::GET_FULL_GAME_DATA), "GetFullGameData"), + (component_key(g::COMPONENT, g::GET_MATCHMAKING_CONFIG), "GetMatchmakingConfig"), + (component_key(g::COMPONENT, g::GET_GAME_DATA_FROM_ID), "GetGameDataFromID"), + (component_key(g::COMPONENT, g::ADD_ADMIN_PLAYER), "AddAdminPlayer"), + (component_key(g::COMPONENT, g::REMOVE_ADMIN_PLAYER), "RemoveAdminPlayer"), + (component_key(g::COMPONENT, g::SET_PLAYER_TEAM), "SetPlayerTeam"), + (component_key(g::COMPONENT, g::CHANGE_GAME_TEAM_ID), "ChangeGameTeamID"), + (component_key(g::COMPONENT, g::MIGRATE_ADMIN_PLAYER), "MigrateAdminPlayer"), + (component_key(g::COMPONENT, g::GET_USER_SET_GAME_LIST_SUBSCRIPTION), "GetUserSetGameListSubscription"), + (component_key(g::COMPONENT, g::SWAP_PLAYERS_TEAM), "SwapPlayersTeam"), + (component_key(g::COMPONENT, g::REGISTER_DYNAMIC_DEDICATED_SERVER_CREATOR), "RegisterDynamicDedicatedServerCreator"), + (component_key(g::COMPONENT, g::UNREGISTER_DYNAMIC_DEDICATED_SERVER_CREATOR), "UnregisterDynamicDedicatedServerCreator"), + + // Redirector + (component_key(r::COMPONENT, r::GET_SERVER_INSTANCE), "GetServerInstance"), + + // Stats + (component_key(s::COMPONENT, s::GET_STAT_DECS), "GetStatDecs"), + (component_key(s::COMPONENT, s::GET_STATS), "GetStats"), + (component_key(s::COMPONENT, s::GET_STAT_GROUP_LIST), "GetStatGroupList"), + (component_key(s::COMPONENT, s::GET_STAT_GROUP), "GetStatGroup"), + (component_key(s::COMPONENT, s::GET_STATS_BY_GROUP), "GetStatsByGroup"), + (component_key(s::COMPONENT, s::GET_DATE_RANGE), "GetDateRange"), + (component_key(s::COMPONENT, s::GET_ENTITY_COUNT), "GetEntityCount"), + (component_key(s::COMPONENT, s::GET_LEADERBOARD_GROUP), "GetLeaderboardGroup"), + (component_key(s::COMPONENT, s::GET_LEADERBOARD_FOLDER_GROUP), "GetLeaderboardFolderGroup"), + (component_key(s::COMPONENT, s::GET_LEADERBOARD), "GetLeaderboard"), + (component_key(s::COMPONENT, s::GET_CENTERED_LEADERBOARD), "GetCenteredLeaderboard"), + (component_key(s::COMPONENT, s::GET_FILTERED_LEADERBOARD), "GetFilteredLeaderboard"), + (component_key(s::COMPONENT, s::GET_KEY_SCOPES_MAP), "GetKeyScopesMap"), + (component_key(s::COMPONENT, s::GET_STATS_BY_GROUP_ASYNC), "GetStatsByGroupASync"), + (component_key(s::COMPONENT, s::GET_LEADERBOARD_TREE_ASYNC), "GetLeaderboardTreeAsync"), + (component_key(s::COMPONENT, s::GET_LEADERBOARD_ENTITY_COUNT), "GetLeaderboardEntityCount"), + (component_key(s::COMPONENT, s::GET_STAT_CATEGORY_LIST), "GetStatCategoryList"), + (component_key(s::COMPONENT, s::GET_PERIOD_IDS), "GetPeriodIDs"), + (component_key(s::COMPONENT, s::GET_LEADERBOARD_RAW), "GetLeaderboardRaw"), + (component_key(s::COMPONENT, s::GET_CENTERED_LEADERBOARD_RAW), "GetCenteredLeaderboardRaw"), + (component_key(s::COMPONENT, s::GET_FILTERED_LEADERBOARD_RAW), "GetFilteredLeaderboardRaw"), + (component_key(s::COMPONENT, s::CHANGE_KEY_SCOPE_VALUE), "ChangeKeyScopeValue"), + + // Util + (component_key(u::COMPONENT, u::FETCH_CLIENT_CONFIG), "FetchClientConfig"), + (component_key(u::COMPONENT, u::PING), "Ping"), + (component_key(u::COMPONENT, u::SET_CLIENT_DATA), "SetClientData"), + (component_key(u::COMPONENT, u::LOCALIZE_STRINGS), "LocalizeStrings"), + (component_key(u::COMPONENT, u::GET_TELEMETRY_SERVER), "GetTelemetryServer"), + (component_key(u::COMPONENT, u::GET_TICKER_SERVER), "GetTickerServer"), + (component_key(u::COMPONENT, u::PRE_AUTH), "PreAuth"), + (component_key(u::COMPONENT, u::POST_AUTH), "PostAuth"), + (component_key(u::COMPONENT, u::USER_SETTINGS_LOAD), "UserSettingsLoad"), + (component_key(u::COMPONENT, u::USER_SETTINGS_SAVE), "UserSettingsSave"), + (component_key(u::COMPONENT, u::USER_SETTINGS_LOAD_ALL), "UserSettingsLoadAll"), + (component_key(u::COMPONENT, u::DELETE_USER_SETTINGS), "DeleteUserSettings"), + (component_key(u::COMPONENT, u::FILTER_FOR_PROFANITY), "FilterForProfanity"), + (component_key(u::COMPONENT, u::FETCH_QOS_CONFIG), "FetchQOSConfig"), + (component_key(u::COMPONENT, u::SET_CLIENT_METRICS), "SetClientMetrics"), + (component_key(u::COMPONENT, u::SET_CONNECTION_STATE), "SetConnectionState"), + (component_key(u::COMPONENT, u::GET_PSS_CONFIG), "GetPSSConfig"), + (component_key(u::COMPONENT, u::GET_USER_OPTIONS), "GetUserOptions"), + (component_key(u::COMPONENT, u::SET_USER_OPTIONS), "SetUserOptions"), + (component_key(u::COMPONENT, u::SUSPEND_USER_PING), "SuspendUserPing"), + + // Messaging + (component_key(m::COMPONENT, m::FETCH_MESSAGES), "FetchMessages"), + (component_key(m::COMPONENT, m::PURGE_MESSAGES), "PurgeMessages"), + (component_key(m::COMPONENT, m::TOUCH_MESSAGES), "TouchMessages"), + (component_key(m::COMPONENT, m::GET_MESSAGES), "GetMessages"), + + // Association Lists + (component_key(al::COMPONENT, al::ADD_USERS_TO_LIST), "AddUsersToList"), + (component_key(al::COMPONENT, al::REMOVE_USERS_FROM_LIST), "RemoveUsersFromList"), + (component_key(al::COMPONENT, al::CLEAR_LIST), "ClearList"), + (component_key(al::COMPONENT, al::SET_USERS_TO_LIST), "SetUsersToList"), + (component_key(al::COMPONENT, al::GET_LIST_FOR_USER), "GetListForUser"), + (component_key(al::COMPONENT, al::GET_LISTS), "GetLists"), + (component_key(al::COMPONENT, al::SUBSCRIBE_TO_LISTS), "SubscribeToLists"), + (component_key(al::COMPONENT, al::UNSUBSCRIBE_TO_LISTS), "UnsubscribeToLists"), + (component_key(al::COMPONENT, al::GET_CONFIG_LISTS_INFO), "GetConfigListsInfo"), + + // Game Reporting + (component_key(gr::COMPONENT, gr::SUBMIT_GAME_REPORT), "SubmitGameReport"), + (component_key(gr::COMPONENT, gr::SUBMIT_OFFLINE_GAME_REPORT), "SubmitOfflineGameReport"), + (component_key(gr::COMPONENT, gr::SUBMIT_GAME_EVENTS), "SubmitGameEvents"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_QUERY), "GetGameReportQuery"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_QUERIES_LIST), "GetGameReportQueriesList"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORTS), "GetGameReports"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_VIEW), "GetGameReportView"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_VIEW_INFO), "GetGameReportViewInfo"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_VIEW_INFO_LIST), "GetGameReportViewInfoList"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_TYPES), "GetGameReportTypes"), + (component_key(gr::COMPONENT, gr::UPDATE_METRICS), "UpdateMetrics"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_COLUMN_INFO), "GetGameReportColumnInfo"), + (component_key(gr::COMPONENT, gr::GET_GAME_REPORT_COLUMN_VALUES), "GetGameReportColumnValues"), + (component_key(gr::COMPONENT, gr::SUBMIT_TRUSTED_MID_GAME_REPORT), "SubmitTrustedMidGameReport"), + (component_key(gr::COMPONENT, gr::SUBMIT_TRUSTED_END_GAME_REPORT), "SubmitTrustedEndGameReport"), + + // User Sessions + (component_key(us::COMPONENT, us::UPDATE_HARDWARE_FLAGS), "UpdateHardwareFlags"), + (component_key(us::COMPONENT, us::LOOKUP_USER), "LookupUser"), + (component_key(us::COMPONENT, us::LOOKUP_USERS), "LookupUsers"), + (component_key(us::COMPONENT, us::LOOKUP_USERS_BY_PREFIX), "LookupUsersByPrefix"), + (component_key(us::COMPONENT, us::UPDATE_NETWORK_INFO), "UpdateNetworkInfo"), + (component_key(us::COMPONENT, us::LOOKUP_USER_GEO_IP_DATA), "LookupUserGeoIPData"), + (component_key(us::COMPONENT, us::OVERRIDE_USER_GEO_IP_DATA), "OverrideUserGeoIPData"), + (component_key(us::COMPONENT, us::UPDATE_USER_SESSION_CLIENT_DATA), "UpdateUserSessionClientData"), + (component_key(us::COMPONENT, us::SET_USER_INFO_ATTRIBUTE), "SetUserInfoAttribute"), + (component_key(us::COMPONENT, us::RESET_USER_GEO_IP_DATA), "ResetUserGeoIPData"), + (component_key(us::COMPONENT, us::LOOKUP_USER_SESSION_ID), "LookupUserSessionID"), + (component_key(us::COMPONENT, us::FETCH_LAST_LOCALE_USED_AND_AUTH_ERROR), "FetchLastLocaleUsedAndAuthError"), + (component_key(us::COMPONENT, us::FETCH_USER_FIRST_LAST_AUTH_TIME), "FetchUserFirstLastAuthTime"), + (component_key(us::COMPONENT, us::RESUME_SESSION), "ResumeSession"), + ] + .into_iter() + .collect() +} + +#[rustfmt::skip] +fn notifications() -> HashMap { + use game_manager as g; + use messaging as m; + use game_reporting as gr; + use user_sessions as us; + + [ + // Game Manager + (component_key(g::COMPONENT, g::MATCHMAKING_FAILED), "MatchmakingFailed"), + (component_key(g::COMPONENT, g::MATCHMAKING_ASYNC_STATUS), "MatchmakingAsyncStatus"), + (component_key(g::COMPONENT, g::GAME_CREATED), "GameCreated"), + (component_key(g::COMPONENT, g::GAME_REMOVED), "GameRemoved"), + (component_key(g::COMPONENT, g::GAME_SETUP), "GameSetup"), + (component_key(g::COMPONENT, g::PLAYER_JOINING), "PlayerJoining"), + (component_key(g::COMPONENT, g::JOINING_PLAYER_INITIATE_CONNECTIONS), "JoiningPlayerInitiateConnections"), + (component_key(g::COMPONENT, g::PLAYER_JOINING_QUEUE), "PlayerJoiningQueue"), + (component_key(g::COMPONENT, g::PLAYER_PROMOTED_FROM_QUEUE), "PlayerPromotedFromQueue"), + (component_key(g::COMPONENT, g::PLAYER_CLAIMING_RESERVATION), "PlayerClaimingReservation"), + (component_key(g::COMPONENT, g::PLAYER_JOIN_COMPLETED), "PlayerJoinCompleted"), + (component_key(g::COMPONENT, g::PLAYER_REMOVED), "PlayerRemoved"), + (component_key(g::COMPONENT, g::HOST_MIGRATION_FINISHED), "HostMigrationFinished"), + (component_key(g::COMPONENT, g::HOST_MIGRATION_START), "HostMigrationStart"), + (component_key(g::COMPONENT, g::PLATFORM_HOST_INITIALIZED), "PlatformHostInitialized"), + (component_key(g::COMPONENT, g::GAME_ATTRIB_CHANGE), "GameAttribChange"), + (component_key(g::COMPONENT, g::PLAYER_ATTRIB_CHANGE), "PlayerAttribChange"), + (component_key(g::COMPONENT, g::PLAYER_CUSTOM_DATA_CHANGE), "PlayerCustomDataChange"), + (component_key(g::COMPONENT, g::GAME_STATE_CHANGE), "GameStateChange"), + (component_key(g::COMPONENT, g::GAME_SETTINGS_CHANGE), "GameSettingsChange"), + (component_key(g::COMPONENT, g::GAME_CAPACITY_CHANGE), "GameCapacityChange"), + (component_key(g::COMPONENT, g::GAME_RESET), "GameReset"), + (component_key(g::COMPONENT, g::GAME_REPORTING_ID_CHANGE), "GameReportingIDChange"), + (component_key(g::COMPONENT, g::GAME_SESSION_UPDATED), "GameSessionUpdated"), + (component_key(g::COMPONENT, g::GAME_PLAYER_STATE_CHANGE), "GamePlayerStateChange"), + (component_key(g::COMPONENT, g::GAME_PLAYER_TEAM_CHANGE), "GamePlayerTeamChange"), + (component_key(g::COMPONENT, g::GAME_TEAM_ID_CHANGE), "GameTeamIDChange"), + (component_key(g::COMPONENT, g::PROCESS_QUEUE), "PROCESS_QUEUE"), + (component_key(g::COMPONENT, g::PRECENSE_MODE_CHANGED), "PrecenseModeChanged"), + (component_key(g::COMPONENT, g::GAME_PLAYER_QUEUE_POSITION_CHANGE), "GamePlayerQueuePositionChange"), + (component_key(g::COMPONENT, g::GAME_LIST_UPDATE), "GameListUpdate"), + (component_key(g::COMPONENT, g::ADMIN_LIST_CHANGE), "AdminListChange"), + (component_key(g::COMPONENT, g::CREATE_DYNAMIC_DEDICATED_SERVER_GAME), "CreateDynamicDedicatedServerGame"), + (component_key(g::COMPONENT, g::GAME_NAME_CHANGE), "GameNameChange"), + + // Messaging + (component_key(m::COMPONENT, m::SEND_MESSAGE), "SendMessage"), + + // Game Reporting + (component_key(gr::COMPONENT, gr::GAME_REPORT_SUBMITTED), "GameReportSubmitted"), + + // User Sessions + (component_key(us::COMPONENT, us::SET_SESSION), "SetSession"), + (component_key(us::COMPONENT, us::SESSION_DETAILS), "SessionDetails"), + (component_key(us::COMPONENT, us::UPDATE_EXTENDED_DATA_ATTRIBUTE), "UpdateExtendedDataAttribute"), + (component_key(us::COMPONENT, us::FETCH_EXTENDED_DATA), "FetchExtendedData"), + ] + .into_iter() + .collect() } diff --git a/src/utils/models.rs b/src/utils/models.rs index 3f3c6cee..d5071b62 100644 --- a/src/utils/models.rs +++ b/src/utils/models.rs @@ -1,57 +1,49 @@ use crate::utils::types::PlayerID; -use blaze_pk::{ - codec::{Decodable, Encodable}, - error::DecodeResult, - reader::TdfReader, - tag::TdfType, - types::Union, - value_type, - writer::TdfWriter, -}; use serde::{ser::SerializeStruct, Serialize}; -use std::{ - fmt::{Debug, Display}, - net::Ipv4Addr, -}; +use std::{fmt::Debug, net::Ipv4Addr}; +use tdf::{GroupSlice, TdfDeserialize, TdfDeserializeOwned, TdfSerialize, TdfType, TdfTyped}; /// Networking information for an instance. Contains the /// host address and the port -pub struct InstanceNet { +pub struct InstanceAddress { pub host: InstanceHost, pub port: Port, } -impl From<(String, Port)> for InstanceNet { +impl From<(String, Port)> for InstanceAddress { fn from((host, port): (String, Port)) -> Self { let host = InstanceHost::from(host); Self { host, port } } } -impl Encodable for InstanceNet { - fn encode(&self, writer: &mut TdfWriter) { - self.host.encode(writer); - writer.tag_u16(b"PORT", self.port); - writer.tag_group_end(); +impl TdfSerialize for InstanceAddress { + fn serialize(&self, w: &mut S) { + w.group_body(|w| { + self.host.serialize(w); + w.tag_u16(b"PORT", self.port); + }); } } -impl Decodable for InstanceNet { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let host: InstanceHost = InstanceHost::decode(reader)?; - let port: u16 = reader.tag(b"PORT")?; - reader.read_byte()?; +impl TdfDeserializeOwned for InstanceAddress { + fn deserialize_owned(r: &mut tdf::TdfDeserializer<'_>) -> tdf::DecodeResult { + let host: InstanceHost = InstanceHost::deserialize_owned(r)?; + let port: u16 = r.tag(b"PORT")?; + GroupSlice::deserialize_content_skip(r)?; Ok(Self { host, port }) } } -value_type!(InstanceNet, TdfType::Group); +impl TdfTyped for InstanceAddress { + const TYPE: TdfType = TdfType::Group; +} /// Type of instance details provided either hostname /// encoded as string or IP address encoded as NetAddress pub enum InstanceHost { Host(String), - Address(NetAddress), + Address(Ipv4Addr), } /// Attempts to convert the provided value into a instance type. If @@ -60,7 +52,7 @@ pub enum InstanceHost { impl From for InstanceHost { fn from(value: String) -> Self { if let Ok(value) = value.parse::() { - Self::Address(NetAddress(value)) + Self::Address(value) } else { Self::Host(value) } @@ -78,307 +70,144 @@ impl From for String { } } -impl Encodable for InstanceHost { - fn encode(&self, writer: &mut TdfWriter) { +impl TdfSerialize for InstanceHost { + fn serialize(&self, w: &mut S) { match self { - InstanceHost::Host(value) => writer.tag_str(b"HOST", value), - InstanceHost::Address(value) => writer.tag_value(b"IP", value), + InstanceHost::Host(value) => w.tag_str(b"HOST", value), + InstanceHost::Address(value) => w.tag_u32(b"IP", (*value).into()), } } } -impl Decodable for InstanceHost { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let host: Option = reader.try_tag(b"HOST")?; +impl TdfDeserializeOwned for InstanceHost { + fn deserialize_owned(r: &mut tdf::TdfDeserializer<'_>) -> tdf::DecodeResult { + let host: Option = r.try_tag(b"HOST")?; if let Some(host) = host { return Ok(Self::Host(host)); } - let ip: NetAddress = reader.tag(b"IP")?; - Ok(Self::Address(ip)) + let ip: u32 = r.tag(b"IP")?; + Ok(Self::Address(Ipv4Addr::from(ip))) } } /// Details about an instance. This is used for the redirector system /// to both encode for redirections and decode for the retriever system +#[derive(TdfDeserialize)] pub struct InstanceDetails { /// The networking information for the instance + #[tdf(tag = "ADDR")] pub net: InstanceNet, /// Whether the host requires a secure connection (SSLv3) + #[tdf(tag = "SECU")] pub secure: bool, + #[tdf(tag = "XDNS")] + pub xdns: bool, +} + +#[derive(Default, TdfSerialize, TdfDeserialize, TdfTyped)] +pub enum InstanceNet { + #[tdf(key = 0x0, tag = "VALU")] + InstanceAddress(InstanceAddress), + #[tdf(unset)] + Unset, + #[default] + #[tdf(default)] + Default, + // IpAddress = 0x0, + // XboxServer = 0x1, } -impl Encodable for InstanceDetails { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_union_start(b"ADDR", ServerAddressType::IpAddress as u8); - writer.tag_value(b"VALU", &self.net); - - writer.tag_bool(b"SECU", self.secure); - writer.tag_bool(b"XDNS", false); - } -} - -impl Decodable for InstanceDetails { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let net: InstanceNet = match reader.tag::>(b"ADDR")? { - Union::Set { value, .. } => value, - Union::Unset => { - return Err(blaze_pk::error::DecodeError::MissingTag { - tag: b"ADDR".into(), - ty: TdfType::Union, - }) - } - }; - let secure: bool = reader.tag(b"SECU")?; - Ok(InstanceDetails { net, secure }) - } -} - +#[derive(TdfSerialize)] pub struct UpdateExtDataAttr { + #[tdf(tag = "FLGS")] pub flags: u8, + #[tdf(tag = "ID")] pub player_id: PlayerID, } -impl Encodable for UpdateExtDataAttr { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u8(b"FLGS", self.flags); - writer.tag_u32(b"ID", self.player_id); - } -} - -#[derive(Debug, Copy, Clone)] -#[repr(u8)] -pub enum NetworkAddressType { - // XboxClient = 0x0, - // XboxServer = 0x1, - Pair = 0x2, - // IpAddress = 0x3, - // HostnameAddress = 0x4, -} - -#[derive(Debug, Copy, Clone)] -#[repr(u8)] -pub enum ServerAddressType { - IpAddress = 0x0, - // XboxServer = 0x1, -} - /// Structure for storing extended network data -#[derive(Debug, Copy, Clone, Default, Serialize)] +#[derive(Debug, Copy, Clone, Default, Serialize, TdfSerialize, TdfDeserialize, TdfTyped)] +#[tdf(group)] pub struct QosNetworkData { /// Downstream bits per second + #[tdf(tag = "DBPS")] pub dbps: u16, /// Natt type + #[tdf(tag = "NATT")] pub natt: NatType, /// Upstream bits per second + #[tdf(tag = "UBPS")] pub ubps: u16, } // -#[derive(Debug, Copy, Clone, Serialize)] +#[derive(Debug, Default, Copy, Clone, Serialize, TdfDeserialize, TdfSerialize, TdfTyped)] #[repr(u8)] pub enum NatType { Open = 0x0, Moderate = 0x1, Sequential = 0x2, + #[default] Strict = 0x3, + #[tdf(default)] Unknown = 0x4, } -impl NatType { - pub fn from_value(value: u8) -> Self { - match value { - 0x1 => Self::Open, - 0x2 => Self::Moderate, - 0x3 => Self::Sequential, - 0x4 => Self::Strict, - // TODO: Possibly debug log this - _ => Self::Unknown, - } - } -} - -impl Default for NatType { - fn default() -> Self { - Self::Strict - } -} - -impl Encodable for NatType { - #[inline] - fn encode(&self, writer: &mut TdfWriter) { - writer.write_u8((*self) as u8); - } -} - -impl Decodable for NatType { - #[inline] - fn decode(reader: &mut TdfReader) -> DecodeResult { - Ok(Self::from_value(reader.read_u8()?)) - } -} - -value_type!(NatType, TdfType::VarInt); - -impl Encodable for QosNetworkData { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_u16(b"DBPS", self.dbps); - writer.tag_value(b"NATT", &self.natt); - writer.tag_u16(b"UBPS", self.ubps); - writer.tag_group_end(); - } -} - -impl Decodable for QosNetworkData { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let dbps: u16 = reader.tag(b"DBPS")?; - let natt: NatType = reader.tag(b"NATT")?; - let ubps: u16 = reader.tag(b"UBPS")?; - reader.read_byte()?; - Ok(Self { dbps, natt, ubps }) - } +#[derive(Default, Debug, Clone, TdfSerialize, TdfDeserialize, TdfTyped, Serialize)] +#[serde(untagged)] +pub enum NetworkAddress { + #[tdf(key = 0x2, tag = "VALU")] + AddressPair(IpPairAddress), + #[tdf(unset)] + Unset, + #[default] + #[tdf(default)] + Default, + // XboxClient = 0x0, + // XboxServer = 0x1, + // Pair = 0x2, + // IpAddress = 0x3, + // HostnameAddress = 0x4, } -value_type!(QosNetworkData, TdfType::Group); - /// Type alias for ports which are always u16 pub type Port = u16; #[derive(Debug, Default, Clone, Serialize)] pub struct NetData { - pub groups: Option, + pub addr: NetworkAddress, pub qos: QosNetworkData, pub hardware_flags: u16, } -#[derive(Debug, Default, Clone, Serialize)] -pub struct NetGroups { - pub internal: NetGroup, - pub external: NetGroup, -} - -impl Encodable for NetGroups { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_group(b"EXIP"); - self.external.encode(writer); - - writer.tag_group(b"INIP"); - self.internal.encode(writer); - - writer.tag_group_end(); - } +/// Pair of socket addresses +#[derive(Debug, Clone, TdfDeserialize, TdfSerialize, TdfTyped, Serialize)] +#[tdf(group)] +pub struct IpPairAddress { + #[tdf(tag = "EXIP")] + pub external: PairAddress, + #[tdf(tag = "INIP")] + pub internal: PairAddress, } -impl Decodable for NetGroups { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let external: NetGroup = reader.tag(b"EXIP")?; - let internal: NetGroup = reader.tag(b"INIP")?; - reader.read_byte()?; - Ok(Self { external, internal }) - } +#[derive(Debug, Clone, TdfDeserialize, TdfSerialize, TdfTyped)] +#[tdf(group)] +pub struct PairAddress { + #[tdf(tag = "IP", into = u32)] + pub addr: Ipv4Addr, + #[tdf(tag = "PORT")] + pub port: u16, } -value_type!(NetGroups, TdfType::Group); - -impl NetData { - pub fn tag_groups(&self, tag: &[u8], writer: &mut TdfWriter) { - if let Some(groups) = &self.groups { - writer.tag_union_value(tag, NetworkAddressType::Pair as u8, b"VALU", groups); - } else { - writer.tag_union_unset(tag); - } - } -} - -/// Structure for a networking group which consists of a -/// networking address and port value -#[derive(Debug, Clone, Default, Eq, PartialEq)] -pub struct NetGroup(pub NetAddress, pub Port); - -impl Encodable for NetGroup { - fn encode(&self, writer: &mut TdfWriter) { - writer.tag_value(b"IP", &self.0); - writer.tag_u16(b"PORT", self.1); - writer.tag_group_end(); - } -} - -impl Decodable for NetGroup { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let ip: NetAddress = reader.tag(b"IP")?; - let port: u16 = reader.tag(b"PORT")?; - reader.read_byte()?; - Ok(Self(ip, port)) - } -} - -value_type!(NetGroup, TdfType::Group); - -impl Serialize for NetGroup { +impl Serialize for PairAddress { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let mut s = serializer.serialize_struct("NetGroup", 2)?; - s.serialize_field("address", &self.0)?; - s.serialize_field("port", &self.1)?; + // TODO: Dashboard likely incompatible now due to serialize change + let mut s = serializer.serialize_struct("PairAddress", 2)?; + s.serialize_field("address", &self.addr)?; + s.serialize_field("port", &self.port)?; s.end() } } - -/// Structure for wrapping a Blaze networking address -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct NetAddress(pub Ipv4Addr); - -impl Default for NetAddress { - fn default() -> Self { - Self(Ipv4Addr::LOCALHOST) - } -} - -impl Encodable for NetAddress { - fn encode(&self, writer: &mut TdfWriter) { - let bytes = self.0.octets(); - let value = u32::from_be_bytes(bytes); - writer.write_u32(value); - } -} - -impl Decodable for NetAddress { - fn decode(reader: &mut TdfReader) -> DecodeResult { - let value = reader.read_u32()?; - let bytes = value.to_be_bytes(); - let addr = Ipv4Addr::from(bytes); - Ok(Self(addr)) - } -} - -value_type!(NetAddress, TdfType::VarInt); - -/// Debug trait implementation sample implementation as the Display -/// implementation so that is just called instead -impl Debug for NetAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(self, f) - } -} - -/// Display trait implementation for NetAddress. If the value is valid -/// the value is translated into the IPv4 representation -impl Display for NetAddress { - /// Converts the value stored in this NetAddress to an IPv4 string - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// Serialization implementation for NetAddress which uses the IPv4 -/// representation implemented in Display -impl Serialize for NetAddress { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let value = self.to_string(); - serializer.serialize_str(&value) - } -}