mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add minimum matchmaking lobby server
This commit is contained in:
28
Cargo.lock
generated
28
Cargo.lock
generated
@@ -2614,6 +2614,34 @@ dependencies = [
|
||||
"sea-orm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oj_rc_lobby"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"oj_polariton_auth",
|
||||
"polariton",
|
||||
"polariton_server",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oj_rc_lobby_room"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"oj_polariton_auth",
|
||||
"oj_rc_core",
|
||||
"polariton",
|
||||
"polariton_server",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oj_rc_microtransactions"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -20,6 +20,7 @@ members = [
|
||||
"rc_social", "rc_social_room",
|
||||
"rc_chat", "rc_chat_room",
|
||||
"rc_singleplayer", "rc_singleplayer_room",
|
||||
"rc_lobby", "rc_lobby_room",
|
||||
"rc_core", "rc_database", "rc_factory",
|
||||
]
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<setting name="ChatServerAddress__note__forwardedTo">127.0.0.1:4537</setting>
|
||||
<setting name="SinglePlayerServerAddress">127.0.0.1:4538</setting>
|
||||
<setting name="SinglePlayerServerAddress__note__forwardedTo">127.0.0.1:4539</setting>
|
||||
<setting name="LobbyServerAddress">127.0.0.1:4540</setting>
|
||||
<setting name="LobbyServerAddress__note__forwardedTo">127.0.0.1:4541</setting>
|
||||
<setting name="authUrl">http://127.0.0.1:8001/</setting>
|
||||
<setting name="S3URL">http://127.0.0.1:8010/live/data.json</setting>
|
||||
</dev>
|
||||
@@ -23,6 +25,8 @@
|
||||
<setting name="ChatServerAddress__note__forwardedTo">:4537</setting>
|
||||
<setting name="SinglePlayerServerAddress">sp.services.rc.ngram.ca:4538</setting>
|
||||
<setting name="SinglePlayerServerAddress__note__forwardedTo">:4539</setting>
|
||||
<setting name="LobbyServerAddress">!!!TODO!!!:4540</setting>
|
||||
<setting name="LobbyServerAddress__note__forwardedTo">:4541</setting>
|
||||
<setting name="authUrl">https://live-auth.rc.ngram.ca/</setting>
|
||||
<setting name="S3URL">https://static.rc.ngram.ca/live/data.json</setting>
|
||||
<setting name="Payment__note__notUsedHere">https://pay.rc.ngram.ca/</setting>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Registration - Openjam</title>
|
||||
<meta name="title" content="Registration - Openjam" />
|
||||
@@ -87,8 +89,9 @@
|
||||
border-color: red;
|
||||
}
|
||||
input:required:invalid.form-input-box::after {
|
||||
content: "*";
|
||||
content: "*required";
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
@@ -97,6 +100,11 @@
|
||||
background-color: lightgrey;
|
||||
font-size: 20px;
|
||||
}
|
||||
.input-required-hint::after {
|
||||
color: red;
|
||||
/*content: "*required";*/
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-disclaimer {
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
@@ -138,6 +146,7 @@
|
||||
<label for="display_name">Username</label>
|
||||
</div>
|
||||
<input class="form-input-box" type="text" id="display_name" name="display_name" value="{{form.display_name}}" placeholder="username" required/>
|
||||
<span class="input-required-hint"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
@@ -145,6 +154,7 @@
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
<input class="form-input-box" type="password" id="password" name="password" value="{{form.password}}" placeholder="password" required/>
|
||||
<span class="input-required-hint"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
@@ -152,6 +162,7 @@
|
||||
<label for="password_c">Password (again)</label>
|
||||
</div>
|
||||
<input class="form-input-box" type="password" id="password_c" name="password_c" value="{{form.password_c}}" placeholder="password (again)" required/>
|
||||
<span class="input-required-hint"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
|
||||
@@ -14,6 +14,7 @@ pub mod crf;
|
||||
pub mod channel;
|
||||
pub mod sanction;
|
||||
pub mod robot_data;
|
||||
pub mod lobby;
|
||||
|
||||
pub mod error_codes;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ fn default_gameplay_settings() -> super::GameplaySettings {
|
||||
shield_hps: 2_000,
|
||||
request_review_level: 10_000,
|
||||
critical_ratio: 5.0,
|
||||
cross_promo_image: "https://git.ngram.ca/assets/img/logo.png".to_owned(),
|
||||
cross_promo_image: "https://git.ngram.ca/OpenJam/servers/raw/branch/main/assets/robocraft/favicon.jpg".to_owned(),
|
||||
cross_promo_link: "https://git.ngram.ca/OpenJam/servers".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
17
rc_lobby/Cargo.toml
Normal file
17
rc_lobby/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "oj_rc_lobby"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util" ] }
|
||||
clap.workspace = true
|
||||
polariton.workspace = true
|
||||
oj_polariton_auth = { version = "*", path = "../polariton_auth" }
|
||||
polariton_server.workspace = true
|
||||
3
rc_lobby/run_debug.sh
Executable file
3
rc_lobby/run_debug.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1
|
||||
31
rc_lobby/src/cli.rs
Normal file
31
rc_lobby/src/cli.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct CliArgs {
|
||||
/// TCP port on which to accept connections
|
||||
#[arg(short, long, default_value_t = 4540)]
|
||||
pub port: u16,
|
||||
|
||||
/// IP Address on which to accept connections
|
||||
#[arg(long, default_value_t = {"127.0.0.1".to_string()})]
|
||||
pub ip: String,
|
||||
|
||||
/// Domain and port of the game server to send new connections
|
||||
#[arg(long, default_value_t = {"127.0.0.1:4541".to_string()})]
|
||||
pub redirect: String,
|
||||
|
||||
/// Name of game server to send new connections
|
||||
#[arg(long, default_value_t = {"ngram_is_ngnius".to_string()})]
|
||||
pub room_name: String,
|
||||
|
||||
/// Handle one connection and then exit
|
||||
#[arg(short = '1', long)]
|
||||
pub once: bool,
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
pub fn get() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
}
|
||||
263
rc_lobby/src/main.rs
Normal file
263
rc_lobby/src/main.rs
Normal file
@@ -0,0 +1,263 @@
|
||||
#![forbid(unsafe_code)]
|
||||
mod cli;
|
||||
|
||||
use oj_polariton_auth::Handshake;
|
||||
use tokio::net;
|
||||
|
||||
use polariton::packet::{Data, Message, Packet, StandardMessage};
|
||||
use polariton::operation::{OperationResponse, Typed};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
let args = cli::CliArgs::get();
|
||||
log::debug!("Got cli args {:?}", args);
|
||||
|
||||
let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");
|
||||
|
||||
// memory leak, but only once (so not a big deal)
|
||||
let redirect_static = Box::leak(Box::new(args.redirect.clone()));
|
||||
let room_name_static = Box::leak(Box::new(args.room_name.clone()));
|
||||
|
||||
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
|
||||
|
||||
if args.once {
|
||||
log::warn!("Handling first connection and then exiting");
|
||||
let (socket, address) = listener.accept().await?;
|
||||
process_socket(socket, address, redirect_static, room_name_static).await;
|
||||
Ok(())
|
||||
} else {
|
||||
loop {
|
||||
let (socket, address) = listener.accept().await?;
|
||||
tokio::spawn(process_socket(socket, address, redirect_static, room_name_static));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, redirect_url: &str, lobby_name: &str) {
|
||||
log::debug!("Accepting connection from address {}", address);
|
||||
|
||||
let enc = match do_connect_handshake(&mut socket, lobby_name, redirect_url).await {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
log::error!("Failed to do connect handshake with {}", address);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let ctx = polariton::packet::SerdesContext::from_boxed(Default::default(), enc);
|
||||
while let Ok(packet) = polariton_server::utils::receive_packet_async(&mut socket, &ctx).await {
|
||||
match packet {
|
||||
Packet::Ping(ping) => {
|
||||
polariton_server::utils::handle_ping_async(ping, &mut socket, &ctx).await.unwrap_or_default();
|
||||
},
|
||||
Packet::Packet(packet) => log::warn!("Not handling packet {:?}", packet),
|
||||
}
|
||||
}
|
||||
log::debug!("Goodbye connection from address {}", address);
|
||||
}
|
||||
|
||||
const APP_ID: &str = "LobbyServer";
|
||||
|
||||
struct AuthImpl;
|
||||
|
||||
const TOKEN_KEY: u8 = 216; // token;refresh_token
|
||||
//const UNKNOWN_BYTE_KEY: u8 = 217;
|
||||
const SERVICE_KEY: u8 = 224;
|
||||
const USERNAME_KEY: u8 = 225;
|
||||
|
||||
//const CCU_KEY: u8 = 245;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AuthError {
|
||||
WrongService { expected: String, actual: String },
|
||||
MissingService,
|
||||
MissingToken,
|
||||
MissingUsername,
|
||||
}
|
||||
|
||||
impl AuthError {
|
||||
fn log_err(&self) {
|
||||
match self {
|
||||
Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected),
|
||||
Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY),
|
||||
Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY),
|
||||
Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_polariton_auth::AuthProvider<AuthError> for AuthImpl {
|
||||
fn validate(&mut self, params: &std::collections::HashMap<u8, Typed>) -> Result<std::collections::HashMap<u8, Typed>, AuthError> {
|
||||
if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) {
|
||||
if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) {
|
||||
if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) {
|
||||
if service.string == APP_ID {
|
||||
let params_resp = std::collections::HashMap::<u8, Typed>::new();
|
||||
//params_resp.insert(CCU_KEY, Typed::Byte(0));
|
||||
log::debug!("Auth success for {} (token: {})", user.string, token.string);
|
||||
Ok(params_resp)
|
||||
} else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) }
|
||||
} else { Err(AuthError::MissingUsername) }
|
||||
} else { Err(AuthError::MissingService) }
|
||||
} else { Err(AuthError::MissingToken) }
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_connect_handshake(
|
||||
socket: &mut net::TcpStream,
|
||||
game_server_name: &str,
|
||||
game_server_url: &str,
|
||||
) -> Option<Box<dyn polariton::packet::Cryptographer>> {
|
||||
let handshake = Handshake::new(APP_ID);
|
||||
// connect
|
||||
log::debug!("(connect) Handling first packet");
|
||||
let packet1 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read connect packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let (handshake, to_send) = match handshake.connect(&packet1) {
|
||||
Ok(x) => (x.handshake, x.extra),
|
||||
Err(e) => {
|
||||
log::error!("Failed to handle connect handshake: {:?}", e.extra);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send connect ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// encrypt
|
||||
log::debug!("(connect) Handling second packet");
|
||||
let mut packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) public key packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
while let Packet::Ping(ping) = packet2 {
|
||||
polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
|
||||
packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) public key packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) {
|
||||
Ok(x) => (x.handshake, x.extra.0, x.extra.1),
|
||||
Err(e) => {
|
||||
log::error!("Failed to handle encryption handshake: {:?}", e.extra);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send encryption ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// pre-auth
|
||||
let handshake = handshake.with_auth(AuthImpl);
|
||||
let op_ctx = polariton::serdes::SerdesContext::default();
|
||||
let ctx = polariton::packet::SerdesContext::new(op_ctx, crypto);
|
||||
// authenticate
|
||||
log::debug!("(connect) Handling third packet");
|
||||
let mut packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) auth packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
while let Packet::Ping(ping) = packet3 {
|
||||
polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
|
||||
packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) auth packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
let to_send = match handshake.authenticate(&packet3, &ctx) {
|
||||
Ok(x) => x,
|
||||
Err(h) => match h.extra {
|
||||
oj_polariton_auth::AuthError::Validation(e) => {
|
||||
e.log_err();
|
||||
return None;
|
||||
},
|
||||
e => {
|
||||
log::error!("Failed to handle auth handshake: {:?}", e);
|
||||
return None;
|
||||
},
|
||||
},
|
||||
};
|
||||
match polariton_server::utils::send_packet_async(&to_send, socket, &ctx).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send auth ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// redirect to lobby
|
||||
log::debug!("(connect) Handling fourth packet");
|
||||
let mut packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) join packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
while let Packet::Ping(ping) = packet_j {
|
||||
polariton_server::utils::handle_ping_async(ping, socket, &ctx).await.unwrap_or_default();
|
||||
packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) join packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
log::debug!("(connect) Got fourth packet {:?}", packet_j);
|
||||
if let Packet::Packet(msg) = &packet_j {
|
||||
if let Message::Standard(st) = &msg.message {
|
||||
if let Data::OpReq(req) = &st.data {
|
||||
if req.code == 226 { // join lobby
|
||||
log::debug!("Max players from lobby join request: {:?}", req.params.to_owned().to_dict().get(&255));
|
||||
let mut params = std::collections::HashMap::<u8, Typed>::new();
|
||||
params.insert(230 /* game server address */, Typed::Str(game_server_url.into()));
|
||||
params.insert(255 /* room name */, Typed::Str(game_server_name.into()));
|
||||
let resp = Packet::from_message(
|
||||
Message::Standard(
|
||||
StandardMessage { flags: 0,
|
||||
data: Data::OpResp(OperationResponse {
|
||||
code: req.code,
|
||||
return_code: 0,
|
||||
message: Typed::Null,
|
||||
params: params.into(),
|
||||
}),
|
||||
}.encrypt(true)), 0, true, &ctx).unwrap();
|
||||
match polariton_server::utils::send_packet_async(&resp, socket, &ctx).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send lobby ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(ctx.into_crypto())
|
||||
}
|
||||
19
rc_lobby_room/Cargo.toml
Normal file
19
rc_lobby_room/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "oj_rc_lobby_room"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util", "time"] }
|
||||
clap.workspace = true
|
||||
polariton.workspace = true
|
||||
oj_polariton_auth = { version = "*", path = "../polariton_auth" }
|
||||
polariton_server.workspace = true
|
||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||
async-trait.workspace = true
|
||||
3
rc_lobby_room/run_debug.sh
Executable file
3
rc_lobby_room/run_debug.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1
|
||||
31
rc_lobby_room/src/cli.rs
Normal file
31
rc_lobby_room/src/cli.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct CliArgs {
|
||||
/// TCP port on which to accept connections
|
||||
#[arg(short, long, default_value_t = 4541)]
|
||||
pub port: u16,
|
||||
|
||||
/// IP Address on which to accept connections
|
||||
#[arg(long, default_value_t = {"127.0.0.1".to_string()})]
|
||||
pub ip: String,
|
||||
|
||||
/// Assets root
|
||||
#[arg(long, default_value_t = {"../assets/robocraft".to_string()})]
|
||||
pub assets: String,
|
||||
|
||||
/// User data root
|
||||
#[arg(long, default_value_t = {"../data/robocraft".to_string()})]
|
||||
pub data: String,
|
||||
|
||||
/// Handle one connection and then exit
|
||||
#[arg(short = '1', long)]
|
||||
pub once: bool,
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
pub fn get() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
}
|
||||
0
rc_lobby_room/src/data/mod.rs
Normal file
0
rc_lobby_room/src/data/mod.rs
Normal file
280
rc_lobby_room/src/main.rs
Normal file
280
rc_lobby_room/src/main.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
#![forbid(unsafe_code)]
|
||||
mod cli;
|
||||
|
||||
mod data;
|
||||
mod operations;
|
||||
|
||||
use oj_polariton_auth::Handshake;
|
||||
use tokio::net;
|
||||
|
||||
use polariton::packet::{Data, Message, Packet, StandardMessage};
|
||||
use polariton::operation::{OperationResponse, Typed};
|
||||
|
||||
pub struct InitConfig {
|
||||
pub config: oj_rc_core::persist::config::ConfigImpl,
|
||||
pub users: std::sync::Arc<oj_rc_core::persist::user::UserImpl>,
|
||||
pub factory: std::sync::Arc<oj_rc_core::factory::Factory>,
|
||||
pub parsers: oj_rc_core::cubes::CubeParsers,
|
||||
}
|
||||
|
||||
pub type UserTy = oj_rc_core::UserState<()>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
let args = cli::CliArgs::get();
|
||||
log::debug!("Got cli args {:?}", args);
|
||||
|
||||
let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data"));
|
||||
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config"));
|
||||
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
||||
|
||||
let init_ctx = InitConfig {
|
||||
config,
|
||||
users,
|
||||
factory,
|
||||
parsers,
|
||||
};
|
||||
|
||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new()));
|
||||
|
||||
let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");
|
||||
|
||||
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
|
||||
|
||||
if args.once {
|
||||
log::warn!("Handling first connection and then exiting");
|
||||
let (socket, address) = listener.accept().await?;
|
||||
process_socket(socket, address, server.clone(), init_ctx.users.clone()).await;
|
||||
} else {
|
||||
loop {
|
||||
let (socket, address) = listener.accept().await?;
|
||||
tokio::spawn(process_socket(socket, address, server.clone(), init_ctx.users.clone()));
|
||||
}
|
||||
}
|
||||
server.join();
|
||||
server.join_async().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, server: std::sync::Arc<polariton_server::Server<crate::UserTy>>, users: std::sync::Arc<oj_rc_core::persist::user::UserImpl>) {
|
||||
log::debug!("Accepting connection from address {}", address);
|
||||
let enc = match do_connect_handshake(&mut socket).await {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
log::error!("Failed to do connect handshake with {}", address);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (socket_r, socket_w) = socket.into_split();
|
||||
let (chann_tx, chann_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let user_state = oj_rc_core::UserState::<()>::new(users, chann_tx.clone());
|
||||
let ctx = polariton::packet::SerdesContext::from_boxed(Default::default(), enc);
|
||||
server.handle_async_with_channel(socket_r, socket_w, user_state, ctx, chann_tx, chann_rx).await;
|
||||
log::debug!("Goodbye connection from address {}", address);
|
||||
}
|
||||
|
||||
const APP_ID: &str = "LobbyServer";
|
||||
|
||||
struct AuthImpl;
|
||||
|
||||
const TOKEN_KEY: u8 = 216; // token;refresh_token
|
||||
//const UNKNOWN_BYTE_KEY: u8 = 217;
|
||||
const SERVICE_KEY: u8 = 224;
|
||||
const USERNAME_KEY: u8 = 225;
|
||||
|
||||
//const CCU_KEY: u8 = 245;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AuthError {
|
||||
WrongService { expected: String, actual: String },
|
||||
MissingService,
|
||||
MissingToken,
|
||||
MissingUsername,
|
||||
}
|
||||
|
||||
impl AuthError {
|
||||
fn log_err(&self) {
|
||||
match self {
|
||||
Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected),
|
||||
Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY),
|
||||
Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY),
|
||||
Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_polariton_auth::AuthProvider<AuthError> for AuthImpl {
|
||||
fn validate(&mut self, params: &std::collections::HashMap<u8, Typed>) -> Result<std::collections::HashMap<u8, Typed>, AuthError> {
|
||||
if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) {
|
||||
if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) {
|
||||
if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) {
|
||||
if service.string == APP_ID {
|
||||
let params_resp = std::collections::HashMap::<u8, Typed>::new();
|
||||
//params_resp.insert(CCU_KEY, Typed::Byte(0));
|
||||
log::debug!("Auth success for {} (token: {})", user.string, token.string);
|
||||
Ok(params_resp)
|
||||
} else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) }
|
||||
} else { Err(AuthError::MissingUsername) }
|
||||
} else { Err(AuthError::MissingService) }
|
||||
} else { Err(AuthError::MissingToken) }
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_connect_handshake(
|
||||
socket: &mut net::TcpStream,
|
||||
) -> Option<Box<dyn polariton::packet::Cryptographer>> {
|
||||
let handshake = Handshake::new(APP_ID);
|
||||
// connect
|
||||
log::debug!("(connect) Handling first packet");
|
||||
let packet1 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read connect packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let (handshake, to_send) = match handshake.connect(&packet1) {
|
||||
Ok(x) => (x.handshake, x.extra),
|
||||
Err(e) => {
|
||||
log::error!("Failed to handle connect handshake: {:?}", e.extra);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send connect ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// encrypt
|
||||
log::debug!("(connect) Handling second packet");
|
||||
let mut packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) public key packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
while let Packet::Ping(ping) = packet2 {
|
||||
polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
|
||||
packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) public key packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) {
|
||||
Ok(x) => (x.handshake, x.extra.0, x.extra.1),
|
||||
Err(e) => {
|
||||
log::error!("Failed to handle encryption handshake: {:?}", e.extra);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send encryption ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// pre-auth
|
||||
let handshake = handshake.with_auth(AuthImpl);
|
||||
let op_ctx = polariton::serdes::SerdesContext::default();
|
||||
let ctx = polariton::packet::SerdesContext::new(op_ctx, crypto);
|
||||
// authenticate
|
||||
log::debug!("(connect) Handling third packet");
|
||||
let mut packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) auth packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
while let Packet::Ping(ping) = packet3 {
|
||||
polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
|
||||
packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) auth packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
let to_send = match handshake.authenticate(&packet3, &ctx) {
|
||||
Ok(x) => x,
|
||||
Err(h) => match h.extra {
|
||||
oj_polariton_auth::AuthError::Validation(e) => {
|
||||
e.log_err();
|
||||
return None;
|
||||
},
|
||||
e => {
|
||||
log::error!("Failed to handle auth handshake: {:?}", e);
|
||||
return None;
|
||||
},
|
||||
},
|
||||
};
|
||||
match polariton_server::utils::send_packet_async(&to_send, socket, &ctx).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send auth ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// join lobby
|
||||
log::debug!("(join lobby) Handling fourth packet");
|
||||
let mut packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) join packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
while let Packet::Ping(ping) = packet_j {
|
||||
polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
|
||||
packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read (maybe) join packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Packet::Packet(msg) = &packet_j {
|
||||
if let Message::Standard(st) = &msg.message {
|
||||
if let Data::OpReq(req) = &st.data {
|
||||
if req.code == 226 { // join lobby (but for real this time)
|
||||
let mut params = std::collections::HashMap::<u8, Typed>::new();
|
||||
//params.insert(252 /* actors in game */, Typed::Str(game_server_url.into()));
|
||||
params.insert(254 /* game server address */, Typed::Int(42));
|
||||
params.insert(249 /* actor properties */, Typed::HashMap(Vec::new().into()));
|
||||
params.insert(248 /* game properties */, Typed::HashMap(Vec::new().into()));
|
||||
let resp = Packet::from_message(
|
||||
Message::Standard(
|
||||
StandardMessage { flags: 0,
|
||||
data: Data::OpResp(OperationResponse {
|
||||
code: req.code,
|
||||
return_code: 0,
|
||||
message: Typed::Null,
|
||||
params: params.into(),
|
||||
}),
|
||||
}.encrypt(true)), 0, true, &ctx).unwrap();
|
||||
match polariton_server::utils::send_packet_async(&resp, socket, &ctx).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
log::error!("Failed to send lobby ack packet: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(ctx.into_crypto())
|
||||
}
|
||||
22
rc_lobby_room/src/operations/eac.rs
Normal file
22
rc_lobby_room/src/operations/eac.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use polariton_server::operations::{Operation, OperationCode};
|
||||
|
||||
pub struct EacChallengeIgnorer;
|
||||
|
||||
impl <C: Send + 'static> Operation<C> for EacChallengeIgnorer {
|
||||
type User = crate::UserTy;
|
||||
|
||||
fn handle(&self, params: polariton::operation::ParameterTable<C>, _: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||
polariton::operation::OperationResponse {
|
||||
code: 5, // skip the challenge (hopefully)
|
||||
return_code: 0,
|
||||
message: polariton::operation::Typed::Null,
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OperationCode for EacChallengeIgnorer {
|
||||
fn op_code() -> u8 {
|
||||
4
|
||||
}
|
||||
}
|
||||
47
rc_lobby_room/src/operations/join_queue.rs
Normal file
47
rc_lobby_room/src/operations/join_queue.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 0;
|
||||
|
||||
const GROUP_ID_PARAM_KEY: u8 = 2; // str; in
|
||||
const GARAGE_SLOT_PARAM_KEY: u8 = 3; // int; in
|
||||
const GROUP_SIZE_PARAM_KEY: u8 = 4; // int; in
|
||||
const IS_GROUP_LEADER_PARAM_KEY: u8 = 14; // bool; in
|
||||
const LOBBY_TY_PARAM_KEY: u8 = 30; // int; in
|
||||
const EVENT_TO_JOIN_PARAM_KEY: u8 = 41; // str; in
|
||||
|
||||
const ESTIMATED_QUEUE_TIME_PARAM_KEY: u8 = 13; // int (seconds); out
|
||||
const PERSONAL_RANKING_PARAM_KEY: u8 = 17; // double; out
|
||||
|
||||
pub(super) struct QueueJoinProvider;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Send + 'static> SimpleOperation<C> for QueueJoinProvider {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, params: ParameterTable<C>, _user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||
let mut params = params.to_dict();
|
||||
if let Some(Typed::Str(group_id)) = params.remove(&GROUP_ID_PARAM_KEY) {
|
||||
if let Some(Typed::Int(slot_id)) = params.remove(&GARAGE_SLOT_PARAM_KEY) {
|
||||
if let Some(Typed::Int(group_size)) = params.remove(&GROUP_SIZE_PARAM_KEY) {
|
||||
if let Some(Typed::Bool(is_leader)) = params.remove(&IS_GROUP_LEADER_PARAM_KEY) {
|
||||
if let Some(Typed::Int(lobby_ty)) = params.remove(&LOBBY_TY_PARAM_KEY) {
|
||||
let lobby_ty = oj_rc_core::data::lobby::LobbyType::from_int(lobby_ty)?;
|
||||
if let Some(Typed::Str(event_to_join)) = params.remove(&EVENT_TO_JOIN_PARAM_KEY) {
|
||||
log::debug!("Got lobby join queue request of platoon {} ({} players, is_leader:{}) slot {} lobby {:?} event {}", group_id.string, group_size, is_leader, slot_id, lobby_ty, event_to_join.string);
|
||||
params.insert(ESTIMATED_QUEUE_TIME_PARAM_KEY, Typed::Int(42));
|
||||
params.insert(PERSONAL_RANKING_PARAM_KEY, Typed::Double(42.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(params.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn join_queue_provider<C: Send + 'static>() -> SimpleOpImpl<C, crate::UserTy, QueueJoinProvider> {
|
||||
SimpleOpImpl::new(QueueJoinProvider)
|
||||
}
|
||||
16
rc_lobby_room/src/operations/mod.rs
Normal file
16
rc_lobby_room/src/operations/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
mod more_auth;
|
||||
mod eac;
|
||||
mod no_quit;
|
||||
mod join_queue;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
pub fn handler(_init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy> {
|
||||
OperationsHandler::<crate::UserTy>::new()
|
||||
.modify(oj_rc_core::polariton::OpIdCopy)
|
||||
.add(more_auth::MoreLobbyAuth)
|
||||
//.add(eac::EacChallengeIgnorer)
|
||||
//.add(polariton_server::operations::Ack::<2, _>::default())
|
||||
.add(no_quit::quit_blocker_provider())
|
||||
.add(join_queue::join_queue_provider())
|
||||
}
|
||||
42
rc_lobby_room/src/operations/more_auth.rs
Normal file
42
rc_lobby_room/src/operations/more_auth.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use polariton::operation::Typed;
|
||||
use polariton_server::operations::{Operation, OperationCode};
|
||||
|
||||
pub struct MoreLobbyAuth;
|
||||
|
||||
impl MoreLobbyAuth {
|
||||
const AUTH_PAYLOAD_KEY: u8 = 245;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
||||
type User = crate::UserTy;
|
||||
|
||||
async fn handle_async(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||
let params_dict = params.to_dict();
|
||||
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
||||
//let mut write_lock = user.write().unwrap();
|
||||
if user.update_with_auth(&auth_payload.string).await {
|
||||
let mut resp_params = std::collections::HashMap::new();
|
||||
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
||||
return polariton::operation::OperationResponse {
|
||||
code: Self::op_code(),
|
||||
return_code: 0,
|
||||
message: polariton::operation::Typed::Null,
|
||||
params: resp_params.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
polariton::operation::OperationResponse {
|
||||
code: Self::op_code(),
|
||||
return_code: 120,
|
||||
message: polariton::operation::Typed::Null,
|
||||
params: std::collections::HashMap::new().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OperationCode for MoreLobbyAuth {
|
||||
fn op_code() -> u8 {
|
||||
230
|
||||
}
|
||||
}
|
||||
26
rc_lobby_room/src/operations/no_quit.rs
Normal file
26
rc_lobby_room/src/operations/no_quit.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 3;
|
||||
|
||||
const DID_QUIT_LAST_GAME_PARAM_KEY: u8 = 19; // bool
|
||||
const BLOCK_TIME_PARAM_KEY: u8 = 15; // int
|
||||
|
||||
pub(super) struct QuitterBlockProvider;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Send + 'static> SimpleOperation<C> for QuitterBlockProvider {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, params: ParameterTable<C>, _user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||
let mut params = params.to_dict();
|
||||
params.insert(DID_QUIT_LAST_GAME_PARAM_KEY, Typed::Bool(false));
|
||||
params.insert(BLOCK_TIME_PARAM_KEY, Typed::Int(0));
|
||||
Ok(params.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn quit_blocker_provider<C: Send + 'static>() -> SimpleOpImpl<C, crate::UserTy, QuitterBlockProvider> {
|
||||
SimpleOpImpl::new(QuitterBlockProvider)
|
||||
}
|
||||
@@ -23,8 +23,8 @@ pub mod player_rank;
|
||||
pub mod quest;
|
||||
//pub use oj_rc_core::data::auto_regen;
|
||||
//pub use oj_rc_core::data::voting;
|
||||
pub mod lobby;
|
||||
pub use oj_rc_core::data::error_codes;
|
||||
//pub use oj_rc_core::data::lobby;
|
||||
//pub use oj_rc_core::data::error_codes;
|
||||
//pub use oj_rc_core::data::game_mode;
|
||||
pub mod score_multipliers;
|
||||
//pub use oj_rc_core::data::campaign;
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(super) fn validate_robot_provider() -> SimpleFunc<102, crate::UserTy, impl (
|
||||
SimpleFunc::new(|params, _user: &crate::UserTy| {
|
||||
let mut params = params.to_dict();
|
||||
if let Some(Typed::Int(lobby_ty)) = params.get(&LOBBY_PARAM_KEY) {
|
||||
log::info!("Got lobby type {} ({:?})", lobby_ty, crate::data::lobby::LobbyType::from_int(*lobby_ty));
|
||||
log::info!("Got lobby type {} ({:?})", lobby_ty, oj_rc_core::data::lobby::LobbyType::from_int(*lobby_ty));
|
||||
}
|
||||
// let lock = user.read().unwrap();
|
||||
// let user_info = lock.user()?;
|
||||
|
||||
Reference in New Issue
Block a user