diff --git a/Cargo.lock b/Cargo.lock index a8ac40e..7fabb1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -614,17 +614,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.0-rc.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a78f88e84d239c7f2619ae8b091603c26208e1cb322571f5a29d6806f56ee5e" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", - "js-sys", "libc", - "rustix", - "wasi 0.13.3+wasi-0.2.2", - "wasm-bindgen", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1051,6 +1048,7 @@ version = "0.0.0" dependencies = [ "proc-macro2", "quote", + "rand 0.9.0", "syn 2.0.98", ] @@ -1425,6 +1423,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -1438,12 +1442,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0-beta.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fccbfebb3972a41a31c605a59207d9fba5489b9a87d9d87024cb6df73a32ec7" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "rand_chacha 0.9.0-beta.1", - "rand_core 0.9.0-beta.1", + "rand_chacha 0.9.0", + "rand_core 0.9.3", "zerocopy 0.8.14", ] @@ -1459,12 +1463,12 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.9.0-beta.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16da77124f4ee9fabd55ce6540866e9101431863b4876de58b68797f331adf2" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0-beta.1", + "rand_core 0.9.3", ] [[package]] @@ -1478,12 +1482,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0-beta.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98fa0b8309344136abe6244130311e76997e546f76fae8054422a7539b43df7" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.0-rc.0", - "zerocopy 0.8.14", + "getrandom 0.3.2", ] [[package]] @@ -1871,7 +1874,7 @@ dependencies = [ "once_cell", "paste", "prost", - "rand 0.9.0-beta.3", + "rand 0.9.0", "rc4", "rocket", "rustls", @@ -1882,6 +1885,7 @@ dependencies = [ "tokio-stream", "tonic", "tonic-build", + "typenum", "v_byte_macros", ] @@ -2268,9 +2272,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ubyte" @@ -2348,9 +2352,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -2624,9 +2628,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] diff --git a/Cargo.toml b/Cargo.toml index 394af2f..8fab144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ rocket = { version = "0.5.1", features = ["json", "serde_json"] } serde = { version = "1.0.217", features = ["derive"] } async-trait = "0.1.86" paste = "1.0.15" +typenum = "1.18.0" [build-dependencies] tonic-build = "0.12.3" diff --git a/macros/Cargo.toml b/macros/Cargo.toml index d0bb83b..e301949 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,4 +13,5 @@ proc-macro = true quote = "1.0.38" proc-macro2 = "1.0.93" syn = { version = "2.0.98", features = ["full"] } +rand = "0.9.0" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index eb3d5da..5c08c0e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -323,7 +323,8 @@ pub fn rmc_proto(attr: TokenStream, input: TokenStream) -> TokenStream{ ProtoMethodData{ id, name: func.sig.ident.clone(), - parameters: funcs + parameters: funcs, + ret_val: func.sig.output.clone() } }).collect() diff --git a/macros/src/protos.rs b/macros/src/protos.rs index cc0e5cd..554df22 100644 --- a/macros/src/protos.rs +++ b/macros/src/protos.rs @@ -1,12 +1,13 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::{quote, ToTokens}; -use syn::{LitInt, Token, Type}; +use syn::{LitInt, ReturnType, Token, Type}; use syn::token::{Brace, Paren, Semi}; pub struct ProtoMethodData{ pub id: LitInt, pub name: Ident, - pub parameters: Vec<(Ident, Type)> + pub parameters: Vec<(Ident, Type)>, + pub ret_val: ReturnType, } @@ -22,8 +23,8 @@ pub struct RmcProtocolData{ pub methods: Vec } -impl ToTokens for RmcProtocolData{ - fn to_tokens(&self, tokens: &mut TokenStream) { +impl RmcProtocolData{ + fn generate_raw_trait(&self, tokens: &mut TokenStream){ let Self{ has_returns, name, @@ -73,7 +74,7 @@ impl ToTokens for RmcProtocolData{ <#param_type as crate::rmc::structures::RmcSerialize>::deserialize( &mut cursor ) else { - return Err(ErrorCode::Core_InvalidArgument); + return Err(crate::rmc::response::ErrorCode::Core_InvalidArgument); }; }.to_tokens(tokens) } @@ -156,6 +157,111 @@ impl ToTokens for RmcProtocolData{ quote!{ impl RawAuth for T{} }.to_tokens(tokens); + } + + fn generate_raw_remote_trait(&self, tokens: &mut TokenStream) { + let Self { + has_returns, + name, + id: proto_id, + methods, + .. + } = self; + + // this gives us the name which the identifier of the corresponding Raw trait + let remote_name = Ident::new(&format!("Remote{}", name), name.span()); + + + // boilerplate tokens which all raw traits need + quote!{ + #[doc(hidden)] + pub trait #remote_name: crate::rmc::protocols::HasRmcConnection + }.to_tokens(tokens); + + // generate the body of the raw protocol trait + Brace::default().surround(tokens, |tokens|{ + //generate each raw method + for method in methods{ + let ProtoMethodData { + name, + parameters, + ret_val, + id: method_id, + .. + } = method; + + quote!{ + async fn #name + }.to_tokens(tokens); + + Paren::default().surround(tokens, |tokens|{ + quote!{ &self, }.to_tokens(tokens); + for (param_ident, param_type) in parameters{ + quote!{ #param_ident: #param_type, }.to_tokens(tokens); + } + }); + + quote!{ + #ret_val + }.to_tokens(tokens); + + Brace::default().surround(tokens, |tokens|{ + quote! { + let mut send_data = Vec::new(); + let mut cursor = ::std::io::Cursor::new(&mut send_data); + }.to_tokens(tokens); + + for (param_name, param_type) in parameters{ + quote!{ + crate::result::ResultExtension::display_err_or_some( + <#param_type as crate::rmc::structures::RmcSerialize>::serialize( + &#param_name, + &mut cursor + ) + ).ok_or(crate::rmc::response::ErrorCode::Core_InvalidArgument)?; + }.to_tokens(tokens) + } + + quote!{ + let call_id = rand::random(); + + let message = crate::rmc::message::RMCMessage{ + call_id, + method_id: #method_id, + protocol_id: #proto_id, + rest_of_data: send_data + }; + + let rmc_conn = ::get_connection(self); + }.to_tokens(tokens); + + if *has_returns{ + quote!{ + crate::result::ResultExtension::display_err_or_some( + rmc_conn.make_raw_call(&message).await + ).ok_or(crate::rmc::response::ErrorCode::Core_Exception) + }.to_tokens(tokens); + } else { + quote!{ + crate::result::ResultExtension::display_err_or_some( + rmc_conn.make_raw_call_no_response(&message).await + ); + }.to_tokens(tokens); + } + + }) + } + }); + + + } + + fn generate_raw_info(&self, tokens: &mut TokenStream){ + let Self{ + name, + id, + .. + } = self; let raw_info_name = Ident::new(&format!("Raw{}Info", name), Span::call_site()); @@ -171,3 +277,15 @@ impl ToTokens for RmcProtocolData{ } } +impl ToTokens for RmcProtocolData{ + fn to_tokens(&self, tokens: &mut TokenStream) { + self.generate_raw_trait(tokens); + self.generate_raw_info(tokens); + self.generate_raw_remote_trait(tokens); + + + + + } +} + diff --git a/src/main.rs b/src/main.rs index cb2d033..b743c62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,92 +2,115 @@ #![warn(missing_docs)] //! # Splatoon RNEX server -//! -//! This server still includes the code for rnex itself as this is the first rnex server and thus -//! also the first and only current usage of rnex, expect this and rnex to be split into seperate +//! +//! This server still includes the code for rnex itself as this is the first rnex server and thus +//! also the first and only current usage of rnex, expect this and rnex to be split into seperate //! repos soon. -use std::{env, fs}; -use std::fs::File; -use std::net::{Ipv4Addr, SocketAddrV4}; +use crate::rmc::protocols::auth::RemoteAuth; +use crate::rmc::protocols::auth::RawAuthInfo; +use crate::rmc::protocols::auth::RawAuth; +use crate::nex::account::Account; +use crate::prudp::packet::VirtualPort; +use crate::prudp::router::Router; +use crate::prudp::sockaddr::PRUDPSockAddr; +use crate::prudp::socket::Unsecure; use chrono::{Local, SecondsFormat}; use log::info; use once_cell::sync::Lazy; -use simplelog::{ColorChoice, CombinedLogger, Config, LevelFilter, TerminalMode, TermLogger, WriteLogger}; -use crate::nex::account::Account; -use crate::prudp::socket::Unsecure; -use crate::prudp::packet::{VirtualPort}; -use crate::prudp::router::Router; -use crate::prudp::sockaddr::PRUDPSockAddr; +use simplelog::{ + ColorChoice, CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode, WriteLogger, +}; +use std::fs::File; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::{env, fs}; +use std::marker::PhantomData; +use std::ops::{BitAnd, BitOr}; +use std::str::FromStr; +use macros::rmc_struct; +use crate::rmc::protocols::auth::Auth; +use crate::rmc::protocols::{new_rmc_gateway_connection, OnlyRemote}; +use crate::rmc::response::ErrorCode; +use crate::rmc::structures::any::Any; +use crate::rmc::structures::connection_data::ConnectionData; +use crate::rmc::structures::qresult::QResult; mod endianness; mod prudp; pub mod rmc; //mod protocols; -mod nex; mod grpc; mod kerberos; +mod nex; mod web; +mod versions; +mod result; -static KERBEROS_SERVER_PASSWORD: Lazy = Lazy::new(||{ +static KERBEROS_SERVER_PASSWORD: Lazy = Lazy::new(|| { env::var("AUTH_SERVER_PASSWORD") .ok() .unwrap_or("password".to_owned()) }); +static AUTH_SERVER_ACCOUNT: Lazy = + Lazy::new(|| Account::new(1, "Quazal Authentication", &KERBEROS_SERVER_PASSWORD)); +static SECURE_SERVER_ACCOUNT: Lazy = + Lazy::new(|| Account::new(2, "Quazal Rendez-Vous", &KERBEROS_SERVER_PASSWORD)); -static AUTH_SERVER_ACCOUNT: Lazy = Lazy::new(|| Account::new(1, "Quazal Authentication", &KERBEROS_SERVER_PASSWORD)); -static SECURE_SERVER_ACCOUNT: Lazy = Lazy::new(|| Account::new(2, "Quazal Rendez-Vous", &KERBEROS_SERVER_PASSWORD)); - -static AUTH_SERVER_PORT: Lazy = Lazy::new(||{ +static AUTH_SERVER_PORT: Lazy = Lazy::new(|| { env::var("AUTH_SERVER_PORT") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(10000) }); -static SECURE_SERVER_PORT: Lazy = Lazy::new(||{ +static SECURE_SERVER_PORT: Lazy = Lazy::new(|| { env::var("SECURE_SERVER_PORT") .ok() .and_then(|s| s.parse().ok()) - .unwrap_or(10001) + .unwrap_or(10002) }); -static OWN_IP_PRIVATE: Lazy = Lazy::new(||{ +static OWN_IP_PRIVATE: Lazy = Lazy::new(|| { env::var("SERVER_IP") .ok() .and_then(|s| s.parse().ok()) .expect("no public ip specified") }); -static OWN_IP_PUBLIC: Lazy = Lazy::new(||{ - env::var("SERVER_IP_PUBLIC") - .unwrap_or(OWN_IP_PRIVATE.to_string()) -}); +static OWN_IP_PUBLIC: Lazy = + Lazy::new(|| env::var("SERVER_IP_PUBLIC").unwrap_or(OWN_IP_PRIVATE.to_string())); -static SECURE_STATION_URL: Lazy = Lazy::new(|| - format!("prudps:/PID=2;sid=1;stream=10;type=2;address={};port={};CID=1", *OWN_IP_PUBLIC, *SECURE_SERVER_PORT) -); +static SECURE_STATION_URL: Lazy = Lazy::new(|| { + format!( + "prudps:/PID=2;sid=1;stream=10;type=2;address={};port={};CID=1", + *OWN_IP_PUBLIC, *SECURE_SERVER_PORT + ) +}); #[tokio::main] async fn main() { - CombinedLogger::init( - vec![ - TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto), - WriteLogger::new(LevelFilter::max(), Config::default(), { - fs::create_dir_all("log").unwrap(); - let date = Local::now().to_rfc3339_opts(SecondsFormat::Secs, false); - // this fixes windows being windows - let date = date.replace(":", "-"); - let filename = format!("{}.log", date); - if cfg!(windows) { - File::create(format!("log\\{}", filename)).unwrap() - } else { - File::create(format!("log/{}", filename)).unwrap() - } - }) - ] - ).unwrap(); + CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Info, + Config::default(), + TerminalMode::Mixed, + ColorChoice::Auto, + ), + WriteLogger::new(LevelFilter::max(), Config::default(), { + fs::create_dir_all("log").unwrap(); + let date = Local::now().to_rfc3339_opts(SecondsFormat::Secs, false); + // this fixes windows being windows + let date = date.replace(":", "-"); + let filename = format!("{}.log", date); + if cfg!(windows) { + File::create(format!("log\\{}", filename)).unwrap() + } else { + File::create(format!("log/{}", filename)).unwrap() + } + }), + ]) + .unwrap(); dotenv::dotenv().ok(); @@ -234,68 +257,63 @@ async fn start_secure_server() -> SecureServer{ socket, } }*/ -/* + define_rmc_proto!( proto AuthClientProtocol{ Auth } -);*/ +); +impl Auth for AuthClient{ + async fn login(&self, name: String) -> Result<(), ErrorCode> { + todo!() + } -//#[rmc_struct(AuthClientProtocol)] -struct AuthClient{ + async fn login_ex(&self, name: String, extra_data: Any) -> Result<(QResult, u32, Vec, ConnectionData, String), ErrorCode> { + todo!() + } + async fn request_ticket(&self, source_pid: u32, destination_pid: u32) -> Result<(QResult, Vec), ErrorCode> { + todo!() + } + + async fn get_pid(&self, username: String) -> Result { + todo!() + } + + async fn get_name(&self, pid: u32) -> Result { + todo!() + } } -async fn start_servers(){ +#[rmc_struct(AuthClientProtocol)] +struct AuthClient {} + +async fn start_servers() { + + + //let auth_ip = SocketAddrV4::from_str("157.90.13.221:30039").unwrap(); + let auth_ip = SocketAddrV4::from_str("31.220.75.208:10000").unwrap(); + let auth_port = VirtualPort::new(1, 10); + + let auth_sockaddr = PRUDPSockAddr::new(auth_ip, auth_port); + + let (router_secure, _) = Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *SECURE_SERVER_PORT)) + .await + .expect("unable to start router"); + + let mut socket_secure = router_secure + .add_socket(VirtualPort::new(1, 10), Unsecure("CD&ML")) + .await + .expect("unable to add socket"); + + let conn = socket_secure.connect(auth_sockaddr).await.unwrap(); + + let obj = new_rmc_gateway_connection(conn, OnlyRemote::::new); - - - let a = tokio::spawn(async{ - let (router_auth, _) = - Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *AUTH_SERVER_PORT)).await.expect("unable to start router"); - - let mut socket_auth = router_auth.add_socket(VirtualPort::new(1,10), Unsecure("CD&ML")).await - .expect("unable to add socket"); - - let mut conn = socket_auth.accept().await.unwrap(); - info!("got conn"); - - if let Some(data) = conn.recv().await{ - let str = String::from_utf8(data).unwrap(); - - println!("{}", str) - } - }); - - let b = tokio::spawn(async{ - let auth_ip = SocketAddrV4::new(*OWN_IP_PRIVATE, *AUTH_SERVER_PORT); - let auth_port = VirtualPort::new(1,10); - - let auth_sockaddr = PRUDPSockAddr::new(auth_ip,auth_port); - - - let (router_secure, _) = - Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *SECURE_SERVER_PORT)).await.expect("unable to start router"); - - let mut socket_secure = router_secure.add_socket(VirtualPort::new(1,10), Unsecure("CD&ML")).await - .expect("unable to add socket"); - - let conn = socket_secure.connect(auth_sockaddr).await.unwrap(); - - let conn = conn.duplicate_sender(); - - conn.send("Yippie".as_bytes().to_owned()).await; - - info!("got conn"); - }); - - a.await; - b.await; - -/* + /* #[cfg(feature = "auth")] let auth_server = start_auth_server().await; #[cfg(feature = "secure")] @@ -307,4 +325,4 @@ async fn start_servers(){ #[cfg(feature = "secure")] secure_server.join_handle.await.expect("auth server crashed"); web_server.await.expect("webserver crashed");*/ -} +} \ No newline at end of file diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..31a37d1 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,23 @@ +use std::error::Error; +use log::error; + +pub trait ResultExtension{ + type Output; + + fn display_err_or_some(self) -> Option; +} + +impl ResultExtension for Result{ + type Output = T; + + fn display_err_or_some(self) -> Option { + match self{ + Ok(v) => Some(v), + Err(e) => { + error!("{}", e); + + None + } + } + } +} \ No newline at end of file diff --git a/src/rmc/protocols/auth.rs b/src/rmc/protocols/auth.rs index 5aa84f3..ed8d1cd 100644 --- a/src/rmc/protocols/auth.rs +++ b/src/rmc/protocols/auth.rs @@ -9,9 +9,13 @@ use macros::{method_id, rmc_proto}; /// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol)) #[rmc_proto(10)] pub trait Auth { + /// representation of the `Login` method(for details see the + /// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol)) #[method_id(1)] async fn login(&self, name: String) -> Result<(), ErrorCode>; + /// representation of the `LoginEx` method(for details see the + /// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol)) #[method_id(2)] async fn login_ex( &self, @@ -19,6 +23,8 @@ pub trait Auth { extra_data: Any, ) -> Result<(QResult, u32, Vec, ConnectionData, String), ErrorCode>; + /// representation of the `RequestTicket` method(for details see the + /// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol)) #[method_id(3)] async fn request_ticket( &self, @@ -26,9 +32,16 @@ pub trait Auth { destination_pid: u32, ) -> Result<(QResult, Vec), ErrorCode>; + /// representation of the `GetPID` method(for details see the + /// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol)) #[method_id(4)] async fn get_pid(&self, username: String) -> Result; + /// representation of the `LoginWithContext` method(for details see the + /// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol)) #[method_id(5)] async fn get_name(&self, pid: u32) -> Result; + + // `LoginWithContext` is left out here because we don't need it right now and versioning still + // needs to be figured out } diff --git a/src/rmc/protocols/mod.rs b/src/rmc/protocols/mod.rs index 74c020c..456b402 100644 --- a/src/rmc/protocols/mod.rs +++ b/src/rmc/protocols/mod.rs @@ -2,52 +2,113 @@ pub mod auth; -use macros::method_id; -use std::collections::HashMap; -use std::ops::Add; -use std::sync::{Arc, Condvar}; -use std::time::Duration; +use crate::prudp::socket::{ExternalConnection, SendingConnection}; +use crate::rmc::message::RMCMessage; +use crate::rmc::protocols::RemoteCallError::ConnectionBroke; +use crate::rmc::response::{ErrorCode, RMCResponse, RMCResponseResult}; +use crate::rmc::structures; +use crate::rmc::structures::connection_data::ConnectionData; +use crate::rmc::structures::matchmake::AutoMatchmakeParam; +use crate::rmc::structures::{Error, RmcSerialize}; use async_trait::async_trait; use chrono::TimeDelta; +use log::{error, info}; +use macros::method_id; use macros::{rmc_proto, rmc_struct}; use paste::paste; +use std::collections::HashMap; +use std::io::Cursor; +use std::ops::{Add, Deref}; +use std::sync::{Arc, Condvar}; +use std::time::Duration; +use thiserror::Error; use tokio::sync::{Mutex, Notify}; use tokio::time::{sleep_until, Instant}; -use crate::prudp::socket::{ExternalConnection, SendingConnection}; -use crate::rmc::response::ErrorCode; -use crate::rmc::structures::connection_data::ConnectionData; -use crate::rmc::structures::Error; -use crate::rmc::structures::matchmake::AutoMatchmakeParam; +use crate::result::ResultExtension; + +#[derive(Error, Debug)] +pub enum RemoteCallError { + #[error("Call to remote timed out whilest waiting on response.")] + Timeout, + #[error("A server side rmc error occurred: {0:?}")] + ServerError(ErrorCode), + #[error("Connection broke")] + ConnectionBroke, + #[error("Error reading response data")] + InvalidResponse(#[from] structures::Error), +} pub struct RmcConnection(pub SendingConnection, pub RmcResponseReceiver); -pub struct RmcResponseReceiver(Notify, Mutex>>); +pub struct RmcResponseReceiver(Arc, Arc>>); -impl RmcResponseReceiver{ +impl RmcConnection { + pub async fn make_raw_call( + &self, + message: &RMCMessage, + ) -> Result { + self.make_raw_call_no_response(message).await?; + + let data = self.1.get_response_data(message.call_id).await?; + + let out = ::deserialize(&mut Cursor::new(data))?; + + Ok(out) + } + + pub async fn make_raw_call_no_response( + &self, + message: &RMCMessage, + ) -> Result<(), RemoteCallError> { + let message_data = message.to_data(); + + self.0.send(message_data).await.ok_or(ConnectionBroke)?; + + Ok(()) + } +} + +impl RmcResponseReceiver { // returns none if timed out - pub async fn get_response_data(&self, call_id: u32) -> Option>{ + pub async fn get_response_data(&self, call_id: u32) -> Result, RemoteCallError> { let mut end_wait_time = Instant::now(); end_wait_time += Duration::from_secs(5); let sleep_fut = sleep_until(end_wait_time); tokio::pin!(sleep_fut); + let mut sleep_manual_unlock_fut = Instant::now(); + sleep_manual_unlock_fut += Duration::from_secs(4); + + let sleep_manual_unlock_fut = sleep_until(sleep_manual_unlock_fut); + tokio::pin!(sleep_manual_unlock_fut); + loop { let mut locked = self.1.lock().await; - if let Some(v) = locked.remove(&call_id){ - return Some(v); + if let Some(v) = locked.remove(&call_id) { + match v.response_result{ + RMCResponseResult::Success { + data, + .. + } => return Ok(data), + RMCResponseResult::Error { + error_code, + .. + } => return Err(RemoteCallError::ServerError(error_code)) + } } drop(locked); - let notif_fut = self.0.notified(); - tokio::select! { + _ = &mut sleep_manual_unlock_fut => { + continue; + } _ = &mut sleep_fut => { - return None; + return Err(RemoteCallError::Timeout); } _ = notif_fut => { continue; @@ -57,28 +118,30 @@ impl RmcResponseReceiver{ } } -pub trait HasRmcConnection{ - fn get_response_receiver(&self) -> &RmcConnection; +pub trait HasRmcConnection { + fn get_connection(&self) -> &RmcConnection; } -pub trait RemoteObject{ +pub trait RemoteObject { fn new(conn: RmcConnection) -> Self; } -impl RemoteObject for (){ +impl RemoteObject for () { fn new(_: RmcConnection) -> Self {} } - - -pub trait RmcCallable{ +pub trait RmcCallable { //type Remote: RemoteObject; - //fn new_callable(remote: Self::Remote); - async fn rmc_call(&self, responder: &SendingConnection, protocol_id: u16, method_id: u32, call_id: u32, rest: Vec); + async fn rmc_call( + &self, + responder: &SendingConnection, + protocol_id: u16, + method_id: u32, + call_id: u32, + rest: Vec, + ); } - - #[macro_export] macro_rules! define_rmc_proto { (proto $name:ident{ @@ -98,7 +161,131 @@ macro_rules! define_rmc_proto { struct [](crate::rmc::protocols::RmcConnection); + impl crate::rmc::protocols::RemoteInstantiatable for []{ + fn new(conn: crate::rmc::protocols::RmcConnection) -> Self{ + Self(conn) + } + } + impl crate::rmc::protocols::HasRmcConnection for []{ + fn get_connection(&self) -> &crate::rmc::protocols::RmcConnection{ + &self.0 + } + } + + $( + impl [] for []{} + )* } }; -} \ No newline at end of file +} + +/// This is a special case to allow unit to represent the fact that no object is represented. +impl RmcCallable for () { + async fn rmc_call( + &self, + remote_response_connection: &crate::prudp::socket::SendingConnection, + protocol_id: u16, + method_id: u32, + call_id: u32, + rest: Vec, + ) { + //todo: maybe reply with not implemented(?) + } +} + +pub trait RemoteInstantiatable{ + fn new(conn: RmcConnection) -> Self; +} + +pub struct OnlyRemote(T); + +impl Deref for OnlyRemote{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl OnlyRemote{ + pub fn new(conn: RmcConnection) -> Self{ + Self(T::new(conn)) + } +} + +impl RmcCallable for OnlyRemote{ + async fn rmc_call(&self, responder: &SendingConnection, protocol_id: u16, method_id: u32, call_id: u32, rest: Vec) { + + } +} + +async fn handle_incoming( + mut connection: ExternalConnection, + remote: Arc, + notify: Arc, + incoming: Arc>>, +) { + let sending_conn = connection.duplicate_sender(); + + while let Some(v) = connection.recv().await{ + let Some(proto_id) = v.get(5) else { + error!("received too small rmc message."); + error!("ending rmc gateway."); + return + }; + + if proto_id & 0x80 == 0{ + let Some(response) = RMCResponse::new(&mut Cursor::new(v)).display_err_or_some() else { + error!("ending rmc gateway."); + return + }; + + info!("got rmc response"); + + let mut locked = incoming.lock().await; + + locked.insert(response.get_call_id(), response); + notify.notify_waiters(); + } else { + let Some(message) = RMCMessage::new(&mut Cursor::new(v)).display_err_or_some() else { + error!("ending rmc gateway."); + return + }; + + info!("got rmc request"); + } + } +} + +pub fn new_rmc_gateway_connection(conn: ExternalConnection, create_internal: F) -> Arc +where + F: FnOnce(RmcConnection) -> T, +{ + let notify = Arc::new(Notify::new()); + let incoming: Arc>> = Default::default(); + + let response_recv = RmcResponseReceiver(notify.clone(), incoming.clone()); + + let sending_conn = conn.duplicate_sender(); + + let rmc_conn = RmcConnection(sending_conn, response_recv); + + let exposed_object = (create_internal)(rmc_conn); + + let exposed_object = Arc::new(exposed_object); + + { + let exposed_object = exposed_object.clone(); + tokio::spawn(async move { + handle_incoming( + conn, + exposed_object, + notify, + incoming + ).await; + }); + } + + exposed_object +} diff --git a/src/rmc/response.rs b/src/rmc/response.rs index ece1486..84bd529 100644 --- a/src/rmc/response.rs +++ b/src/rmc/response.rs @@ -1,12 +1,16 @@ use std::io; -use std::io::{Write}; +use std::io::{Read, Seek, Write}; use std::mem::transmute; use bytemuck::bytes_of; +use log::error; +use v_byte_macros::EnumTryInto; +use crate::endianness::{ReadExtensions, IS_BIG_ENDIAN}; use crate::prudp::packet::{PRUDPPacket}; use crate::prudp::packet::flags::{NEED_ACK, RELIABLE}; use crate::prudp::packet::PacketOption::FragmentId; use crate::prudp::packet::types::DATA; use crate::prudp::socket::{ExternalConnection, SendingConnection}; +use crate::rmc::response::ErrorCode::Core_Exception; use crate::rmc::structures::qresult::ERROR_MASK; use crate::rmc::structures::RmcSerialize; use crate::web::DirectionalData::{Incoming, Outgoing}; @@ -30,6 +34,69 @@ pub struct RMCResponse { } impl RMCResponse { + pub fn new(stream: &mut (impl Seek + Read)) -> io::Result{ + // ignore the size for now this will only be used for checking + let _: u32 = stream.read_struct(IS_BIG_ENDIAN)?; + + let protocol_id: u8 = stream.read_struct(IS_BIG_ENDIAN)?; + + /*let protocol_id: u16 = match protocol_id{ + 0x7F => { + stream.read_struct(IS_BIG_ENDIAN)? + }, + _ => protocol_id as u16 + };*/ + + let is_success: u8 = stream.read_struct(IS_BIG_ENDIAN)?; + + let response_result = if is_success == 0x01{ + let call_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?; + let method_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?; + let method_id = method_id & (!0x8000); + + let mut data: Vec = Vec::new(); + + stream.read_to_end(&mut data)?; + + + RMCResponseResult::Success { + call_id, + method_id, + data + } + } else { + let error_code: u32 = stream.read_struct(IS_BIG_ENDIAN)?; + let error_code = error_code & (!0x80000000); + let call_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?; + + RMCResponseResult::Error { + error_code: { + match ErrorCode::try_from(error_code){ + Ok(v) => v, + Err(e) => { + error!("invalid error code {:#010x}", error_code); + Core_Exception + } + } + }, + call_id, + + } + }; + + Ok(Self{ + protocol_id, + response_result + }) + } + + pub fn get_call_id(&self) -> u32{ + match &self.response_result{ + RMCResponseResult::Success { call_id, ..} => *call_id, + RMCResponseResult::Error { call_id, .. } => *call_id + } + } + pub fn to_data(self) -> Vec { generate_response(self.protocol_id, self.response_result).expect("failed to generate response") } @@ -120,6 +187,7 @@ pub async fn send_response(connection: &SendingConnection, rmcresponse: RMCRespo //taken from kinnays error list directly #[allow(nonstandard_style)] #[repr(u32)] +#[derive(Debug, EnumTryInto)] pub enum ErrorCode { Core_Unknown = 0x00010001, Core_NotImplemented = 0x00010002, diff --git a/src/rmc/structures/any.rs b/src/rmc/structures/any.rs index cc1de19..d402f3e 100644 --- a/src/rmc/structures/any.rs +++ b/src/rmc/structures/any.rs @@ -2,15 +2,24 @@ use std::io::{Read, Write}; use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions}; use super::{Result, RmcSerialize}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Any{ pub name: String, pub data: Vec } impl RmcSerialize for Any{ - fn serialize(&self, _writer: &mut dyn Write) -> Result<()> { - todo!() + fn serialize(&self, writer: &mut dyn Write) -> Result<()> { + self.name.serialize(writer)?; + + let u32_len = self.data.len() as u32; + + u32_len.serialize(writer)?; + u32_len.serialize(writer)?; + + self.data.serialize(writer)?; + + Ok(()) } fn deserialize(mut reader: &mut dyn Read) -> Result { let name = String::deserialize(reader)?; diff --git a/src/rmc/structures/connection_data.rs b/src/rmc/structures/connection_data.rs index de20491..d0c27b1 100644 --- a/src/rmc/structures/connection_data.rs +++ b/src/rmc/structures/connection_data.rs @@ -3,6 +3,7 @@ use bytemuck::bytes_of; use crate::kerberos::KerberosDateTime; use crate::rmc::structures::{rmc_struct, RmcSerialize}; +#[derive(Debug)] pub struct ConnectionData<'a>{ pub station_url: &'a str, pub special_protocols: Vec, diff --git a/src/rmc/structures/qresult.rs b/src/rmc/structures/qresult.rs index d4a5078..41a5263 100644 --- a/src/rmc/structures/qresult.rs +++ b/src/rmc/structures/qresult.rs @@ -7,7 +7,7 @@ use crate::rmc::structures::{RmcSerialize, Result}; pub const ERROR_MASK: u32 = 1 << 31; -#[derive(Pod, Zeroable, Copy, Clone, SwapEndian)] +#[derive(Pod, Zeroable, Copy, Clone, SwapEndian, Debug)] #[repr(transparent)] pub struct QResult(u32); diff --git a/src/versions.rs b/src/versions.rs new file mode 100644 index 0000000..0fa85fb --- /dev/null +++ b/src/versions.rs @@ -0,0 +1,97 @@ +use std::marker::PhantomData; +use std::ops::{BitAnd, BitOr}; +use typenum::{Cmp, IsEqual, IsLess, IsLessOrEqual, Unsigned, U1, U2, U3}; + +/// This trait represents a version at compile time +trait Version{ + type Major: Unsigned; + type Minor: Unsigned; +} + +/// This struct contains nothing and is used to represent specific versions as an instance of +/// [`Version`]. It is instances as `Ver` +struct Ver{ + _phantom: PhantomData<(MAJ, MIN)> +} + +impl Version for Ver{ + type Major = MAJ; + type Minor = MIN; +} + +/// Represents two versions which can be compared +trait ComparableVersion: Version{ + type IsAtLeast: SameOrUnit; +} + +impl ComparableVersion for U where + ::Major: Cmp, + ::Minor: IsLessOrEqual, + ::Major: IsEqual< + Self::Major, + Output: BitAnd< + typenum::LeEq + >, + >, + ::Major: IsLess< + Self::Major, + Output: BitOr< + typenum::And< + typenum::Eq, + typenum::LeEq, + >, + Output: SameOrUnit + > + > { + + type IsAtLeast = typenum::Or< + typenum::Le, + typenum::And< + typenum::Eq, + typenum::LeEq, + > + >; +} + + +/// Simple check for testing if the `TEST` version is at least `REQ` or higher. +type VersionAbove = >::IsAtLeast; + +trait VersionIsAtLeast{} + +impl> VersionIsAtLeast for T{} + + +/// Trait for containing the result of elements which only conditionally exist +trait CondElemResult{ + type Output; +} + +/// Empty helper struct which only servers to give a concrete type when creating fields in rmc +/// structs which have a version requirement. This is not meant to be used directly, use +/// [`MinVersion`] instead. +struct MinVersionElementHelper>{ + _phantom: PhantomData<(T, REQUIRED, VER)> +} + +/// This should be used either with [`typenum::True`] or [`typenum::False`]. When `True` the [`Self::Output`] +/// will be the same as the `T` you put into Output. When `False` it will always be `()` +trait SameOrUnit{ + type Output; +} + +impl SameOrUnit for typenum::True{ + type Output = T; +} + +impl SameOrUnit for typenum::False{ + type Output = (); +} + +impl> CondElemResult for MinVersionElementHelper where { + type Output = <>::IsAtLeast as SameOrUnit>::Output; +} + +/// When the version condition is met the field will exist and will simply be `T` if not it will be +/// replaced by `()`. Use this when you need to add versioning to rmc structs. +type MinVersion = as CondElemResult>::Output; \ No newline at end of file