feat(secure): a bunch of stuff
This commit is contained in:
parent
4f26aae1d7
commit
81f7a0a738
15 changed files with 480 additions and 49 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||||
use chrono::{Datelike, Timelike};
|
use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
|
||||||
use hmac::Hmac;
|
use hmac::Hmac;
|
||||||
use md5::{Digest, Md5};
|
use md5::{Digest, Md5};
|
||||||
use rc4::{Rc4, Rc4Core, StreamCipher};
|
use rc4::{Rc4, Rc4Core, StreamCipher};
|
||||||
|
|
@ -45,12 +45,47 @@ impl KerberosDateTime{
|
||||||
now.year() as u64,
|
now.year() as u64,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_seconds(&self) -> u8{
|
||||||
|
(self.0 & 0b111111) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_minutes(&self) -> u8{
|
||||||
|
((self.0 >> 6) & 0b111111) as u8
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn get_hours(&self) -> u8{
|
||||||
|
((self.0 >> 12) & 0b111111) as u8
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn get_days(&self) -> u8{
|
||||||
|
((self.0 >> 17) & 0b111111) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_month(&self) -> u8{
|
||||||
|
((self.0 >> 22) & 0b111111) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_year(&self) -> u64{
|
||||||
|
((self.0 >> 26) & 0xFFFFFFFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_regular_time(&self) -> chrono::DateTime<Utc>{
|
||||||
|
NaiveDateTime::new(
|
||||||
|
NaiveDate::from_ymd_opt(self.get_year() as i32, self.get_month() as u32, self.get_days() as u32).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(self.get_hours() as u32, self.get_minutes() as u32, self.get_seconds() as u32).unwrap()
|
||||||
|
).and_utc()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Pod, Zeroable, Copy, Clone)]
|
#[derive(Pod, Zeroable, Copy, Clone)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct TicketInternalData{
|
pub struct TicketInternalData{
|
||||||
issued_time: KerberosDateTime,
|
pub issued_time: KerberosDateTime,
|
||||||
pub pid: u32,
|
pub pid: u32,
|
||||||
pub session_key: [u8; 32],
|
pub session_key: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/main.rs
25
src/main.rs
|
|
@ -15,10 +15,12 @@ use crate::nex::account::Account;
|
||||||
use crate::protocols::auth;
|
use crate::protocols::auth;
|
||||||
use crate::protocols::auth::AuthProtocolConfig;
|
use crate::protocols::auth::AuthProtocolConfig;
|
||||||
use crate::protocols::server::RMCProtocolServer;
|
use crate::protocols::server::RMCProtocolServer;
|
||||||
use crate::prudp::socket::{EncryptionPair, Socket};
|
use crate::prudp::socket::{ActiveSecureConnectionData, EncryptionPair, Socket};
|
||||||
use crate::prudp::packet::{PRUDPPacket, VirtualPort};
|
use crate::prudp::packet::{PRUDPPacket, VirtualPort};
|
||||||
use crate::prudp::router::Router;
|
use crate::prudp::router::Router;
|
||||||
|
use crate::prudp::secure::{generate_secure_encryption_pairs, read_secure_connection_data};
|
||||||
use crate::rmc::message::RMCMessage;
|
use crate::rmc::message::RMCMessage;
|
||||||
|
use crate::rmc::structures::RmcSerialize;
|
||||||
|
|
||||||
mod endianness;
|
mod endianness;
|
||||||
mod prudp;
|
mod prudp;
|
||||||
|
|
@ -164,7 +166,9 @@ async fn start_secure_server() -> SecureServer{
|
||||||
|
|
||||||
info!("setting up endpoints");
|
info!("setting up endpoints");
|
||||||
|
|
||||||
let rmcserver = RMCProtocolServer::new(Box::new([]));
|
let rmcserver = RMCProtocolServer::new(Box::new([
|
||||||
|
Box::new(protocols::secure::bound_protocol())
|
||||||
|
]));
|
||||||
|
|
||||||
let mut socket =
|
let mut socket =
|
||||||
Socket::new(
|
Socket::new(
|
||||||
|
|
@ -174,9 +178,24 @@ async fn start_secure_server() -> SecureServer{
|
||||||
Box::new(|p, count|{
|
Box::new(|p, count|{
|
||||||
Box::pin(
|
Box::pin(
|
||||||
async move {
|
async move {
|
||||||
|
let (session_key, pid, check_value) = read_secure_connection_data(&p.payload, &SECURE_SERVER_ACCOUNT)?;
|
||||||
|
|
||||||
|
let check_value_response = check_value + 1;
|
||||||
|
|
||||||
Some((Vec::new(), Vec::new(), None))
|
let data = bytemuck::bytes_of(&check_value_response);
|
||||||
|
|
||||||
|
let mut response = Vec::new();
|
||||||
|
|
||||||
|
data.serialize(&mut response).ok()?;
|
||||||
|
|
||||||
|
let encryption_pairs = generate_secure_encryption_pairs(session_key, count);
|
||||||
|
|
||||||
|
Some((response, encryption_pairs, Some(
|
||||||
|
ActiveSecureConnectionData{
|
||||||
|
pid,
|
||||||
|
session_key
|
||||||
|
}
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::io::Cursor;
|
||||||
use log::error;
|
use log::error;
|
||||||
use crate::nex::account::Account;
|
use crate::nex::account::Account;
|
||||||
use crate::protocols::auth::AuthProtocolConfig;
|
use crate::protocols::auth::AuthProtocolConfig;
|
||||||
|
use crate::prudp::socket::ConnectionData;
|
||||||
use crate::rmc::message::RMCMessage;
|
use crate::rmc::message::RMCMessage;
|
||||||
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
||||||
use crate::rmc::structures::RmcSerialize;
|
use crate::rmc::structures::RmcSerialize;
|
||||||
|
|
@ -12,7 +13,7 @@ pub async fn login(rmcmessage: &RMCMessage, _name: &str) -> RMCResponseResult{
|
||||||
rmcmessage.error_result_with_code(ErrorCode::Core_NotImplemented)
|
rmcmessage.error_result_with_code(ErrorCode::Core_NotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login_raw_params(rmcmessage: &RMCMessage, data: AuthProtocolConfig) -> RMCResponseResult{
|
pub async fn login_raw_params(rmcmessage: &RMCMessage, _: &mut ConnectionData, data: AuthProtocolConfig) -> RMCResponseResult{
|
||||||
let mut reader = Cursor::new(&rmcmessage.rest_of_data);
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@ use crate::kerberos::KerberosDateTime;
|
||||||
use crate::nex::account::Account;
|
use crate::nex::account::Account;
|
||||||
use crate::protocols::auth::AuthProtocolConfig;
|
use crate::protocols::auth::AuthProtocolConfig;
|
||||||
use crate::protocols::auth::ticket_generation::generate_ticket;
|
use crate::protocols::auth::ticket_generation::generate_ticket;
|
||||||
|
use crate::prudp::socket::ConnectionData;
|
||||||
|
use crate::rmc;
|
||||||
use crate::rmc::message::RMCMessage;
|
use crate::rmc::message::RMCMessage;
|
||||||
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
||||||
use crate::rmc::structures::{RmcSerialize};
|
use crate::rmc::structures::{RmcSerialize};
|
||||||
use crate::rmc::structures::any::Any;
|
use crate::rmc::structures::any::Any;
|
||||||
use crate::rmc::structures::connection_data::ConnectionData;
|
|
||||||
use crate::rmc::structures::qresult::QResult;
|
use crate::rmc::structures::qresult::QResult;
|
||||||
|
|
||||||
pub async fn login_ex(rmcmessage: &RMCMessage, proto_data: AuthProtocolConfig, pid: u32) -> RMCResponseResult{
|
pub async fn login_ex(rmcmessage: &RMCMessage, proto_data: AuthProtocolConfig, pid: u32) -> RMCResponseResult{
|
||||||
|
|
@ -32,7 +33,7 @@ pub async fn login_ex(rmcmessage: &RMCMessage, proto_data: AuthProtocolConfig, p
|
||||||
|
|
||||||
let result = QResult::success(ErrorCode::Core_Unknown);
|
let result = QResult::success(ErrorCode::Core_Unknown);
|
||||||
|
|
||||||
let connection_data = ConnectionData{
|
let connection_data = rmc::structures::connection_data::ConnectionData{
|
||||||
station_url: proto_data.station_url,
|
station_url: proto_data.station_url,
|
||||||
special_station_url: "",
|
special_station_url: "",
|
||||||
date_time: KerberosDateTime::now(),
|
date_time: KerberosDateTime::now(),
|
||||||
|
|
@ -50,7 +51,7 @@ pub async fn login_ex(rmcmessage: &RMCMessage, proto_data: AuthProtocolConfig, p
|
||||||
return rmcmessage.success_with_data(response);
|
return rmcmessage.success_with_data(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login_ex_raw_params(rmcmessage: &RMCMessage, data: AuthProtocolConfig) -> RMCResponseResult{
|
pub async fn login_ex_raw_params(rmcmessage: &RMCMessage, _: &mut ConnectionData, data: AuthProtocolConfig) -> RMCResponseResult{
|
||||||
let mut reader = Cursor::new(&rmcmessage.rest_of_data);
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use crate::grpc::account;
|
||||||
use crate::protocols::auth::{AuthProtocolConfig, get_login_data_by_pid};
|
use crate::protocols::auth::{AuthProtocolConfig, get_login_data_by_pid};
|
||||||
use crate::protocols::auth::method_login_ex::login_ex;
|
use crate::protocols::auth::method_login_ex::login_ex;
|
||||||
use crate::protocols::auth::ticket_generation::generate_ticket;
|
use crate::protocols::auth::ticket_generation::generate_ticket;
|
||||||
|
use crate::prudp::socket::ConnectionData;
|
||||||
use crate::rmc::message::RMCMessage;
|
use crate::rmc::message::RMCMessage;
|
||||||
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
||||||
use crate::rmc::response::ErrorCode::Core_Unknown;
|
use crate::rmc::response::ErrorCode::Core_Unknown;
|
||||||
|
|
@ -38,7 +39,7 @@ pub async fn request_ticket(rmcmessage: &RMCMessage, data: AuthProtocolConfig, s
|
||||||
rmcmessage.success_with_data(response)
|
rmcmessage.success_with_data(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_ticket_raw_params(rmcmessage: &RMCMessage, data: AuthProtocolConfig) -> RMCResponseResult{
|
pub async fn request_ticket_raw_params(rmcmessage: &RMCMessage, _: &mut ConnectionData, data: AuthProtocolConfig) -> RMCResponseResult{
|
||||||
let mut reader = Cursor::new(&rmcmessage.rest_of_data);
|
let mut reader = Cursor::new(&rmcmessage.rest_of_data);
|
||||||
|
|
||||||
let Ok(source_pid) = reader.read_struct(IS_BIG_ENDIAN) else {
|
let Ok(source_pid) = reader.read_struct(IS_BIG_ENDIAN) else {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
|
use crate::prudp::socket::ConnectionData;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub mod secure;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_protocol {
|
macro_rules! define_protocol {
|
||||||
($id:literal ($($varname:ident : $ty:ty),*) => {$($func_id:literal => $func:path),*} ) => {
|
($id:literal ($($varname:ident : $ty:ty),*) => {$($func_id:literal => $func:path),*} ) => {
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
async fn protocol (rmcmessage: &RMCMessage, $($varname : $ty),*) -> Option<RMCResponse>{
|
async fn protocol (rmcmessage: &crate::RMCMessage, connection: &mut crate::protocols::ConnectionData, $($varname : $ty),*) -> Option<crate::rmc::response::RMCResponse>{
|
||||||
if rmcmessage.protocol_id != $id{
|
if rmcmessage.protocol_id != $id{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -13,33 +17,34 @@ macro_rules! define_protocol {
|
||||||
|
|
||||||
let response_result = match rmcmessage.method_id{
|
let response_result = match rmcmessage.method_id{
|
||||||
$(
|
$(
|
||||||
$func_id => $func ( rmcmessage, self_data).await,
|
$func_id => $func ( rmcmessage, connection, self_data).await,
|
||||||
)*
|
)*
|
||||||
_ => {
|
_ => {
|
||||||
error!("invalid method id sent to protocol {}: {:?}", $id, rmcmessage.method_id);
|
log::error!("invalid method id sent to protocol {}: {:?}", $id, rmcmessage.method_id);
|
||||||
return Some(
|
return Some(
|
||||||
RMCResponse{
|
crate::rmc::response::RMCResponse{
|
||||||
protocol_id: $id,
|
protocol_id: $id,
|
||||||
response_result: rmcmessage.error_result_with_code(ErrorCode::Core_NotImplemented)
|
response_result: rmcmessage.error_result_with_code(crate::rmc::response::ErrorCode::Core_NotImplemented)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(RMCResponse{
|
Some(crate::rmc::response::RMCResponse{
|
||||||
protocol_id: $id,
|
protocol_id: $id,
|
||||||
response_result
|
response_result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
pub fn bound_protocol($($varname : $ty,)*) -> Box<dyn for<'message_lifetime> Fn(&'message_lifetime RMCMessage) -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = Option<RMCResponse>> + Send + 'message_lifetime>> + Send + Sync>{
|
pub fn bound_protocol($($varname : $ty,)*) -> Box<dyn for<'message_lifetime> Fn(&'message_lifetime crate::RMCMessage, &'message_lifetime mut crate::protocols::ConnectionData)
|
||||||
|
-> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = Option<crate::rmc::response::RMCResponse>> + Send + 'message_lifetime>> + Send + Sync>{
|
||||||
Box::new(
|
Box::new(
|
||||||
move |v| {
|
move |v, cd| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
$(
|
$(
|
||||||
let $varname = $varname.clone();
|
let $varname = $varname.clone();
|
||||||
)*
|
)*
|
||||||
protocol(v, $($varname,)*).await
|
protocol(v, cd, $($varname,)*).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
65
src/protocols/secure/method_register.rs
Normal file
65
src/protocols/secure/method_register.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use bytemuck::bytes_of;
|
||||||
|
use log::{error, warn};
|
||||||
|
use crate::protocols::auth::AuthProtocolConfig;
|
||||||
|
use crate::prudp::socket::ConnectionData;
|
||||||
|
use crate::prudp::station_url::{nat_types, StationUrl};
|
||||||
|
use crate::prudp::station_url::Type::PRUDPS;
|
||||||
|
use crate::prudp::station_url::UrlOptions::{Address, NatFiltering, NatMapping, NatType, Port, PrincipalID, RVConnectionID};
|
||||||
|
use crate::rmc::message::RMCMessage;
|
||||||
|
use crate::rmc::response::{ErrorCode, RMCResponseResult};
|
||||||
|
use crate::rmc::structures::any::Any;
|
||||||
|
use crate::rmc::structures::qresult::QResult;
|
||||||
|
use crate::rmc::structures::RmcSerialize;
|
||||||
|
|
||||||
|
type StringList = Vec<String>;
|
||||||
|
|
||||||
|
pub async fn register(rmcmessage: &RMCMessage, station_urls: Vec<StationUrl>, conn_data: &mut ConnectionData) -> RMCResponseResult{
|
||||||
|
|
||||||
|
let Some(active_connection_data) = conn_data.active_connection_data.as_ref() else {
|
||||||
|
return rmcmessage.error_result_with_code(ErrorCode::RendezVous_NotAuthenticated)
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(active_secure_connection_data) = active_connection_data.active_secure_connection_data.as_ref() else {
|
||||||
|
return rmcmessage.error_result_with_code(ErrorCode::RendezVous_NotAuthenticated)
|
||||||
|
};
|
||||||
|
|
||||||
|
let public_station = StationUrl{
|
||||||
|
url_type: PRUDPS,
|
||||||
|
options: vec![
|
||||||
|
RVConnectionID(active_connection_data.connection_id),
|
||||||
|
Address(*conn_data.sock_addr.regular_socket_addr.ip()),
|
||||||
|
Port(conn_data.sock_addr.regular_socket_addr.port()),
|
||||||
|
NatFiltering(0),
|
||||||
|
NatMapping(0),
|
||||||
|
NatType(nat_types::BEHIND_NAT),
|
||||||
|
PrincipalID(active_secure_connection_data.pid),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let result = QResult::success(ErrorCode::Core_Unknown);
|
||||||
|
|
||||||
|
let mut response = Vec::new();
|
||||||
|
|
||||||
|
result.serialize(&mut response).expect("unable to serialize result");
|
||||||
|
response.write_all(bytes_of(&active_connection_data.connection_id)).expect("unable to serialize connection id");
|
||||||
|
public_station.to_string().serialize(&mut response).expect("unable to serialize station id");
|
||||||
|
|
||||||
|
rmcmessage.success_with_data(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn register_raw_params(rmcmessage: &RMCMessage, conn_data: &mut ConnectionData, _: ()) -> RMCResponseResult{
|
||||||
|
let mut reader = Cursor::new(&rmcmessage.rest_of_data);
|
||||||
|
|
||||||
|
let Ok(station_urls) = StringList::deserialize(&mut reader) else {
|
||||||
|
return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(station_urls): Result<Vec<StationUrl>, _> = station_urls.iter().map(|c| StationUrl::try_from((&c) as &str)).collect() else {
|
||||||
|
return rmcmessage.error_result_with_code(ErrorCode::Core_InvalidArgument);
|
||||||
|
};
|
||||||
|
|
||||||
|
register(rmcmessage, station_urls, conn_data).await
|
||||||
|
}
|
||||||
10
src/protocols/secure/mod.rs
Normal file
10
src/protocols/secure/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
mod method_register;
|
||||||
|
|
||||||
|
use crate::define_protocol;
|
||||||
|
use crate::protocols::secure::method_register::register_raw_params;
|
||||||
|
|
||||||
|
define_protocol!{
|
||||||
|
11() => {
|
||||||
|
0x01 => register_raw_params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::rmc::message::RMCMessage;
|
||||||
use crate::rmc::response::{RMCResponse, RMCResponseResult, send_response};
|
use crate::rmc::response::{RMCResponse, RMCResponseResult, send_response};
|
||||||
use crate::rmc::response::ErrorCode::Core_NotImplemented;
|
use crate::rmc::response::ErrorCode::Core_NotImplemented;
|
||||||
|
|
||||||
type ContainedProtocolList = Box<[Box<dyn for<'a> Fn(&'a RMCMessage) -> Pin<Box<dyn Future<Output = Option<RMCResponse>> + Send + 'a>> + Send + Sync>]>;
|
type ContainedProtocolList = Box<[Box<dyn for<'a> Fn(&'a RMCMessage, &'a mut ConnectionData) -> Pin<Box<dyn Future<Output = Option<RMCResponse>> + Send + 'a>> + Send + Sync>]>;
|
||||||
|
|
||||||
pub struct RMCProtocolServer(ContainedProtocolList);
|
pub struct RMCProtocolServer(ContainedProtocolList);
|
||||||
|
|
||||||
|
|
@ -27,12 +27,14 @@ impl RMCProtocolServer{
|
||||||
println!("recieved rmc message: {{ protocol: {}, method: {}}}", rmc.protocol_id, rmc.method_id);
|
println!("recieved rmc message: {{ protocol: {}, method: {}}}", rmc.protocol_id, rmc.method_id);
|
||||||
|
|
||||||
for proto in &self.0 {
|
for proto in &self.0 {
|
||||||
if let Some(response) = proto(&rmc).await {
|
if let Some(response) = proto(&rmc, connection).await {
|
||||||
send_response(&packet, &socket, connection, response).await;
|
send_response(&packet, &socket, connection, response).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error!("tried to send message to unimplemented protocol {} with method id {}", rmc.protocol_id, rmc.method_id);
|
||||||
|
|
||||||
send_response(&packet, &socket, connection, RMCResponse{
|
send_response(&packet, &socket, connection, RMCResponse{
|
||||||
protocol_id: rmc.protocol_id as u8,
|
protocol_id: rmc.protocol_id as u8,
|
||||||
response_result: RMCResponseResult::Error {
|
response_result: RMCResponseResult::Error {
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,6 @@ pub mod packet;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod socket;
|
pub mod socket;
|
||||||
mod auth_module;
|
mod auth_module;
|
||||||
mod sockaddr;
|
mod sockaddr;
|
||||||
|
pub mod secure;
|
||||||
|
pub mod station_url;
|
||||||
|
|
@ -365,6 +365,8 @@ impl PRUDPPacket {
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Self{
|
Self{
|
||||||
header: PRUDPHeader{
|
header: PRUDPHeader{
|
||||||
types_and_flags: flags,
|
types_and_flags: flags,
|
||||||
|
|
|
||||||
101
src/prudp/secure.rs
Normal file
101
src/prudp/secure.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
use std::io::Cursor;
|
||||||
|
use hmac::digest::consts::U32;
|
||||||
|
use log::error;
|
||||||
|
use rc4::cipher::StreamCipherCoreWrapper;
|
||||||
|
use rc4::{KeyInit, Rc4, Rc4Core, StreamCipher};
|
||||||
|
use rc4::consts::U16;
|
||||||
|
use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions};
|
||||||
|
use crate::kerberos::{derive_key, TicketInternalData};
|
||||||
|
use crate::nex::account::Account;
|
||||||
|
use crate::prudp::packet::PRUDPHeader;
|
||||||
|
use crate::prudp::socket::EncryptionPair;
|
||||||
|
use crate::rmc::structures::RmcSerialize;
|
||||||
|
|
||||||
|
pub fn read_secure_connection_data(data: &[u8], act: &Account) -> Option<([u8; 32], u32, u32)>{
|
||||||
|
let mut cursor = Cursor::new(data);
|
||||||
|
|
||||||
|
let mut ticket_data: Vec<u8> = Vec::deserialize(&mut cursor).ok()?;
|
||||||
|
let mut request_data: Vec<u8> = Vec::deserialize(&mut cursor).ok()?;
|
||||||
|
|
||||||
|
let ticket_data_size = ticket_data.len();
|
||||||
|
|
||||||
|
let ticket_data = &mut ticket_data[0..ticket_data_size-0x10];
|
||||||
|
|
||||||
|
let server_key = derive_key(act.pid, act.kerbros_password);
|
||||||
|
|
||||||
|
let mut rc4: StreamCipherCoreWrapper<Rc4Core<U16>> =
|
||||||
|
Rc4::new_from_slice(&server_key).expect("unable to init rc4 keystream");
|
||||||
|
|
||||||
|
rc4.apply_keystream(ticket_data);
|
||||||
|
|
||||||
|
let ticket_data: &TicketInternalData = match bytemuck::try_from_bytes(ticket_data){
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("unable to read internal ticket data: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo: add ticket expiration
|
||||||
|
|
||||||
|
let TicketInternalData{
|
||||||
|
session_key,
|
||||||
|
pid: ticket_source_pid,
|
||||||
|
issued_time
|
||||||
|
} = *ticket_data;
|
||||||
|
|
||||||
|
// todo: add checking if tickets are signed with a valid md5-hmac
|
||||||
|
let request_data_length = request_data.len();
|
||||||
|
let request_data = &mut request_data[0.. request_data_length - 0x10];
|
||||||
|
|
||||||
|
let mut rc4: StreamCipherCoreWrapper<Rc4Core<U32>> =
|
||||||
|
Rc4::new_from_slice(&session_key).expect("unable to init rc4 keystream");
|
||||||
|
|
||||||
|
rc4.apply_keystream(request_data);
|
||||||
|
|
||||||
|
let mut reqest_data_cursor = Cursor::new(request_data);
|
||||||
|
|
||||||
|
let pid: u32 = reqest_data_cursor.read_struct(IS_BIG_ENDIAN).ok()?;
|
||||||
|
|
||||||
|
if pid != ticket_source_pid{
|
||||||
|
let ticket_created_on = issued_time.to_regular_time();
|
||||||
|
|
||||||
|
error!("someone tried to spoof their pid, ticket was created on: {}", ticket_created_on.to_rfc2822());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _cid: u32 = reqest_data_cursor.read_struct(IS_BIG_ENDIAN).ok()?;
|
||||||
|
let response_check: u32 = reqest_data_cursor.read_struct(IS_BIG_ENDIAN).ok()?;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Some((session_key, pid, response_check))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rc4U32 = StreamCipherCoreWrapper<Rc4Core<U32>>;
|
||||||
|
|
||||||
|
pub fn generate_secure_encryption_pairs(mut session_key: [u8; 32], count: u8) -> Vec<EncryptionPair>{
|
||||||
|
let mut vec = Vec::with_capacity(count as usize);
|
||||||
|
|
||||||
|
vec.push(EncryptionPair{
|
||||||
|
send: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4")),
|
||||||
|
recv: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4"))
|
||||||
|
});
|
||||||
|
|
||||||
|
for _ in 1..=count{
|
||||||
|
let modifier = session_key.len() + 1;
|
||||||
|
|
||||||
|
let key_length = session_key.len();
|
||||||
|
|
||||||
|
for (position, val) in (&mut session_key[0..key_length/2]).iter_mut().enumerate(){
|
||||||
|
*val = val.wrapping_add((modifier - position) as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec.push(EncryptionPair{
|
||||||
|
send: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4")),
|
||||||
|
recv: Box::new(Rc4U32::new_from_slice(&session_key).expect("unable to create rc4"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
@ -30,8 +30,8 @@ type OnConnectHandlerFn = Box<dyn Fn(PRUDPPacket, u8) -> Pin<Box<dyn Future<Outp
|
||||||
type OnDataHandlerFn = Box<dyn for<'a> Fn(PRUDPPacket, Arc<SocketData>, &'a mut MutexGuard<'_, ConnectionData>) -> Pin<Box<dyn Future<Output=()> + 'a + Send>> + Send + Sync>;
|
type OnDataHandlerFn = Box<dyn for<'a> Fn(PRUDPPacket, Arc<SocketData>, &'a mut MutexGuard<'_, ConnectionData>) -> Pin<Box<dyn Future<Output=()> + 'a + Send>> + Send + Sync>;
|
||||||
|
|
||||||
pub struct ActiveSecureConnectionData {
|
pub struct ActiveSecureConnectionData {
|
||||||
pid: u32,
|
pub(crate) pid: u32,
|
||||||
session_key: [u8; 32],
|
pub(crate) session_key: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SocketData {
|
pub struct SocketData {
|
||||||
|
|
@ -41,6 +41,7 @@ pub struct SocketData {
|
||||||
connections: RwLock<HashMap<PRUDPSockAddr, Arc<Mutex<ConnectionData>>>>,
|
connections: RwLock<HashMap<PRUDPSockAddr, Arc<Mutex<ConnectionData>>>>,
|
||||||
on_connect_handler: OnConnectHandlerFn,
|
on_connect_handler: OnConnectHandlerFn,
|
||||||
on_data_handler: OnDataHandlerFn,
|
on_data_handler: OnDataHandlerFn,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EncryptionPair{
|
pub struct EncryptionPair{
|
||||||
|
|
@ -54,6 +55,7 @@ pub struct ActiveConnectionData {
|
||||||
pub reliable_client_queue: VecDeque<PRUDPPacket>,
|
pub reliable_client_queue: VecDeque<PRUDPPacket>,
|
||||||
pub encryption_pairs: Vec<EncryptionPair>,
|
pub encryption_pairs: Vec<EncryptionPair>,
|
||||||
pub server_session_id: u8,
|
pub server_session_id: u8,
|
||||||
|
pub connection_id: u32,
|
||||||
pub active_secure_connection_data: Option<ActiveSecureConnectionData>
|
pub active_secure_connection_data: Option<ActiveSecureConnectionData>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,7 +229,7 @@ impl SocketData {
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some((
|
let Some((
|
||||||
accepted,
|
response_data,
|
||||||
encryption_pairs,
|
encryption_pairs,
|
||||||
active_secure_connection_data
|
active_secure_connection_data
|
||||||
)) = (self.on_connect_handler)(packet.clone(), *max_substream).await else {
|
)) = (self.on_connect_handler)(packet.clone(), *max_substream).await else {
|
||||||
|
|
@ -242,11 +244,14 @@ impl SocketData {
|
||||||
reliable_client_counter: 2,
|
reliable_client_counter: 2,
|
||||||
reliable_server_counter: 1,
|
reliable_server_counter: 1,
|
||||||
server_session_id: packet.header.session_id,
|
server_session_id: packet.header.session_id,
|
||||||
active_secure_connection_data
|
active_secure_connection_data,
|
||||||
|
connection_id: random()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut response_packet = packet.base_response_packet();
|
let mut response_packet = packet.base_response_packet();
|
||||||
|
|
||||||
|
response_packet.payload = response_data;
|
||||||
|
|
||||||
response_packet.header.types_and_flags.set_types(CONNECT);
|
response_packet.header.types_and_flags.set_types(CONNECT);
|
||||||
response_packet.header.types_and_flags.set_flag(ACK);
|
response_packet.header.types_and_flags.set_flag(ACK);
|
||||||
response_packet.header.types_and_flags.set_flag(HAS_SIZE);
|
response_packet.header.types_and_flags.set_flag(HAS_SIZE);
|
||||||
|
|
@ -286,14 +291,7 @@ impl SocketData {
|
||||||
|
|
||||||
response_packet.set_sizes();
|
response_packet.set_sizes();
|
||||||
|
|
||||||
let potential_session_key = connection
|
response_packet.calculate_and_assign_signature(self.access_key, None, Some(connection.server_signature));
|
||||||
.active_connection_data
|
|
||||||
.as_ref()
|
|
||||||
.unwrap().active_secure_connection_data
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.session_key);
|
|
||||||
|
|
||||||
response_packet.calculate_and_assign_signature(self.access_key, potential_session_key, Some(connection.server_signature));
|
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
response_packet.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
response_packet.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||||
|
|
@ -322,7 +320,14 @@ impl SocketData {
|
||||||
ack.header.session_id = active_connection.server_session_id;
|
ack.header.session_id = active_connection.server_session_id;
|
||||||
|
|
||||||
ack.set_sizes();
|
ack.set_sizes();
|
||||||
ack.calculate_and_assign_signature(self.access_key, None, Some(connection.server_signature));
|
let potential_session_key = connection
|
||||||
|
.active_connection_data
|
||||||
|
.as_ref()
|
||||||
|
.unwrap().active_secure_connection_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.session_key);
|
||||||
|
|
||||||
|
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(connection.server_signature));
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||||
|
|
@ -383,7 +388,13 @@ impl SocketData {
|
||||||
ack.header.session_id = active_connection.server_session_id;
|
ack.header.session_id = active_connection.server_session_id;
|
||||||
|
|
||||||
ack.set_sizes();
|
ack.set_sizes();
|
||||||
ack.calculate_and_assign_signature(self.access_key, None, Some(*server_signature));
|
|
||||||
|
let potential_session_key = active_connection.
|
||||||
|
active_secure_connection_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.session_key);
|
||||||
|
|
||||||
|
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(*server_signature));
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||||
|
|
@ -392,28 +403,27 @@ impl SocketData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DISCONNECT => {
|
DISCONNECT => {
|
||||||
let ConnectionData{
|
|
||||||
server_signature,
|
|
||||||
active_connection_data,
|
|
||||||
..
|
|
||||||
} = &*connection;
|
|
||||||
|
|
||||||
let Some(active_connection) = active_connection_data.as_ref() else {
|
let Some(active_connection) = &connection.active_connection_data else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
info!("client disconnected");
|
||||||
|
|
||||||
let mut ack = packet.base_acknowledgement_packet();
|
let mut ack = packet.base_acknowledgement_packet();
|
||||||
|
|
||||||
|
ack.header.session_id = active_connection.server_session_id;
|
||||||
|
|
||||||
ack.set_sizes();
|
ack.set_sizes();
|
||||||
|
|
||||||
let potential_session_key = active_connection_data
|
let potential_session_key = active_connection.active_secure_connection_data
|
||||||
.as_ref()
|
|
||||||
.unwrap().active_secure_connection_data
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.session_key);
|
.map(|s| s.session_key);
|
||||||
|
|
||||||
|
|
||||||
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(*server_signature));
|
ack.calculate_and_assign_signature(self.access_key, potential_session_key, Some(connection.server_signature));
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
ack.write_to(&mut vec).expect("somehow failed to convert backet to bytes");
|
||||||
|
|
@ -450,7 +460,14 @@ impl ConnectionData{
|
||||||
|
|
||||||
packet.set_sizes();
|
packet.set_sizes();
|
||||||
|
|
||||||
packet.calculate_and_assign_signature(socket.access_key, None, Some(self.server_signature));
|
let potential_session_key = self.active_connection_data
|
||||||
|
.as_ref()
|
||||||
|
.unwrap().active_secure_connection_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.session_key);
|
||||||
|
|
||||||
|
|
||||||
|
packet.calculate_and_assign_signature(socket.access_key, potential_session_key, Some(self.server_signature));
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
|
|
||||||
|
|
|
||||||
161
src/prudp/station_url.rs
Normal file
161
src/prudp/station_url.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
use log::error;
|
||||||
|
use std::fmt::{Display, Formatter, Write};
|
||||||
|
use crate::prudp::station_url::Type::{PRUDP, PRUDPS, UDP};
|
||||||
|
use crate::prudp::station_url::UrlOptions::{Address, ConnectionID, NatFiltering, NatMapping, NatType, Platform, PMP, Port, PrincipalID, RVConnectionID, StreamID, StreamType, UPNP};
|
||||||
|
|
||||||
|
pub enum Type{
|
||||||
|
UDP,
|
||||||
|
PRUDP,
|
||||||
|
PRUDPS
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod nat_types{
|
||||||
|
pub const BEHIND_NAT: u8 = 1;
|
||||||
|
pub const PUBLIC: u8 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum UrlOptions {
|
||||||
|
Address(Ipv4Addr),
|
||||||
|
Port(u16),
|
||||||
|
StreamType(u8),
|
||||||
|
StreamID(u8),
|
||||||
|
ConnectionID(u8),
|
||||||
|
PrincipalID(u32),
|
||||||
|
NatType(u8),
|
||||||
|
NatMapping(u8),
|
||||||
|
NatFiltering(u8),
|
||||||
|
UPNP(u8),
|
||||||
|
RVConnectionID(u32),
|
||||||
|
Platform(u8),
|
||||||
|
PMP(u8),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StationUrl{
|
||||||
|
pub url_type: Type,
|
||||||
|
pub options: Vec<UrlOptions>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StationUrl{
|
||||||
|
fn read_options(options: &str) -> Option<Vec<UrlOptions>>{
|
||||||
|
let mut options_out = Vec::new();
|
||||||
|
|
||||||
|
for option in options.split(';'){
|
||||||
|
if option == "" { continue; }
|
||||||
|
let mut option_parts = option.split('=');
|
||||||
|
let option_name= option_parts.next()?.to_ascii_lowercase();
|
||||||
|
let option_value = option_parts.next()?;
|
||||||
|
|
||||||
|
match option_name.as_ref(){
|
||||||
|
"address" => {
|
||||||
|
options_out.push(Address(option_value.parse().ok()?))
|
||||||
|
},
|
||||||
|
"port" => {
|
||||||
|
options_out.push(Port(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"natf" => {
|
||||||
|
options_out.push(NatFiltering(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"natm" => {
|
||||||
|
options_out.push(NatMapping(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"sid" => {
|
||||||
|
options_out.push(StreamID(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"upnp" => {
|
||||||
|
options_out.push(UPNP(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"type" => {
|
||||||
|
options_out.push(NatType(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"stream" => {
|
||||||
|
options_out.push(StreamType(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"RVCID" => {
|
||||||
|
options_out.push(RVConnectionID(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"pl" => {
|
||||||
|
options_out.push(Platform(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
"pmp" => {
|
||||||
|
options_out.push(PMP(option_value.parse().ok()?))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("unimplemented option type, skipping: {}", option_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(options_out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for StationUrl{
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, ()> {
|
||||||
|
let (url_type, options) = value.split_at(value.find(":/").ok_or(())?);
|
||||||
|
|
||||||
|
let options = &options[2..];
|
||||||
|
|
||||||
|
let url_type = match url_type{
|
||||||
|
"udp" => UDP,
|
||||||
|
"prudp" => PRUDP,
|
||||||
|
"prudps" => PRUDPS,
|
||||||
|
_ => return Err(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = Self::read_options(options).ok_or(())?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
Self{
|
||||||
|
url_type,
|
||||||
|
options
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Into<String> for &'a StationUrl{
|
||||||
|
fn into(self) -> String {
|
||||||
|
let mut url = match self.url_type{
|
||||||
|
UDP => "udp:/",
|
||||||
|
PRUDP => "prudp:/",
|
||||||
|
PRUDPS => "prudps:/"
|
||||||
|
}.to_owned();
|
||||||
|
|
||||||
|
for option in &self.options{
|
||||||
|
match option{
|
||||||
|
Address(v) => write!(url, "address={}", v).expect("failed to write"),
|
||||||
|
Port(v) => write!(url, "port={}", v).expect("failed to write"),
|
||||||
|
StreamType(v) => write!(url, "stream={}", v).expect("failed to write"),
|
||||||
|
StreamID(v) => write!(url, "sid={}", v).expect("failed to write"),
|
||||||
|
ConnectionID(v) => write!(url, "CID={}", v).expect("failed to write"),
|
||||||
|
PrincipalID(v) => write!(url, "PID={}", v).expect("failed to write"),
|
||||||
|
NatType(v) => write!(url, "type={}", v).expect("failed to write"),
|
||||||
|
NatMapping(v) => write!(url, "natm={}", v).expect("failed to write"),
|
||||||
|
NatFiltering(v) => write!(url, "natf={}", v).expect("failed to write"),
|
||||||
|
UPNP(v) => write!(url, "upnp={}", v).expect("failed to write"),
|
||||||
|
RVConnectionID(v) => write!(url, "RVCID={}", v).expect("failed to write"),
|
||||||
|
Platform(v) => write!(url, "pl={}", v).expect("failed to write"),
|
||||||
|
PMP(v) => write!(url, "pmp={}", v).expect("failed to write"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for StationUrl{
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let str: String = self.into();
|
||||||
|
|
||||||
|
write!(f, "{}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use bytemuck::bytes_of;
|
use bytemuck::bytes_of;
|
||||||
|
use crate::endianness::{IS_BIG_ENDIAN, ReadExtensions};
|
||||||
use crate::rmc::structures::RmcSerialize;
|
use crate::rmc::structures::RmcSerialize;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,7 +16,15 @@ impl<T: RmcSerialize> RmcSerialize for Vec<T>{
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
|
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
|
||||||
todo!()
|
let len: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
|
||||||
|
|
||||||
|
let mut vec = Vec::with_capacity(len as usize);
|
||||||
|
|
||||||
|
for _ in 0..len{
|
||||||
|
vec.push(T::deserialize(reader)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue