feat: almost done with rework communication with remotes now works

This commit is contained in:
DJMrTV 2025-03-24 23:31:25 +01:00
commit a7c36c39ef
14 changed files with 696 additions and 155 deletions

54
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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"

View file

@ -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()

View file

@ -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<ProtoMethodData>
}
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<T: #name> 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 = <Self as crate::rmc::protocols::HasRmcConnection>::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);
}
}

View file

@ -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<String> = Lazy::new(||{
static KERBEROS_SERVER_PASSWORD: Lazy<String> = Lazy::new(|| {
env::var("AUTH_SERVER_PASSWORD")
.ok()
.unwrap_or("password".to_owned())
});
static AUTH_SERVER_ACCOUNT: Lazy<Account> =
Lazy::new(|| Account::new(1, "Quazal Authentication", &KERBEROS_SERVER_PASSWORD));
static SECURE_SERVER_ACCOUNT: Lazy<Account> =
Lazy::new(|| Account::new(2, "Quazal Rendez-Vous", &KERBEROS_SERVER_PASSWORD));
static AUTH_SERVER_ACCOUNT: Lazy<Account> = Lazy::new(|| Account::new(1, "Quazal Authentication", &KERBEROS_SERVER_PASSWORD));
static SECURE_SERVER_ACCOUNT: Lazy<Account> = Lazy::new(|| Account::new(2, "Quazal Rendez-Vous", &KERBEROS_SERVER_PASSWORD));
static AUTH_SERVER_PORT: Lazy<u16> = Lazy::new(||{
static AUTH_SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("AUTH_SERVER_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10000)
});
static SECURE_SERVER_PORT: Lazy<u16> = Lazy::new(||{
static SECURE_SERVER_PORT: Lazy<u16> = 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<Ipv4Addr> = Lazy::new(||{
static OWN_IP_PRIVATE: Lazy<Ipv4Addr> = Lazy::new(|| {
env::var("SERVER_IP")
.ok()
.and_then(|s| s.parse().ok())
.expect("no public ip specified")
});
static OWN_IP_PUBLIC: Lazy<String> = Lazy::new(||{
env::var("SERVER_IP_PUBLIC")
.unwrap_or(OWN_IP_PRIVATE.to_string())
});
static OWN_IP_PUBLIC: Lazy<String> =
Lazy::new(|| env::var("SERVER_IP_PUBLIC").unwrap_or(OWN_IP_PRIVATE.to_string()));
static SECURE_STATION_URL: Lazy<String> = 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<String> = 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<u8>, ConnectionData, String), ErrorCode> {
todo!()
}
async fn request_ticket(&self, source_pid: u32, destination_pid: u32) -> Result<(QResult, Vec<u8>), ErrorCode> {
todo!()
}
async fn get_pid(&self, username: String) -> Result<u32, ErrorCode> {
todo!()
}
async fn get_name(&self, pid: u32) -> Result<String, ErrorCode> {
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::<RemoteAuthClientProtocol>::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");*/
}
}

23
src/result.rs Normal file
View file

@ -0,0 +1,23 @@
use std::error::Error;
use log::error;
pub trait ResultExtension{
type Output;
fn display_err_or_some(self) -> Option<Self::Output>;
}
impl<T, U: Error> ResultExtension for Result<T, U>{
type Output = T;
fn display_err_or_some(self) -> Option<Self::Output> {
match self{
Ok(v) => Some(v),
Err(e) => {
error!("{}", e);
None
}
}
}
}

View file

@ -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<u8>, 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<u8>), 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<u32, ErrorCode>;
/// 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<String, ErrorCode>;
// `LoginWithContext` is left out here because we don't need it right now and versioning still
// needs to be figured out
}

View file

@ -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<HashMap<(u32), Vec<u8>>>);
pub struct RmcResponseReceiver(Arc<Notify>, Arc<Mutex<HashMap<u32, RMCResponse>>>);
impl RmcResponseReceiver{
impl RmcConnection {
pub async fn make_raw_call<T: RmcSerialize>(
&self,
message: &RMCMessage,
) -> Result<T, RemoteCallError> {
self.make_raw_call_no_response(message).await?;
let data = self.1.get_response_data(message.call_id).await?;
let out = <T as RmcSerialize>::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<Vec<u8>>{
pub async fn get_response_data(&self, call_id: u32) -> Result<Vec<u8>, 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<u8>);
async fn rmc_call(
&self,
responder: &SendingConnection,
protocol_id: u16,
method_id: u32,
call_id: u32,
rest: Vec<u8>,
);
}
#[macro_export]
macro_rules! define_rmc_proto {
(proto $name:ident{
@ -98,7 +161,131 @@ macro_rules! define_rmc_proto {
struct [<Remote $name>](crate::rmc::protocols::RmcConnection);
impl crate::rmc::protocols::RemoteInstantiatable for [<Remote $name>]{
fn new(conn: crate::rmc::protocols::RmcConnection) -> Self{
Self(conn)
}
}
impl crate::rmc::protocols::HasRmcConnection for [<Remote $name>]{
fn get_connection(&self) -> &crate::rmc::protocols::RmcConnection{
&self.0
}
}
$(
impl [<Remote $protocol>] for [<Remote $name>]{}
)*
}
};
}
}
/// 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<u8>,
) {
//todo: maybe reply with not implemented(?)
}
}
pub trait RemoteInstantiatable{
fn new(conn: RmcConnection) -> Self;
}
pub struct OnlyRemote<T: RemoteInstantiatable>(T);
impl<T: RemoteInstantiatable> Deref for OnlyRemote<T>{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: RemoteInstantiatable> OnlyRemote<T>{
pub fn new(conn: RmcConnection) -> Self{
Self(T::new(conn))
}
}
impl<T: RemoteInstantiatable> RmcCallable for OnlyRemote<T>{
async fn rmc_call(&self, responder: &SendingConnection, protocol_id: u16, method_id: u32, call_id: u32, rest: Vec<u8>) {
}
}
async fn handle_incoming<T: RmcCallable>(
mut connection: ExternalConnection,
remote: Arc<T>,
notify: Arc<Notify>,
incoming: Arc<Mutex<HashMap<u32, RMCResponse>>>,
) {
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<T: RmcCallable + Sync + Send + 'static,F>(conn: ExternalConnection, create_internal: F) -> Arc<T>
where
F: FnOnce(RmcConnection) -> T,
{
let notify = Arc::new(Notify::new());
let incoming: Arc<Mutex<HashMap<u32, RMCResponse>>> = 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
}

View file

@ -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<Self>{
// 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<u8> = 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<u8> {
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,

View file

@ -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<u8>
}
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<Self> {
let name = String::deserialize(reader)?;

View file

@ -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<u8>,

View file

@ -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);

97
src/versions.rs Normal file
View file

@ -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<Major, Minor>`
struct Ver<MAJ: Unsigned, MIN: Unsigned>{
_phantom: PhantomData<(MAJ, MIN)>
}
impl<MAJ: Unsigned, MIN: Unsigned> Version for Ver<MAJ, MIN>{
type Major = MAJ;
type Minor = MIN;
}
/// Represents two versions which can be compared
trait ComparableVersion<T: Version>: Version{
type IsAtLeast: SameOrUnit;
}
impl<T: Version, U: Version> ComparableVersion<T> for U where
<T as Version>::Major: Cmp<Self::Major>,
<T as Version>::Minor: IsLessOrEqual<Self::Minor>,
<T as Version>::Major: IsEqual<
Self::Major,
Output: BitAnd<
typenum::LeEq<T::Minor, Self::Minor>
>,
>,
<T as Version>::Major: IsLess<
Self::Major,
Output: BitOr<
typenum::And<
typenum::Eq<T::Major, Self::Major>,
typenum::LeEq<T::Minor, Self::Minor>,
>,
Output: SameOrUnit
>
> {
type IsAtLeast = typenum::Or<
typenum::Le<T::Major, Self::Major>,
typenum::And<
typenum::Eq<T::Major, Self::Major>,
typenum::LeEq<T::Minor, Self::Minor>,
>
>;
}
/// Simple check for testing if the `TEST` version is at least `REQ` or higher.
type VersionAbove<REQ, TEST> = <TEST as ComparableVersion<REQ>>::IsAtLeast;
trait VersionIsAtLeast<VER: Version>{}
impl<VER: Version, T: ComparableVersion<VER, IsAtLeast = typenum::True>> VersionIsAtLeast<VER> 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<T, REQUIRED: Version, VER: Version + ComparableVersion<REQUIRED>>{
_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<T>;
}
impl SameOrUnit for typenum::True{
type Output<T> = T;
}
impl SameOrUnit for typenum::False{
type Output<T> = ();
}
impl<T, REQUIRED: Version, VER: Version + ComparableVersion<REQUIRED>> CondElemResult for MinVersionElementHelper<T, REQUIRED, VER> where {
type Output = <<VER as ComparableVersion<REQUIRED>>::IsAtLeast as SameOrUnit>::Output<T>;
}
/// 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<T, REQUIRED, VER> = <MinVersionElementHelper<T, REQUIRED, VER> as CondElemResult>::Output;