From d18ba43aed62974d4a2949865b1ceef034350183 Mon Sep 17 00:00:00 2001 From: DJMrTV Date: Sun, 2 Feb 2025 20:25:22 +0100 Subject: [PATCH] feat(auth): finish protocol 10 method 2 and 3 --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/grpc/account.rs | 17 +-- src/grpc/mod.rs | 3 - src/kerberos/mod.rs | 111 ++++++++++++++++++++ src/main.rs | 13 ++- src/nex/account.rs | 28 ++++- src/protocols/auth/method_login.rs | 9 +- src/protocols/auth/method_login_ex.rs | 43 ++++++-- src/protocols/auth/method_request_ticket.rs | 53 ++++++++++ src/protocols/auth/mod.rs | 30 +++++- src/protocols/auth/ticket_generation.rs | 19 ++++ src/protocols/mod.rs | 3 +- src/prudp/packet.rs | 9 +- src/prudp/socket.rs | 3 +- src/rmc/message.rs | 8 ++ src/rmc/response.rs | 9 ++ src/rmc/structures/buffer.rs | 44 ++++++++ src/rmc/structures/connection_data.rs | 27 +++++ src/rmc/structures/list.rs | 21 ++++ src/rmc/structures/mod.rs | 5 + src/rmc/structures/qresult.rs | 37 +++++++ src/rmc/structures/rmc_struct.rs | 24 +++++ src/rmc/structures/string.rs | 9 ++ 24 files changed, 490 insertions(+), 43 deletions(-) create mode 100644 src/kerberos/mod.rs create mode 100644 src/protocols/auth/method_request_ticket.rs create mode 100644 src/protocols/auth/ticket_generation.rs create mode 100644 src/rmc/structures/buffer.rs create mode 100644 src/rmc/structures/connection_data.rs create mode 100644 src/rmc/structures/list.rs create mode 100644 src/rmc/structures/qresult.rs create mode 100644 src/rmc/structures/rmc_struct.rs diff --git a/Cargo.lock b/Cargo.lock index 7875aab..6a6450c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,6 +549,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -1304,6 +1310,7 @@ dependencies = [ "bytemuck", "chrono", "dotenv", + "hex", "hmac", "log", "md-5", diff --git a/Cargo.toml b/Cargo.toml index e6d7e2e..9daf326 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "net", "s tokio-stream = { version = "0.1.17", features = ["io-util"] } tonic = "0.12.3" prost = "0.13.4" +hex = "0.4.3" [build-dependencies] tonic-build = "0.12.3" \ No newline at end of file diff --git a/src/grpc/account.rs b/src/grpc/account.rs index 5972cc9..a8c1024 100644 --- a/src/grpc/account.rs +++ b/src/grpc/account.rs @@ -1,14 +1,13 @@ use std::{env, result}; -use std::net::{Ipv4Addr, SocketAddrV4}; +use std::array::TryFromSliceError; +use std::net::{Ipv4Addr}; use once_cell::sync::Lazy; use thiserror::Error; -use tonic::codegen::http::uri::InvalidUri; use tonic::metadata::{Ascii, MetadataValue}; -use tonic::{Request, Status, transport}; +use tonic::{Request, transport}; use tonic::codegen::InterceptedService; -use tonic::service::Interceptor; use tonic::transport::Channel; -use crate::grpc::{InterceptorFunc, protobufs}; +use crate::grpc::InterceptorFunc; use crate::grpc::protobufs::account::account_client::AccountClient; use crate::grpc::protobufs::account::GetNexPasswordRequest; @@ -42,7 +41,9 @@ pub enum Error{ #[error(transparent)] Transport(#[from] transport::Error), #[error(transparent)] - Status(#[from] tonic::Status) + Status(#[from] tonic::Status), + #[error("invalid password size: {0}")] + PasswordConversion(#[from] TryFromSliceError) } pub type Result = result::Result; @@ -64,14 +65,14 @@ impl Client{ Ok(Self(client)) } - pub async fn get_nex_password(&mut self , pid: u32) -> Result>{ + pub async fn get_nex_password(&mut self , pid: u32) -> Result<[u8; 16]>{ let req = Request::new(GetNexPasswordRequest{ pid }); let response = self.0.get_nex_password(req).await?.into_inner(); - Ok(response.password.into_boxed_str()) + Ok(response.password.as_bytes().try_into()?) } } diff --git a/src/grpc/mod.rs b/src/grpc/mod.rs index 30e893e..d1a4342 100644 --- a/src/grpc/mod.rs +++ b/src/grpc/mod.rs @@ -1,6 +1,3 @@ -use std::env; -use std::net::Ipv4Addr; -use once_cell::sync::Lazy; use tonic::{Request, Status}; type InterceptorFunc = Box<(dyn Fn(Request<()>) -> Result, Status> + Send)>; diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs new file mode 100644 index 0000000..cf5cec3 --- /dev/null +++ b/src/kerberos/mod.rs @@ -0,0 +1,111 @@ +use std::io::{Read, Write}; +use bytemuck::{bytes_of, Pod, Zeroable}; +use chrono::{Datelike, Timelike}; +use hmac::Hmac; +use md5::{Digest, Md5}; +use rc4::{Rc4, Rc4Core, StreamCipher}; +use rc4::cipher::StreamCipherCoreWrapper; +use rc4::consts::U16; +use hmac::Mac; +use rc4::KeyInit; +use crate::rmc::structures::RmcSerialize; + +type Md5Hmac = Hmac; + +pub fn derive_key(pid: u32, password: [u8; 16]) -> [u8; 16]{ + let iteration_count = 65000 + pid%1024; + + let mut key = password; + + for _ in 0..iteration_count { + let mut md5 = Md5::new(); + md5.update(key); + key = md5.finalize().try_into().unwrap(); + } + + key +} +#[derive(Pod, Zeroable, Copy, Clone)] +#[repr(transparent)] +pub struct KerberosDateTime(u64); + +impl KerberosDateTime{ + pub fn new(second: u64, minute: u64, hour: u64, day: u64, month: u64, year:u64 ) -> Self { + Self(second | (minute << 6) | (hour << 12) | (day << 17) | (month << 22) | (year << 26)) + } + + pub fn now() -> Self{ + let now = chrono::Utc::now(); + Self::new( + now.second() as u64, + now.minute() as u64, + now.hour() as u64, + now.day() as u64, + now.month() as u64, + now.year() as u64, + ) + } +} + +#[derive(Pod, Zeroable, Copy, Clone)] +#[repr(C, packed)] +pub struct TicketInternalData{ + issued_time: KerberosDateTime, + pub pid: u32, + pub session_key: [u8; 32], +} + +impl TicketInternalData{ + pub(crate) fn new(pid: u32) -> Self{ + Self{ + issued_time: KerberosDateTime::now(), + pid, + session_key: rand::random() + } + } + + pub(crate) fn encrypt(&self, key: [u8; 16]) -> Box<[u8]>{ + let mut data = bytes_of(self).to_vec(); + + let mut rc4: StreamCipherCoreWrapper> = Rc4::new_from_slice(&key).unwrap(); + rc4.apply_keystream(&mut data); + + let mut hmac = ::new_from_slice(&key).unwrap(); + + hmac.write_all(&data[..]).expect("failed to write data to hmac"); + + let hmac_result = &hmac.finalize().into_bytes()[..]; + + data.write_all(&hmac_result).expect("failed to write data to vec"); + + data.into_boxed_slice() + } +} + +#[derive(Pod, Zeroable, Copy, Clone)] +#[repr(C, packed)] +pub struct Ticket{ + pub session_key: [u8; 32], + pub pid: u32, +} + +impl Ticket{ + pub(crate) fn encrypt(&self, key: [u8; 16], internal_data: &[u8]) -> Box<[u8]>{ + let mut data = bytes_of(self).to_vec(); + + internal_data.serialize(&mut data).expect("unable to write to vec"); + + let mut rc4: StreamCipherCoreWrapper> = Rc4::new_from_slice(&key).unwrap(); + rc4.apply_keystream(&mut data); + + let mut hmac = ::new_from_slice(&key).unwrap(); + + hmac.write_all(&data[..]).expect("failed to write data to hmac"); + + let hmac_result = &hmac.finalize().into_bytes()[..]; + + data.write_all(&hmac_result).expect("failed to write data to vec"); + + data.into_boxed_slice() + } +} diff --git a/src/main.rs b/src/main.rs index 0013687..999f9aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use rc4::consts::U5; use simplelog::{ColorChoice, CombinedLogger, Config, LevelFilter, TerminalMode, TermLogger, WriteLogger}; use crate::nex::account::Account; use crate::protocols::auth; +use crate::protocols::auth::AuthProtocolConfig; use crate::protocols::server::RMCProtocolServer; use crate::prudp::socket::Socket; use crate::prudp::packet::{PRUDPPacket, VirtualPort}; @@ -24,9 +25,10 @@ mod protocols; mod nex; mod grpc; +mod kerberos; static KERBEROS_SERVER_PASSWORD: Lazy = Lazy::new(||{ - env::var("AUTH_SERVER_PORT") + env::var("AUTH_SERVER_PASSWORD") .ok() .unwrap_or("password".to_owned()) }); @@ -80,8 +82,15 @@ async fn start_servers(){ info!("setting up endpoints"); // dont assign it to the name _ as that will make it drop right here and now + + let auth_protocol_config = AuthProtocolConfig{ + secure_server_account: &SECURE_SERVER_ACCOUNT, + build_name: "branch:origin/project/wup-agmj build:3_8_15_2004_0", + station_url: "prudps:/PID=2;sid=1;stream=10;type=2;address=31.220.75.208;port=10001;CID=1" + }; + let rmcserver = RMCProtocolServer::new(Box::new([ - Box::new(auth::bound_protocol(&SECURE_SERVER_ACCOUNT)) + Box::new(auth::bound_protocol(auth_protocol_config)) ])); let mut _socket = diff --git a/src/nex/account.rs b/src/nex/account.rs index 8dc94f7..f18dbe8 100644 --- a/src/nex/account.rs +++ b/src/nex/account.rs @@ -1,18 +1,38 @@ pub struct Account{ - pid: u32, - username: Box, - kerbros_password: Box, + pub pid: u32, + pub username: Box, + pub kerbros_password: [u8; 16], } impl Account{ pub fn new(pid: u32, username: &str, passwd: &str) -> Self{ + let passwd_data = passwd.as_bytes(); + + let mut passwd = [0u8; 16]; + + for (idx, byte) in passwd_data.iter().enumerate(){ + passwd[idx] = *byte; + } + Self{ - kerbros_password: passwd.into(), + kerbros_password: passwd, username: username.into(), pid } } + + pub fn new_raw_password(pid: u32, username: &str, passwd: [u8; 16]) -> Self{ + Self{ + kerbros_password: passwd, + username: username.into(), + pid + } + } + + pub fn get_login_data(&self) -> (u32, [u8; 16]){ + (self.pid, self.kerbros_password) + } } \ No newline at end of file diff --git a/src/protocols/auth/method_login.rs b/src/protocols/auth/method_login.rs index 37bb635..31959b3 100644 --- a/src/protocols/auth/method_login.rs +++ b/src/protocols/auth/method_login.rs @@ -1,17 +1,18 @@ use std::io::Cursor; use log::error; use crate::nex::account::Account; -use crate::protocols::auth::method_login_ex::login_ex; +use crate::protocols::auth::AuthProtocolConfig; use crate::rmc::message::RMCMessage; use crate::rmc::response::{ErrorCode, RMCResponseResult}; -use crate::rmc::structures::any::Any; use crate::rmc::structures::RmcSerialize; -pub async fn login(rmcmessage: &RMCMessage, name: &str) -> RMCResponseResult{ +pub async fn login(rmcmessage: &RMCMessage, _name: &str) -> RMCResponseResult{ + + rmcmessage.error_result_with_code(ErrorCode::Core_NotImplemented) } -pub async fn login_raw_params(rmcmessage: &RMCMessage, data: (&Account)) -> RMCResponseResult{ +pub async fn login_raw_params(rmcmessage: &RMCMessage, data: AuthProtocolConfig) -> RMCResponseResult{ let mut reader = Cursor::new(&rmcmessage.rest_of_data); let Ok(str) = String::deserialize(&mut reader) else { diff --git a/src/protocols/auth/method_login_ex.rs b/src/protocols/auth/method_login_ex.rs index 0fcc8da..cc80dce 100644 --- a/src/protocols/auth/method_login_ex.rs +++ b/src/protocols/auth/method_login_ex.rs @@ -1,13 +1,20 @@ -use std::io::Cursor; -use log::{error, info}; +use std::io::{Cursor, Write}; +use bytemuck::bytes_of; +use hex::encode; +use log::{error}; use crate::grpc::account; +use crate::kerberos::KerberosDateTime; use crate::nex::account::Account; +use crate::protocols::auth::AuthProtocolConfig; +use crate::protocols::auth::ticket_generation::generate_ticket; use crate::rmc::message::RMCMessage; use crate::rmc::response::{ErrorCode, RMCResponseResult}; use crate::rmc::structures::{RmcSerialize}; use crate::rmc::structures::any::Any; +use crate::rmc::structures::connection_data::ConnectionData; +use crate::rmc::structures::qresult::QResult; -pub async fn login_ex(rmcmessage: &RMCMessage, secure_server_account: &Account , pid: u32) -> RMCResponseResult{ +pub async fn login_ex(rmcmessage: &RMCMessage, proto_data: AuthProtocolConfig, pid: u32) -> RMCResponseResult{ // todo: figure out how the AuthenticationInfo struct works, parse it and validate login info let Ok(mut client) = account::Client::new().await else { @@ -18,15 +25,35 @@ pub async fn login_ex(rmcmessage: &RMCMessage, secure_server_account: &Account , return rmcmessage.error_result_with_code(ErrorCode::Core_Exception); }; - + let source_login_data = (pid, passwd); + let destination_login_data = proto_data.secure_server_account.get_login_data(); - return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument); + let ticket = generate_ticket(source_login_data, destination_login_data); + + let result = QResult::success(ErrorCode::Core_Unknown); + + let connection_data = ConnectionData{ + station_url: proto_data.station_url, + special_station_url: "", + date_time: KerberosDateTime::now(), + special_protocols: Vec::new() + }; + + let mut response: Vec = Vec::new(); + + result.serialize(&mut response).expect("failed serializing result"); + response.write_all(bytes_of(&source_login_data.0)).expect("failed writing pid"); + ticket.serialize(&mut response).expect("failed serializing ticket"); + connection_data.serialize(&mut response).expect("failed writing connection data"); + proto_data.build_name.serialize(&mut response).expect("failed writing build name"); + + return rmcmessage.success_with_data(response); } -pub async fn login_ex_raw_params(rmcmessage: &RMCMessage, (secure_server_account): (&Account)) -> RMCResponseResult{ +pub async fn login_ex_raw_params(rmcmessage: &RMCMessage, data: AuthProtocolConfig) -> RMCResponseResult{ let mut reader = Cursor::new(&rmcmessage.rest_of_data); - let Ok(_str) = String::deserialize(&mut reader) else { + let Ok(str) = String::deserialize(&mut reader) else { error!("error reading packet"); return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument); }; @@ -50,5 +77,5 @@ pub async fn login_ex_raw_params(rmcmessage: &RMCMessage, (secure_server_account return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument); }; - login_ex(rmcmessage, secure_server_account, pid).await + login_ex(rmcmessage, data, pid).await } \ No newline at end of file diff --git a/src/protocols/auth/method_request_ticket.rs b/src/protocols/auth/method_request_ticket.rs new file mode 100644 index 0000000..439eaa0 --- /dev/null +++ b/src/protocols/auth/method_request_ticket.rs @@ -0,0 +1,53 @@ +use std::io::Cursor; +use log::error; +use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions}; +use crate::grpc::account; +use crate::protocols::auth::{AuthProtocolConfig, get_login_data_by_pid}; +use crate::protocols::auth::method_login_ex::login_ex; +use crate::protocols::auth::ticket_generation::generate_ticket; +use crate::rmc::message::RMCMessage; +use crate::rmc::response::{ErrorCode, RMCResponseResult}; +use crate::rmc::response::ErrorCode::Core_Unknown; +use crate::rmc::structures::any::Any; +use crate::rmc::structures::qresult::QResult; +use crate::rmc::structures::RmcSerialize; + +pub async fn request_ticket(rmcmessage: &RMCMessage, data: AuthProtocolConfig, source_pid: u32, destination_pid: u32) -> RMCResponseResult{ + let Some(source_login_data) = get_login_data_by_pid(source_pid).await else { + return rmcmessage.error_result_with_code(ErrorCode::Core_Exception); + }; + + let desgination_login_data = if destination_pid == data.secure_server_account.pid{ + data.secure_server_account.get_login_data() + } else { + let Some(login) = get_login_data_by_pid(destination_pid).await else { + return rmcmessage.error_result_with_code(ErrorCode::Core_Exception); + }; + login + }; + + let result = QResult::success(Core_Unknown); + + let ticket = generate_ticket(source_login_data, desgination_login_data); + + let mut response: Vec = Vec::new(); + + result.serialize(&mut response).expect("failed serializing result"); + ticket.serialize(&mut response).expect("failed serializing ticket"); + + rmcmessage.success_with_data(response) +} + +pub async fn request_ticket_raw_params(rmcmessage: &RMCMessage, data: AuthProtocolConfig) -> RMCResponseResult{ + let mut reader = Cursor::new(&rmcmessage.rest_of_data); + + let Ok(source_pid) = reader.read_struct(IS_BIG_ENDIAN) else { + return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument); + }; + + let Ok(destination_pid) = reader.read_struct(IS_BIG_ENDIAN) else { + return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument); + }; + + request_ticket(rmcmessage, data, source_pid, destination_pid).await +} \ No newline at end of file diff --git a/src/protocols/auth/mod.rs b/src/protocols/auth/mod.rs index eab460c..9f54532 100644 --- a/src/protocols/auth/mod.rs +++ b/src/protocols/auth/mod.rs @@ -1,18 +1,42 @@ mod method_login_ex; mod method_login; +mod ticket_generation; +mod method_request_ticket; +use std::sync::Arc; use log::{error}; use crate::define_protocol; +use crate::grpc::account; use crate::nex::account::Account; use crate::protocols::auth::method_login::login_raw_params; -use crate::protocols::auth::method_login_ex::{login_ex, login_ex_raw_params}; +use crate::protocols::auth::method_login_ex::login_ex_raw_params; +use crate::protocols::auth::method_request_ticket::request_ticket_raw_params; use crate::rmc::message::RMCMessage; use crate::rmc::response::{ErrorCode, RMCResponse}; +#[derive(Copy, Clone)] +pub struct AuthProtocolConfig { + pub secure_server_account: &'static Account, + pub build_name: &'static str, + pub station_url: &'static str +} define_protocol!{ - 10(secure_server_account: &'static Account) => { + 10(proto_data: AuthProtocolConfig) => { 0x01 => login_raw_params, - 0x02 => login_ex_raw_params + 0x02 => login_ex_raw_params, + 0x03 => request_ticket_raw_params } } + +async fn get_login_data_by_pid(pid: u32) -> Option<(u32, [u8; 16])> { + let Ok(mut client) = account::Client::new().await else { + return None + }; + + let Ok(passwd) = client.get_nex_password(pid).await else{ + return None + }; + + Some((pid, passwd)) +} \ No newline at end of file diff --git a/src/protocols/auth/ticket_generation.rs b/src/protocols/auth/ticket_generation.rs new file mode 100644 index 0000000..f2219de --- /dev/null +++ b/src/protocols/auth/ticket_generation.rs @@ -0,0 +1,19 @@ +use crate::kerberos; +use crate::kerberos::{derive_key, Ticket}; + + +pub fn generate_ticket(source_act_login_data: (u32, [u8;16]), dest_act_login_data: (u32, [u8;16])) -> Box<[u8]>{ + let source_key = derive_key(source_act_login_data.0, source_act_login_data.1); + let dest_key = derive_key(dest_act_login_data.0, dest_act_login_data.1); + + let internal_data = kerberos::TicketInternalData::new(source_act_login_data.0); + + let encrypted_inner = internal_data.encrypt(dest_key); + let encrypted_session_ticket = Ticket{ + pid: dest_act_login_data.0, + session_key: internal_data.session_key, + }.encrypt(source_key, &encrypted_inner); + + + encrypted_session_ticket +} \ No newline at end of file diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 437eb7f..53b363d 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -3,6 +3,7 @@ pub mod server; #[macro_export] macro_rules! define_protocol { ($id:literal ($($varname:ident : $ty:ty),*) => {$($func_id:literal => $func:path),*} ) => { + #[allow(unused_parens)] async fn protocol (rmcmessage: &RMCMessage, $($varname : $ty),*) -> Option{ if rmcmessage.protocol_id != $id{ return None; @@ -30,7 +31,7 @@ macro_rules! define_protocol { response_result }) } - + #[allow(unused_parens)] pub fn bound_protocol($($varname : $ty,)*) -> Box Fn(&'message_lifetime RMCMessage) -> ::std::pin::Pin> + Send + 'message_lifetime>> + Send + Sync>{ Box::new( move |v| { diff --git a/src/prudp/packet.rs b/src/prudp/packet.rs index 8abaf26..4f9b28c 100644 --- a/src/prudp/packet.rs +++ b/src/prudp/packet.rs @@ -12,7 +12,7 @@ use hmac::{Hmac, Mac}; use log::{error, trace, warn}; use md5::{Md5, Digest}; use thiserror::Error; -use v_byte_macros::{EnumTryInto, SwapEndian}; +use v_byte_macros::{SwapEndian}; use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions}; use crate::prudp::packet::flags::ACK; use crate::prudp::packet::PacketOption::{ConnectionSignature, FragmentId, InitialSequenceId, MaximumSubstreamId, SupportedFunctions}; @@ -173,13 +173,6 @@ impl Default for PRUDPHeader{ } -#[derive(EnumTryInto)] - -#[repr(u16)] -enum PacketSpecificData { - E = 0x10 -} - #[derive(Debug, Clone)] pub enum PacketOption{ SupportedFunctions(u32), diff --git a/src/prudp/socket.rs b/src/prudp/socket.rs index e677c5d..63e1d5b 100644 --- a/src/prudp/socket.rs +++ b/src/prudp/socket.rs @@ -42,7 +42,6 @@ pub struct ActiveConnectionData { pub reliable_client_counter: u16, pub reliable_server_counter: u16, pub reliable_client_queue: VecDeque, - pub connection_data_channel: Sender>, server_encryption: Box, client_decryption: Box, pub server_session_id: u8, @@ -363,7 +362,7 @@ impl SocketData { self.socket.send_to(&vec, client_address.regular_socket_addr).await.expect("failed to send data back"); } } - + 3 => {} _ => unimplemented!("unimplemented packet type: {}", packet.header.types_and_flags.get_types()) } } diff --git a/src/rmc/message.rs b/src/rmc/message.rs index e7b6f8f..7607672 100644 --- a/src/rmc/message.rs +++ b/src/rmc/message.rs @@ -58,4 +58,12 @@ impl RMCMessage{ error_code } } + + pub fn success_with_data(&self, data: Vec) -> RMCResponseResult{ + RMCResponseResult::Success { + call_id: self.call_id, + method_id: self.method_id, + data + } + } } \ No newline at end of file diff --git a/src/rmc/response.rs b/src/rmc/response.rs index a274479..30ec03f 100644 --- a/src/rmc/response.rs +++ b/src/rmc/response.rs @@ -8,6 +8,8 @@ use crate::prudp::packet::PacketOption::FragmentId; use crate::prudp::packet::types::DATA; use crate::prudp::socket::{ConnectionData, SocketData}; + + pub enum RMCResponseResult { Success{ call_id: u32, @@ -389,6 +391,7 @@ mod test{ use hmac::digest::consts::U5; use hmac::digest::KeyInit; use rc4::{Rc4, StreamCipher}; + use crate::rmc::response::ErrorCode; #[test] fn test(){ @@ -410,4 +413,10 @@ mod test{ assert_eq!(data_orig, data); } + + #[test] + fn test_enum_equivilance(){ + let val: u32 = ErrorCode::Core_Unknown.into(); + assert_eq!(val, 0x00010001) + } } \ No newline at end of file diff --git a/src/rmc/structures/buffer.rs b/src/rmc/structures/buffer.rs new file mode 100644 index 0000000..9f58556 --- /dev/null +++ b/src/rmc/structures/buffer.rs @@ -0,0 +1,44 @@ +use std::io::{Read, Write}; +use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions}; +use crate::rmc::structures::RmcSerialize; + +impl<'a> RmcSerialize for &'a [u8]{ + fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> { + let u32_size = self.len() as u32; + writer.write(bytemuck::bytes_of(&u32_size))?; + writer.write(self)?; + + Ok(()) + } + + /// DO NOT USE (also maybe split off the serialize and deserialize functions at some point) + fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result { + panic!("cannot deserialize to a u8 slice reference (use this ONLY for writing)") + } +} + +impl<'a> RmcSerialize for Vec{ + fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> { + (&self[..]).serialize(writer) + } + + fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result { + let len: u32 = reader.read_struct(IS_BIG_ENDIAN)?; + + let mut data = vec![0; len as usize]; + + reader.read_exact(&mut data)?; + + Ok(data) + } +} + +impl<'a> RmcSerialize for Box<[u8]>{ + fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> { + (&self[..]).serialize(writer) + } + + fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result { + Vec::deserialize(reader).map(|v| v.into_boxed_slice()) + } +} \ No newline at end of file diff --git a/src/rmc/structures/connection_data.rs b/src/rmc/structures/connection_data.rs new file mode 100644 index 0000000..a0fc79a --- /dev/null +++ b/src/rmc/structures/connection_data.rs @@ -0,0 +1,27 @@ +use std::io::{Read, Write}; +use bytemuck::bytes_of; +use crate::kerberos::KerberosDateTime; +use crate::rmc::structures::{rmc_struct, RmcSerialize}; + +pub struct ConnectionData<'a>{ + pub station_url: &'a str, + pub special_protocols: Vec, + pub special_station_url: &'a str, + pub date_time: KerberosDateTime +} + +impl<'a> RmcSerialize for ConnectionData<'a>{ + fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> { + rmc_struct::write_struct(writer, 1, |mut v|{ + self.station_url.serialize(v).expect("unable to write station url"); + self.special_protocols.serialize(v).expect("unable to write special protocols"); + self.special_station_url.serialize(v).expect("unable to write special station url"); + v.write_all(bytes_of(&self.date_time)).expect("unable to write date time"); + }) + } + + fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result { + todo!() + } +} + diff --git a/src/rmc/structures/list.rs b/src/rmc/structures/list.rs new file mode 100644 index 0000000..d47f567 --- /dev/null +++ b/src/rmc/structures/list.rs @@ -0,0 +1,21 @@ +use std::io::{Read, Write}; +use bytemuck::bytes_of; +use crate::rmc::structures::RmcSerialize; + + +impl RmcSerialize for Vec{ + fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> { + let u32_len = self.len(); + + writer.write_all(bytes_of(&u32_len))?; + for e in self{ + e.serialize(writer)?; + } + + Ok(()) + } + + fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result { + todo!() + } +} diff --git a/src/rmc/structures/mod.rs b/src/rmc/structures/mod.rs index 0de253f..1718ef1 100644 --- a/src/rmc/structures/mod.rs +++ b/src/rmc/structures/mod.rs @@ -17,6 +17,11 @@ type Result = std::result::Result; pub mod string; pub mod any; +pub mod qresult; +pub mod buffer; +pub mod connection_data; +pub mod rmc_struct; +pub mod list; pub trait RmcSerialize: Sized{ fn serialize(&self, writer: &mut dyn Write) -> Result<()>; diff --git a/src/rmc/structures/qresult.rs b/src/rmc/structures/qresult.rs new file mode 100644 index 0000000..6a16074 --- /dev/null +++ b/src/rmc/structures/qresult.rs @@ -0,0 +1,37 @@ +use std::io::{Read, Write}; +use bytemuck::{bytes_of, Pod, Zeroable}; +use v_byte_macros::SwapEndian; +use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions}; +use crate::rmc::response::ErrorCode; +use crate::rmc::structures::{RmcSerialize, Result}; + +const ERROR_MASK: u32 = 1 << 31; + +#[derive(Pod, Zeroable, Copy, Clone, SwapEndian)] +#[repr(transparent)] +pub struct QResult(u32); + +impl QResult{ + pub fn success(error_code: ErrorCode) -> Self{ + let val: u32 = error_code.into(); + + Self(val & (!ERROR_MASK)) + } + + pub fn error(error_code: ErrorCode) -> Self{ + let val: u32 = error_code.into(); + + Self(val | ERROR_MASK) + } +} + +impl RmcSerialize for QResult{ + fn serialize(&self, writer: &mut dyn Write) -> Result<()> { + writer.write(bytes_of(self))?; + Ok(()) + } + + fn deserialize(mut reader: &mut dyn Read) -> Result { + Ok(reader.read_struct(IS_BIG_ENDIAN)?) + } +} \ No newline at end of file diff --git a/src/rmc/structures/rmc_struct.rs b/src/rmc/structures/rmc_struct.rs new file mode 100644 index 0000000..a665a9a --- /dev/null +++ b/src/rmc/structures/rmc_struct.rs @@ -0,0 +1,24 @@ +use std::io::Write; +use bytemuck::bytes_of; +use crate::rmc::structures::Result; + +#[repr(C, packed)] +struct StructureHeader{ + version: u8, + length: u32 +} + +pub fn write_struct(mut writer: &mut dyn Write, version: u8, pred: impl Fn(&mut Vec)) -> Result<()> { + writer.write_all(&[version])?; + + let mut scratch_space: Vec = Vec::new(); + + (pred)(&mut scratch_space); + + let u32_size= scratch_space.len() as u32; + + writer.write_all(bytes_of(&u32_size))?; + writer.write_all(&scratch_space)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/rmc/structures/string.rs b/src/rmc/structures/string.rs index b58d870..afaeb03 100644 --- a/src/rmc/structures/string.rs +++ b/src/rmc/structures/string.rs @@ -17,6 +17,15 @@ impl RmcSerialize for String{ Ok(String::from_utf8(data)?) } + fn serialize(&self, writer: &mut dyn Write) -> Result<()> { + (&self[..]).serialize(writer) + } +} + +impl RmcSerialize for &str{ + fn deserialize(mut reader: &mut dyn Read) -> Result { + panic!("cannot serialize to &str") + } fn serialize(&self, writer: &mut dyn Write) -> Result<()> { let u16_len: u16 = self.len() as u16; writer.write(bytes_of(&u16_len))?;