mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
Server functions and proxy example (#59)
* A couple useful things for servers * Add proxy example * Use Uuid's serde feature * Add const options to proxy example * Example crates go in dev-dependencies * Warn instead of error * Log address on login * Requested changes * add a test for deserializing game profile + random small changes Co-authored-by: mat <github@matdoes.dev>
This commit is contained in:
parent
1059afa6fc
commit
9ee5e71bb1
12 changed files with 656 additions and 42 deletions
172
Cargo.lock
generated
172
Cargo.lock
generated
|
@ -97,7 +97,7 @@ version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.1.19",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
@ -121,7 +121,7 @@ dependencies = [
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
"azalea-protocol",
|
"azalea-protocol",
|
||||||
"azalea-world",
|
"azalea-world",
|
||||||
"env_logger",
|
"env_logger 0.10.0",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
|
@ -140,7 +140,7 @@ dependencies = [
|
||||||
"azalea-buf",
|
"azalea-buf",
|
||||||
"azalea-crypto",
|
"azalea-crypto",
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger 0.9.3",
|
||||||
"log",
|
"log",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -300,6 +300,7 @@ dependencies = [
|
||||||
name = "azalea-protocol"
|
name = "azalea-protocol"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"azalea-auth",
|
"azalea-auth",
|
||||||
|
@ -319,11 +320,14 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
"trust-dns-resolver",
|
"trust-dns-resolver",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -415,7 +419,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"azalea",
|
"azalea",
|
||||||
"azalea-protocol",
|
"azalea-protocol",
|
||||||
"env_logger",
|
"env_logger 0.9.3",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rand",
|
"rand",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -711,6 +715,40 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"is-terminal",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||||
|
dependencies = [
|
||||||
|
"errno-dragonfly",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno-dragonfly"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -928,6 +966,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -1054,12 +1101,34 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
|
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.2.6",
|
||||||
|
"io-lifetimes",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -1108,6 +1177,12 @@ version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -1217,6 +1292,16 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1310,7 +1395,7 @@ version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.1.19",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1325,9 +1410,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.16.0"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
|
@ -1380,6 +1465,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -1650,6 +1741,20 @@ version = "0.1.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -1768,6 +1873,15 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1888,6 +2002,15 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinytemplate"
|
name = "tinytemplate"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -2004,6 +2127,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
||||||
|
dependencies = [
|
||||||
|
"nu-ansi-term",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2110,6 +2259,15 @@ name = "uuid"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
|
|
|
@ -9,8 +9,8 @@ version = "0.5.0"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
azalea-buf = {path = "../azalea-buf", version = "^0.5.0" }
|
azalea-buf = {path = "../azalea-buf", version = "^0.5.0"}
|
||||||
azalea-crypto = {path = "../azalea-crypto", version = "^0.5.0" }
|
azalea-crypto = {path = "../azalea-crypto", version = "^0.5.0"}
|
||||||
chrono = {version = "0.4.22", default-features = false}
|
chrono = {version = "0.4.22", default-features = false}
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
num-bigint = "0.4.3"
|
num-bigint = "0.4.3"
|
||||||
|
@ -19,8 +19,8 @@ serde = {version = "1.0.145", features = ["derive"]}
|
||||||
serde_json = "1.0.86"
|
serde_json = "1.0.86"
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.37"
|
||||||
tokio = {version = "1.23.1", features = ["fs"]}
|
tokio = {version = "1.23.1", features = ["fs"]}
|
||||||
uuid = "^1.1.2"
|
uuid = {version = "^1.1.2", features = ["serde"]}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.9.1"
|
env_logger = "0.9.3"
|
||||||
tokio = {version = "1.23.1", features = ["full"]}
|
tokio = {version = "1.23.1", features = ["full"]}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::{
|
||||||
time::{Instant, SystemTime, UNIX_EPOCH},
|
time::{Instant, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AuthOpts {
|
pub struct AuthOpts {
|
||||||
|
@ -209,8 +210,7 @@ pub struct GameOwnershipItem {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct ProfileResponse {
|
pub struct ProfileResponse {
|
||||||
// todo: make the id a uuid
|
pub id: Uuid,
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub skins: Vec<serde_json::Value>,
|
pub skins: Vec<serde_json::Value>,
|
||||||
pub capes: Vec<serde_json::Value>,
|
pub capes: Vec<serde_json::Value>,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(McBuf, Debug, Clone, Default)]
|
#[derive(McBuf, Debug, Clone, Default, Eq, PartialEq)]
|
||||||
pub struct GameProfile {
|
pub struct GameProfile {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -19,8 +20,101 @@ impl GameProfile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(McBuf, Debug, Clone)]
|
impl From<SerializableGameProfile> for GameProfile {
|
||||||
|
fn from(value: SerializableGameProfile) -> Self {
|
||||||
|
let mut properties = HashMap::new();
|
||||||
|
for value in value.properties {
|
||||||
|
properties.insert(
|
||||||
|
value.name,
|
||||||
|
ProfilePropertyValue {
|
||||||
|
value: value.value,
|
||||||
|
signature: value.signature,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
uuid: value.id,
|
||||||
|
name: value.name,
|
||||||
|
properties,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(McBuf, Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct ProfilePropertyValue {
|
pub struct ProfilePropertyValue {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub signature: Option<String>,
|
pub signature: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SerializableGameProfile {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub properties: Vec<SerializableProfilePropertyValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GameProfile> for SerializableGameProfile {
|
||||||
|
fn from(value: GameProfile) -> Self {
|
||||||
|
let mut properties = Vec::new();
|
||||||
|
for (key, value) in value.properties {
|
||||||
|
properties.push(SerializableProfilePropertyValue {
|
||||||
|
name: key,
|
||||||
|
value: value.value,
|
||||||
|
signature: value.signature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
id: value.uuid,
|
||||||
|
name: value.name,
|
||||||
|
properties,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SerializableProfilePropertyValue {
|
||||||
|
pub name: String,
|
||||||
|
pub value: String,
|
||||||
|
pub signature: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_game_profile() {
|
||||||
|
let json = r#"{
|
||||||
|
"id": "f1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
|
||||||
|
"name": "Notch",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "qwer",
|
||||||
|
"value": "asdf",
|
||||||
|
"signature": "zxcv"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"#;
|
||||||
|
let profile = GameProfile::from(
|
||||||
|
serde_json::from_str::<SerializableProfilePropertyValue>(json).unwrap(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
profile,
|
||||||
|
GameProfile {
|
||||||
|
uuid: Uuid::parse_str("f1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6").unwrap(),
|
||||||
|
name: "Notch".to_string(),
|
||||||
|
properties: {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(
|
||||||
|
"asdf".to_string(),
|
||||||
|
ProfilePropertyValue {
|
||||||
|
value: "qwer".to_string(),
|
||||||
|
signature: Some("zxcv".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
//! Tell Mojang you're joining a multiplayer server.
|
//! Tell Mojang you're joining a multiplayer server.
|
||||||
|
use log::debug;
|
||||||
|
use reqwest::StatusCode;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::game_profile::{GameProfile, SerializableGameProfile};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum SessionServerError {
|
pub enum ClientSessionServerError {
|
||||||
#[error("Error sending HTTP request to sessionserver: {0}")]
|
#[error("Error sending HTTP request to sessionserver: {0}")]
|
||||||
HttpError(#[from] reqwest::Error),
|
HttpError(#[from] reqwest::Error),
|
||||||
#[error("Multiplayer is not enabled for this account")]
|
#[error("Multiplayer is not enabled for this account")]
|
||||||
|
@ -24,6 +28,18 @@ pub enum SessionServerError {
|
||||||
UnexpectedResponse { status_code: u16, body: String },
|
UnexpectedResponse { status_code: u16, body: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ServerSessionServerError {
|
||||||
|
#[error("Error sending HTTP request to sessionserver: {0}")]
|
||||||
|
HttpError(#[from] reqwest::Error),
|
||||||
|
#[error("Invalid or expired session")]
|
||||||
|
InvalidSession,
|
||||||
|
#[error("Unexpected response from sessionserver (status code {status_code}): {body}")]
|
||||||
|
UnexpectedResponse { status_code: u16, body: String },
|
||||||
|
#[error("Unknown sessionserver error: {0}")]
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ForbiddenError {
|
pub struct ForbiddenError {
|
||||||
pub error: String,
|
pub error: String,
|
||||||
|
@ -39,7 +55,7 @@ pub async fn join(
|
||||||
private_key: &[u8],
|
private_key: &[u8],
|
||||||
uuid: &Uuid,
|
uuid: &Uuid,
|
||||||
server_id: &str,
|
server_id: &str,
|
||||||
) -> Result<(), SessionServerError> {
|
) -> Result<(), ClientSessionServerError> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
let server_hash = azalea_crypto::hex_digest(&azalea_crypto::digest_data(
|
let server_hash = azalea_crypto::hex_digest(&azalea_crypto::digest_data(
|
||||||
|
@ -63,28 +79,82 @@ pub async fn join(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
match res.status() {
|
match res.status() {
|
||||||
reqwest::StatusCode::NO_CONTENT => Ok(()),
|
StatusCode::NO_CONTENT => Ok(()),
|
||||||
reqwest::StatusCode::FORBIDDEN => {
|
StatusCode::FORBIDDEN => {
|
||||||
let forbidden = res.json::<ForbiddenError>().await?;
|
let forbidden = res.json::<ForbiddenError>().await?;
|
||||||
match forbidden.error.as_str() {
|
match forbidden.error.as_str() {
|
||||||
"InsufficientPrivilegesException" => Err(SessionServerError::MultiplayerDisabled),
|
"InsufficientPrivilegesException" => {
|
||||||
"UserBannedException" => Err(SessionServerError::Banned),
|
Err(ClientSessionServerError::MultiplayerDisabled)
|
||||||
"AuthenticationUnavailableException" => {
|
|
||||||
Err(SessionServerError::AuthServersUnreachable)
|
|
||||||
}
|
}
|
||||||
"InvalidCredentialsException" => Err(SessionServerError::InvalidSession),
|
"UserBannedException" => Err(ClientSessionServerError::Banned),
|
||||||
"ForbiddenOperationException" => Err(SessionServerError::ForbiddenOperation),
|
"AuthenticationUnavailableException" => {
|
||||||
_ => Err(SessionServerError::Unknown(forbidden.error)),
|
Err(ClientSessionServerError::AuthServersUnreachable)
|
||||||
|
}
|
||||||
|
"InvalidCredentialsException" => Err(ClientSessionServerError::InvalidSession),
|
||||||
|
"ForbiddenOperationException" => Err(ClientSessionServerError::ForbiddenOperation),
|
||||||
|
_ => Err(ClientSessionServerError::Unknown(forbidden.error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status_code => {
|
status_code => {
|
||||||
// log the headers
|
// log the headers
|
||||||
log::debug!("Error headers: {:#?}", res.headers());
|
debug!("Error headers: {:#?}", res.headers());
|
||||||
let body = res.text().await?;
|
let body = res.text().await?;
|
||||||
Err(SessionServerError::UnexpectedResponse {
|
Err(ClientSessionServerError::UnexpectedResponse {
|
||||||
status_code: status_code.as_u16(),
|
status_code: status_code.as_u16(),
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ask Mojang's servers if the player joining is authenticated.
|
||||||
|
/// Included in the reply is the player's skin and cape.
|
||||||
|
/// The IP field is optional and equivalent to enabling
|
||||||
|
/// 'prevent-proxy-connections' in server.properties
|
||||||
|
pub async fn serverside_auth(
|
||||||
|
username: &str,
|
||||||
|
public_key: &[u8],
|
||||||
|
private_key: &[u8; 16],
|
||||||
|
ip: Option<&str>,
|
||||||
|
) -> Result<GameProfile, ServerSessionServerError> {
|
||||||
|
let hash = azalea_crypto::hex_digest(&azalea_crypto::digest_data(
|
||||||
|
"".as_bytes(),
|
||||||
|
public_key,
|
||||||
|
private_key,
|
||||||
|
));
|
||||||
|
|
||||||
|
let url = reqwest::Url::parse_with_params(
|
||||||
|
"https://sessionserver.mojang.com/session/minecraft/hasJoined",
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
vec![("username", username), ("serverId", &hash), ("ip", ip)]
|
||||||
|
} else {
|
||||||
|
vec![("username", username), ("serverId", &hash)]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("URL should always be valid");
|
||||||
|
|
||||||
|
let res = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
StatusCode::OK => {}
|
||||||
|
StatusCode::NO_CONTENT => {
|
||||||
|
return Err(ServerSessionServerError::InvalidSession);
|
||||||
|
}
|
||||||
|
StatusCode::FORBIDDEN => {
|
||||||
|
return Err(ServerSessionServerError::Unknown(
|
||||||
|
res.json::<ForbiddenError>().await?.error,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
status_code => {
|
||||||
|
// log the headers
|
||||||
|
debug!("Error headers: {:#?}", res.headers());
|
||||||
|
let body = res.text().await?;
|
||||||
|
return Err(ServerSessionServerError::UnexpectedResponse {
|
||||||
|
status_code: status_code.as_u16(),
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res.json::<SerializableGameProfile>().await?.into())
|
||||||
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct Account {
|
||||||
/// This is an Arc<Mutex> so it can be modified by [`Self::refresh`].
|
/// This is an Arc<Mutex> so it can be modified by [`Self::refresh`].
|
||||||
pub access_token: Option<Arc<Mutex<String>>>,
|
pub access_token: Option<Arc<Mutex<String>>>,
|
||||||
/// Only required for online-mode accounts.
|
/// Only required for online-mode accounts.
|
||||||
pub uuid: Option<uuid::Uuid>,
|
pub uuid: Option<Uuid>,
|
||||||
|
|
||||||
/// The parameters (i.e. email) that were passed for creating this
|
/// The parameters (i.e. email) that were passed for creating this
|
||||||
/// [`Account`]. This is used to for automatic reauthentication when we get
|
/// [`Account`]. This is used to for automatic reauthentication when we get
|
||||||
|
@ -85,7 +85,7 @@ impl Account {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
username: auth_result.profile.name,
|
username: auth_result.profile.name,
|
||||||
access_token: Some(Arc::new(Mutex::new(auth_result.access_token))),
|
access_token: Some(Arc::new(Mutex::new(auth_result.access_token))),
|
||||||
uuid: Some(Uuid::parse_str(&auth_result.profile.id).expect("Invalid UUID")),
|
uuid: Some(auth_result.profile.id),
|
||||||
auth_opts: AuthOpts::Microsoft {
|
auth_opts: AuthOpts::Microsoft {
|
||||||
email: email.to_string(),
|
email: email.to_string(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -77,8 +77,8 @@ impl ChatPacket {
|
||||||
/// player-sent chat message, this will be None.
|
/// player-sent chat message, this will be None.
|
||||||
pub fn uuid(&self) -> Option<Uuid> {
|
pub fn uuid(&self) -> Option<Uuid> {
|
||||||
match self {
|
match self {
|
||||||
ChatPacket::System(_) => return None,
|
ChatPacket::System(_) => None,
|
||||||
ChatPacket::Player(m) => return Some(m.sender),
|
ChatPacket::Player(m) => Some(m.sender),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub use crate::chat::ChatPacket;
|
pub use crate::chat::ChatPacket;
|
||||||
use crate::{movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo};
|
use crate::{movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo};
|
||||||
use azalea_auth::{game_profile::GameProfile, sessionserver::SessionServerError};
|
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
|
||||||
use azalea_chat::Component;
|
use azalea_chat::Component;
|
||||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
|
@ -141,7 +141,7 @@ pub enum JoinError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
SessionServer(#[from] azalea_auth::sessionserver::SessionServerError),
|
SessionServer(#[from] azalea_auth::sessionserver::ClientSessionServerError),
|
||||||
#[error("The given address could not be parsed into a ServerAddress")]
|
#[error("The given address could not be parsed into a ServerAddress")]
|
||||||
InvalidAddress,
|
InvalidAddress,
|
||||||
#[error("Couldn't refresh access token: {0}")]
|
#[error("Couldn't refresh access token: {0}")]
|
||||||
|
@ -315,8 +315,8 @@ impl Client {
|
||||||
}
|
}
|
||||||
if matches!(
|
if matches!(
|
||||||
e,
|
e,
|
||||||
SessionServerError::InvalidSession
|
ClientSessionServerError::InvalidSession
|
||||||
| SessionServerError::ForbiddenOperation
|
| ClientSessionServerError::ForbiddenOperation
|
||||||
) {
|
) {
|
||||||
// uh oh, we got an invalid session and have
|
// uh oh, we got an invalid session and have
|
||||||
// to reauthenticate now
|
// to reauthenticate now
|
||||||
|
|
|
@ -40,3 +40,9 @@ uuid = "1.1.2"
|
||||||
connecting = []
|
connecting = []
|
||||||
default = ["packets"]
|
default = ["packets"]
|
||||||
packets = ["connecting", "dep:async-compression", "dep:azalea-core"]
|
packets = ["connecting", "dep:async-compression", "dep:azalea-core"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
anyhow = "^1.0.65"
|
||||||
|
tracing = "^0.1.36"
|
||||||
|
tracing-subscriber = "^0.3.15"
|
||||||
|
once_cell = "1.17.0"
|
225
azalea-protocol/examples/handshake_proxy.rs
Normal file
225
azalea-protocol/examples/handshake_proxy.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
//! A "simple" server that gets login information and proxies connections.
|
||||||
|
//! After login all connections are encrypted and Azalea cannot read them.
|
||||||
|
|
||||||
|
use azalea_protocol::{
|
||||||
|
connect::Connection,
|
||||||
|
packets::{
|
||||||
|
handshake::{
|
||||||
|
client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
|
||||||
|
ServerboundHandshakePacket,
|
||||||
|
},
|
||||||
|
login::{serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket},
|
||||||
|
status::{
|
||||||
|
clientbound_pong_response_packet::ClientboundPongResponsePacket,
|
||||||
|
clientbound_status_response_packet::{
|
||||||
|
ClientboundStatusResponsePacket, Players, Version,
|
||||||
|
},
|
||||||
|
ServerboundStatusPacket,
|
||||||
|
},
|
||||||
|
ConnectionProtocol, PROTOCOL_VERSION,
|
||||||
|
},
|
||||||
|
read::ReadPacketError,
|
||||||
|
};
|
||||||
|
use futures::FutureExt;
|
||||||
|
use log::{error, info, warn};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::error::Error;
|
||||||
|
use tokio::{
|
||||||
|
io::{self, AsyncWriteExt},
|
||||||
|
net::{TcpListener, TcpStream},
|
||||||
|
};
|
||||||
|
use tracing::Level;
|
||||||
|
|
||||||
|
const LISTEN_ADDR: &str = "127.0.0.1:25566";
|
||||||
|
const PROXY_ADDR: &str = "173.205.80.60:25565";
|
||||||
|
|
||||||
|
const PROXY_DESC: &str = "An Azalea Minecraft Proxy";
|
||||||
|
|
||||||
|
// String must be formatted like "data:image/png;base64,<data>"
|
||||||
|
static PROXY_FAVICON: Lazy<Option<String>> = Lazy::new(|| None);
|
||||||
|
|
||||||
|
static PROXY_VERSION: Lazy<Version> = Lazy::new(|| Version {
|
||||||
|
name: "1.19.3".to_string(),
|
||||||
|
protocol: PROTOCOL_VERSION as i32,
|
||||||
|
});
|
||||||
|
|
||||||
|
const PROXY_PLAYERS: Players = Players {
|
||||||
|
max: 1,
|
||||||
|
online: 0,
|
||||||
|
sample: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROXY_PREVIEWS_CHAT: Option<bool> = Some(false);
|
||||||
|
const PROXY_SECURE_CHAT: Option<bool> = Some(false);
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
|
||||||
|
|
||||||
|
// Bind to an address and port
|
||||||
|
let listener = TcpListener::bind(LISTEN_ADDR).await?;
|
||||||
|
loop {
|
||||||
|
// When a connection is made, pass it off to another thread
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
tokio::spawn(handle_connection(stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_connection(stream: TcpStream) -> anyhow::Result<()> {
|
||||||
|
stream.set_nodelay(true)?;
|
||||||
|
let ip = stream.peer_addr()?;
|
||||||
|
let mut conn: Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> =
|
||||||
|
Connection::wrap(stream);
|
||||||
|
|
||||||
|
// The first packet sent from a client is the intent packet.
|
||||||
|
// This specifies whether the client is pinging
|
||||||
|
// the server or is going to join the game.
|
||||||
|
let intent = match conn.read().await {
|
||||||
|
Ok(packet) => match packet {
|
||||||
|
ServerboundHandshakePacket::ClientIntention(packet) => {
|
||||||
|
info!(
|
||||||
|
"New connection: {0}, Version {1}, {2:?}",
|
||||||
|
ip.ip(),
|
||||||
|
packet.protocol_version,
|
||||||
|
packet.intention
|
||||||
|
);
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let e = e.into();
|
||||||
|
warn!("Error during intent: {e}");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match intent.intention {
|
||||||
|
// If the client is pinging the proxy,
|
||||||
|
// reply with the information below.
|
||||||
|
ConnectionProtocol::Status => {
|
||||||
|
let mut conn = conn.status();
|
||||||
|
loop {
|
||||||
|
match conn.read().await {
|
||||||
|
Ok(p) => match p {
|
||||||
|
ServerboundStatusPacket::StatusRequest(_) => {
|
||||||
|
conn.write(
|
||||||
|
ClientboundStatusResponsePacket {
|
||||||
|
description: PROXY_DESC.into(),
|
||||||
|
favicon: PROXY_FAVICON.clone(),
|
||||||
|
players: PROXY_PLAYERS.clone(),
|
||||||
|
version: PROXY_VERSION.clone(),
|
||||||
|
previews_chat: PROXY_PREVIEWS_CHAT,
|
||||||
|
enforces_secure_chat: PROXY_SECURE_CHAT,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ServerboundStatusPacket::PingRequest(p) => {
|
||||||
|
conn.write(ClientboundPongResponsePacket { time: p.time }.get())
|
||||||
|
.await?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => match *e {
|
||||||
|
ReadPacketError::ConnectionClosed => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
warn!("Error during status: {e}");
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the client intends to join the proxy,
|
||||||
|
// wait for them to send the `Hello` packet to
|
||||||
|
// log their username and uuid, then forward the
|
||||||
|
// connection along to the proxy target.
|
||||||
|
ConnectionProtocol::Login => {
|
||||||
|
let mut conn = conn.login();
|
||||||
|
loop {
|
||||||
|
match conn.read().await {
|
||||||
|
Ok(p) => {
|
||||||
|
if let ServerboundLoginPacket::Hello(hello) = p {
|
||||||
|
info!(
|
||||||
|
"Player \'{0}\' from {1} logging in with uuid: {2}",
|
||||||
|
hello.name,
|
||||||
|
ip.ip(),
|
||||||
|
if let Some(id) = hello.profile_id {
|
||||||
|
id.to_string()
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
tokio::spawn(transfer(conn.unwrap()?, intent, hello).map(|r| {
|
||||||
|
if let Err(e) = r {
|
||||||
|
error!("Failed to proxy: {e}");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match *e {
|
||||||
|
ReadPacketError::ConnectionClosed => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
warn!("Error during login: {e}");
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Client provided weird intent: {:?}", intent.intention);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transfer(
|
||||||
|
mut inbound: TcpStream,
|
||||||
|
intent: ClientIntentionPacket,
|
||||||
|
hello: ServerboundHelloPacket,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let outbound = TcpStream::connect(PROXY_ADDR).await?;
|
||||||
|
let name = hello.name.clone();
|
||||||
|
outbound.set_nodelay(true)?;
|
||||||
|
|
||||||
|
// Repeat the intent and hello packet
|
||||||
|
// recieved earlier to the proxy target
|
||||||
|
let mut outbound_conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> =
|
||||||
|
Connection::wrap(outbound);
|
||||||
|
outbound_conn.write(intent.get()).await?;
|
||||||
|
|
||||||
|
let mut outbound_conn = outbound_conn.login();
|
||||||
|
outbound_conn.write(hello.get()).await?;
|
||||||
|
|
||||||
|
let mut outbound = outbound_conn.unwrap()?;
|
||||||
|
|
||||||
|
// Split the incoming and outgoing connections in
|
||||||
|
// halves and handle each pair on separate threads.
|
||||||
|
let (mut ri, mut wi) = inbound.split();
|
||||||
|
let (mut ro, mut wo) = outbound.split();
|
||||||
|
|
||||||
|
let client_to_server = async {
|
||||||
|
io::copy(&mut ri, &mut wo).await?;
|
||||||
|
wo.shutdown().await
|
||||||
|
};
|
||||||
|
|
||||||
|
let server_to_client = async {
|
||||||
|
io::copy(&mut ro, &mut wi).await?;
|
||||||
|
wi.shutdown().await
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::try_join!(client_to_server, server_to_client)?;
|
||||||
|
info!("Player \'{name}\' left the game");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -8,7 +8,8 @@ use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
|
||||||
use crate::packets::ProtocolPacket;
|
use crate::packets::ProtocolPacket;
|
||||||
use crate::read::{read_packet, ReadPacketError};
|
use crate::read::{read_packet, ReadPacketError};
|
||||||
use crate::write::write_packet;
|
use crate::write::write_packet;
|
||||||
use azalea_auth::sessionserver::SessionServerError;
|
use azalea_auth::game_profile::GameProfile;
|
||||||
|
use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerError};
|
||||||
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
|
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
@ -17,7 +18,7 @@ use std::marker::PhantomData;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -327,7 +328,7 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
|
||||||
uuid: &Uuid,
|
uuid: &Uuid,
|
||||||
private_key: [u8; 16],
|
private_key: [u8; 16],
|
||||||
packet: &ClientboundHelloPacket,
|
packet: &ClientboundHelloPacket,
|
||||||
) -> Result<(), SessionServerError> {
|
) -> Result<(), ClientSessionServerError> {
|
||||||
azalea_auth::sessionserver::join(
|
azalea_auth::sessionserver::join(
|
||||||
access_token,
|
access_token,
|
||||||
&packet.public_key,
|
&packet.public_key,
|
||||||
|
@ -339,6 +340,63 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> {
|
||||||
|
/// Change our state from handshake to login. This is the state that is used
|
||||||
|
/// for logging in.
|
||||||
|
pub fn login(self) -> Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
|
||||||
|
Connection::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change our state from handshake to status. This is the state that is
|
||||||
|
/// used for pinging the server.
|
||||||
|
pub fn status(self) -> Connection<ServerboundStatusPacket, ClientboundStatusPacket> {
|
||||||
|
Connection::from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
|
||||||
|
/// Set our compression threshold, i.e. the maximum size that a packet is
|
||||||
|
/// allowed to be without getting compressed. If you set it to less than 0
|
||||||
|
/// then compression gets disabled.
|
||||||
|
pub fn set_compression_threshold(&mut self, threshold: i32) {
|
||||||
|
// if you pass a threshold of less than 0, compression is disabled
|
||||||
|
if threshold >= 0 {
|
||||||
|
self.reader.compression_threshold = Some(threshold as u32);
|
||||||
|
self.writer.compression_threshold = Some(threshold as u32);
|
||||||
|
} else {
|
||||||
|
self.reader.compression_threshold = None;
|
||||||
|
self.writer.compression_threshold = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the encryption key that is used to encrypt and decrypt packets. It's
|
||||||
|
/// the same for both reading and writing.
|
||||||
|
pub fn set_encryption_key(&mut self, key: [u8; 16]) {
|
||||||
|
let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key);
|
||||||
|
self.reader.dec_cipher = Some(dec_cipher);
|
||||||
|
self.writer.enc_cipher = Some(enc_cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change our state from login to game. This is the state that's used when
|
||||||
|
/// the client is actually in the game.
|
||||||
|
pub fn game(self) -> Connection<ServerboundGamePacket, ClientboundGamePacket> {
|
||||||
|
Connection::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify connecting clients have authenticated with Minecraft's servers.
|
||||||
|
/// This must happen after the client sends a `ServerboundLoginPacket::Key`
|
||||||
|
/// packet.
|
||||||
|
pub async fn authenticate(
|
||||||
|
&self,
|
||||||
|
username: &str,
|
||||||
|
public_key: &[u8],
|
||||||
|
private_key: &[u8; 16],
|
||||||
|
ip: Option<&str>,
|
||||||
|
) -> Result<GameProfile, ServerSessionServerError> {
|
||||||
|
azalea_auth::sessionserver::serverside_auth(username, public_key, private_key, ip).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// rust doesn't let us implement From because allegedly it conflicts with
|
// rust doesn't let us implement From because allegedly it conflicts with
|
||||||
// `core`'s "impl<T> From<T> for T" so we do this instead
|
// `core`'s "impl<T> From<T> for T" so we do this instead
|
||||||
impl<R1, W1> Connection<R1, W1>
|
impl<R1, W1> Connection<R1, W1>
|
||||||
|
@ -390,4 +448,9 @@ where
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert from a `Connection` into a `TcpStream`. Useful for servers.
|
||||||
|
pub fn unwrap(self) -> Result<TcpStream, ReuniteError> {
|
||||||
|
self.reader.read_stream.reunite(self.writer.write_stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,4 @@ tokio = "^1.23.1"
|
||||||
uuid = "1.2.2"
|
uuid = "1.2.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "^1.0.65"
|
env_logger = "^0.10.0"
|
||||||
env_logger = "^0.9.1"
|
|
||||||
tokio = "^1.23.1"
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue