From cec6da0d85e59e5bc1c5f346a9f1fa3a9099c7b0 Mon Sep 17 00:00:00 2001 From: Jake Probst Date: Wed, 17 Jul 2019 23:17:57 -0700 Subject: [PATCH] major refactor of client/server structures --- src/common/client.rs | 91 +++++++++++ src/common/mod.rs | 3 +- src/common/network.rs | 3 +- src/common/serverstate.rs | 23 +++ src/patch/main.rs | 325 +++++++++++++++++--------------------- 5 files changed, 266 insertions(+), 179 deletions(-) create mode 100644 src/common/client.rs create mode 100644 src/common/serverstate.rs diff --git a/src/common/client.rs b/src/common/client.rs new file mode 100644 index 0000000..ad3b378 --- /dev/null +++ b/src/common/client.rs @@ -0,0 +1,91 @@ +use libpso::crypto::{PSOCipher, NullCipher}; +use libpso::{PSOPacket, PacketParseError}; + +use crate::common::serverstate::{ServerState, ServerPacket, OnConnect}; +use crate::common::network::{send_packet, recv_packet}; + +use std::net; +use mio::tcp::TcpStream; +use mio::{Poll, Events, Token, Ready, PollOpt}; + +pub struct Client { + running: bool, + socket: mio::tcp::TcpStream, + cipher_in: Box, + cipher_out: Box, + state: Box>, +} + +impl Client { + pub fn new(socket: net::TcpStream, state: Box>) -> Client { + let mut client = Client { + running: true, + socket: mio::tcp::TcpStream::from_stream(socket).expect("could not convert socket to nonblocking"), + cipher_in: Box::new(NullCipher {}), + cipher_out: Box::new(NullCipher {}), + state: state, + }; + + for task in client.state.on_connect() { + match task { + OnConnect::Packet(pkt) => client.send(&*pkt), + OnConnect::Cipher((cipher_in, cipher_out)) => { + client.cipher_in = cipher_in; + client.cipher_out = cipher_out; + }, + } + } + + client + } + + fn send(&mut self, pkt: &dyn PSOPacket) { + match send_packet(&mut self.socket, &mut *self.cipher_out, pkt) { + Ok(_) => { + println!("[patch] send ({:?}): {:?}", self.socket, pkt); + }, + Err(err) => { + println!("[patch] error sending packet to {:?}: {:?}", self.socket, err); + self.running = false; + } + } + } + + pub fn io_loop(mut self) { + let poll = mio::Poll::new().unwrap(); + poll.register(&self.socket, Token(0), Ready::readable(), PollOpt::edge()).unwrap(); + + + let mut events = Events::with_capacity(1024); + loop { + poll.poll(&mut events, None).unwrap(); + + for event in &events{ + println!("event! {:?}", event); + if event.token() == Token(0) { + loop { + let pkt = recv_packet(&mut self.socket, &mut *self.cipher_in) + .and_then(|pkt| { + P::from_bytes(&pkt) + .map_err(|err| err.into()) + }); + + match pkt { + Ok(pkt) => { + let response = self.state.handle(&pkt); + for r in response { + self.send(&*r); + } + }, + Err(err) => { + println!("error recv-ing packet with {:?}: {:?}", self.socket, err); + break; + } + } + } + } + } + } + + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index a6bb89e..29ec690 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,4 +1,5 @@ pub mod cipherkeys; pub mod network; - +pub mod serverstate; +pub mod client; diff --git a/src/common/network.rs b/src/common/network.rs index c1976f0..f110462 100644 --- a/src/common/network.rs +++ b/src/common/network.rs @@ -36,7 +36,6 @@ pub fn recv_packet(socket: &mut T, cipher: &mut dyn PSOCipher) -> Resul let mut offset = 0; while offset < cipher.header_size() { let diff = socket.read(&mut size_buf[offset..])?; - println!("! {} {:?}", diff, size_buf); if diff == 0 { return Err(PacketNetworkError::ClientDisconnected); } @@ -76,6 +75,8 @@ pub fn recv_packet(socket: &mut T, cipher: &mut dyn PSOCipher) -> Resul Ok(full_buf) } +// TODO: this fails horribly when the send buffer is full +// if it didnt send it all make a mio poll pub fn send_packet(socket: &mut T, cipher: &mut dyn PSOCipher, pkt: &dyn PSOPacket) -> Result<(), PacketNetworkError> { let buf = pkt.as_bytes(); //println!("[send]: {:X?}", buf); diff --git a/src/common/serverstate.rs b/src/common/serverstate.rs new file mode 100644 index 0000000..affb31f --- /dev/null +++ b/src/common/serverstate.rs @@ -0,0 +1,23 @@ +use libpso::{PSOPacket, PacketParseError}; +use libpso::crypto::PSOCipher; + + +pub enum OnConnect { + Packet(Box), + Cipher((Box, Box)), +} + +pub trait ServerPacket: Sized { + fn from_bytes(data: &Vec) -> Result; +} + +pub trait ServerState { + type Packet: ServerPacket; + type PacketError; + + //fn handle(&mut self, pkt: &Self::Packet) -> Result>, Self::PacketError>; + fn on_connect(&mut self) -> Vec; + //fn handle(&mut self, pkt: &Self::Packet) -> Result>>, Self::PacketError>; + fn handle(&mut self, pkt: &Self::Packet) -> Box>>; +} + diff --git a/src/patch/main.rs b/src/patch/main.rs index 8760a98..91de59f 100644 --- a/src/patch/main.rs +++ b/src/patch/main.rs @@ -12,9 +12,10 @@ use rand::{Rng, RngCore}; use crc::{crc32, Hasher32}; use libpso::{PacketParseError, PSOPacket}; use libpso::packet::patch::*; -use libpso::crypto::{CipherError, PSOCipher, NullCipher}; use libpso::crypto::pc::PSOPCCipher; use elseware::common::network::{send_packet, recv_packet, PacketNetworkError}; +use elseware::common::client::Client; +use elseware::common::serverstate::{ServerPacket, ServerState, OnConnect}; const PATCH_PORT: u16 = 11000; @@ -43,25 +44,6 @@ impl From for PatchError { } } -#[derive(Debug)] -pub enum PatchPacket { - PatchWelcomeReply(PatchWelcomeReply), - LoginReply(LoginReply), - FileInfoReply(FileInfoReply), - FileInfoListEnd(FileInfoListEnd), -} - -impl PatchPacket { - pub fn from_bytes(data: &Vec) -> Result { - match data[2] { - 0x02 => Ok(PatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)), - 0x04 => Ok(PatchPacket::LoginReply(LoginReply::from_bytes(data)?)), - 0x0F => Ok(PatchPacket::FileInfoReply(FileInfoReply::from_bytes(data)?)), - 0x10 => Ok(PatchPacket::FileInfoListEnd(FileInfoListEnd::from_bytes(data)?)), - _ => Err(PacketParseError::WrongPacketForServerType) - } - } -} #[derive(Debug, Clone)] struct PatchFile { @@ -70,7 +52,6 @@ struct PatchFile { size: u32, } - enum PatchTreeIterItem { Directory(PathBuf), File(PathBuf, u32), @@ -109,50 +90,87 @@ impl PatchFileTree { } +#[derive(Debug)] +pub enum PatchPacket { + PatchWelcomeReply(PatchWelcomeReply), + LoginReply(LoginReply), + FileInfoReply(FileInfoReply), + FileInfoListEnd(FileInfoListEnd), +} + +impl ServerPacket for PatchPacket { + fn from_bytes(data: &Vec) -> Result { + match data[2] { + 0x02 => Ok(PatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)), + 0x04 => Ok(PatchPacket::LoginReply(LoginReply::from_bytes(data)?)), + 0x0F => Ok(PatchPacket::FileInfoReply(FileInfoReply::from_bytes(data)?)), + 0x10 => Ok(PatchPacket::FileInfoListEnd(FileInfoListEnd::from_bytes(data)?)), + _ => Err(PacketParseError::WrongPacketForServerType) + } + } +} -struct Client { - running: bool, - key_in: u32, - key_out: u32, - socket: mio::tcp::TcpStream, - cipher_in: Box, - cipher_out: Box, +struct PatchServerState { patch_file_tree: PatchFileTree, patch_file_lookup: HashMap, patch_file_info: Vec, } -impl Client { - fn new(socket: net::TcpStream, patch_file_tree: PatchFileTree, patch_file_lookup: HashMap) -> Client { +impl PatchServerState { + fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap) -> PatchServerState { + PatchServerState { + patch_file_tree: patch_file_tree, + patch_file_lookup: patch_file_lookup, + patch_file_info: Vec::new(), + } + } +} + +impl ServerState for PatchServerState { + type Packet = PatchPacket; + type PacketError = PatchError; + + fn on_connect(&mut self) -> Vec { let mut rng = rand::thread_rng(); let key_in: u32 = rng.gen(); let key_out: u32 = rng.gen(); - Client { - running: true, - key_in: key_in, - key_out: key_out, - socket: mio::tcp::TcpStream::from_stream(socket).expect("could not convert socket to nonblocking"), - cipher_in: Box::new(NullCipher {}), - cipher_out: Box::new(NullCipher {}), - patch_file_tree: patch_file_tree, - patch_file_lookup: patch_file_lookup, - patch_file_info: Vec::new() - } + vec![OnConnect::Packet(Box::new(PatchWelcome::new(key_out, key_in))), + OnConnect::Cipher((Box::new(PSOPCCipher::new(key_in)), Box::new(PSOPCCipher::new(key_out)))) + ] } - fn send(&mut self, pkt: &dyn PSOPacket) { - match send_packet(&mut self.socket, &mut *self.cipher_out, pkt) { - Ok(_) => { - println!("[patch] send ({:?}): {:?}", self.socket, pkt); + fn handle(&mut self, pkt: &PatchPacket) -> Box>> { + match pkt { + PatchPacket::PatchWelcomeReply(_pkt) => { + let p: Vec> = vec![Box::new(RequestLogin {})]; + Box::new(p.into_iter()) + }, + PatchPacket::LoginReply(_pkt) => { + let mut p: Vec> = vec![Box::new(Message::new("hello player".to_string()))]; + p.append(&mut get_file_list_packets(&self.patch_file_tree)); + p.push(Box::new(PatchEndList {})); + Box::new(p.into_iter()) + }, + PatchPacket::FileInfoReply(pkt) => { + self.patch_file_info.push(pkt.clone()); + Box::new(None.into_iter()) }, - Err(err) => { - println!("[patch] error sending packet to {:?}: {:?}", self.socket, err); - self.running = false; + PatchPacket::FileInfoListEnd(_pkt) => { + let need_update = self.patch_file_info.iter() + .filter(|file_info| does_file_need_updating(file_info, &self.patch_file_lookup)) + .collect::>(); + + let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size); + let total_files = need_update.len() as u32; + + let p: Vec> = vec![Box::new(FilesToPatchMetadata::new(total_size, total_files)), + Box::new(PatchStartList {}), + ]; + Box::new(p.into_iter().chain(SendFileIterator::new(&self))) } } } - } fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap) -> PatchFileTree { @@ -192,7 +210,6 @@ fn generate_patch_tree(basedir: &str) -> (PatchFileTree, HashMap } -// TODO: this should be a function that takes client and sends packets instead of returning a vec of packets fn get_file_list_packets(patch_file_tree: &PatchFileTree) -> Vec> { let mut pkts: Vec> = Vec::new(); @@ -213,20 +230,6 @@ fn get_file_list_packets(patch_file_tree: &PatchFileTree) -> Vec Result<(), PatchError> { - client.send(&PatchStartList {}); - - let pkts = get_file_list_packets(&client.patch_file_tree); - for pkt in pkts { - client.send(&*pkt); - } - - client.send(&PatchEndList {}); - - Ok(()) -} - fn get_checksum_and_size(path: &PathBuf) -> Result<(u32, u32), PatchError> { let file = fs::File::open(path)?; let size = file.metadata().unwrap().len(); @@ -248,124 +251,92 @@ fn does_file_need_updating(file_info: &FileInfoReply, patch_file_lookup: &HashMa patch_file.checksum != file_info.checksum || patch_file.size != file_info.size } -fn send_file(client: &mut Client, actual_path: &PathBuf, patch_path: &PathBuf, id: u32) -> Result<(), PatchError>{ - let file = fs::File::open(actual_path)?; - let size = file.metadata().unwrap().len(); - - client.send(&StartFileSend::new(patch_path.to_str().unwrap(), size as u32, id)); - let mut buf = [0u8; PATCH_FILE_CHUNK_SIZE as usize]; - let mut reader = io::BufReader::new(file); - let mut chunk_num = 0; - while let Ok(len) = reader.read(&mut buf) { - if len == 0 { - break; - } - - let mut crc = crc32::Digest::new(crc32::IEEE); - crc.write(&buf[0..len]); - - let pkt = FileSend { - chunk_num: chunk_num, - checksum: crc.sum32(), - chunk_size: len as u32, - buffer: buf - }; - - client.send(&pkt); - - chunk_num += 1; - } - - client.send(&EndFileSend::new()); - Ok(()) +struct SendFileIterator { + done: bool, + file_iter: Box>, + file_ids_to_update: HashSet, + patch_file_lookup: HashMap, + current_file: Option>, + chunk_num: u32, } - -fn send_file_data(client: &mut Client) -> Result<(), PatchError> { - let need_update = client.patch_file_info.iter() - .filter(|file_info| does_file_need_updating(file_info, &client.patch_file_lookup)) - .collect::>(); - - let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size); - let total_files = need_update.len() as u32; - - let file_ids_to_update = need_update.iter().map(|k| k.id).collect::>(); - - client.send(&FilesToPatchMetadata::new(total_size, total_files)); - client.send(&PatchStartList {}); - - for file in client.patch_file_tree.flatten() { - match file { - PatchTreeIterItem::Directory(path) => { - client.send(&ChangeDirectory::new(path.to_str().unwrap())); - }, - PatchTreeIterItem::File(path, id) => { - if file_ids_to_update.contains(&id) { - let patch_file = client.patch_file_lookup.get(&id).unwrap().clone(); - send_file(client, &patch_file.path, &path, id)?; - } - }, - PatchTreeIterItem::UpDirectory => { - client.send(&UpOneDirectory {}); - } +impl SendFileIterator { + fn new(state: &PatchServerState) -> SendFileIterator { + let need_update = state.patch_file_info.iter() + .filter(|file_info| does_file_need_updating(file_info, &state.patch_file_lookup)) + .collect::>(); + let file_ids_to_update = need_update.iter().map(|k| k.id).collect::>(); + + SendFileIterator { + done: false, + file_ids_to_update: file_ids_to_update, + patch_file_lookup: state.patch_file_lookup.clone(), + file_iter: Box::new(state.patch_file_tree.flatten().into_iter()), + current_file: None, + chunk_num: 0, } - } - client.send(&FinalizePatching {}); - - Ok(()) } +impl Iterator for SendFileIterator { + type Item = Box; -fn handle_packet(client: &mut Client, pkt: PatchPacket) -> Result<(), PatchError> { - println!("[patch] recv({:?}): {:?}", client.socket, pkt); - - match pkt { - PatchPacket::PatchWelcomeReply(_pkt) => { - client.send(&RequestLogin {}); - }, - PatchPacket::LoginReply(_pkt) => { - client.send(&Message::new("hello player".to_string())); - send_file_list(client)?; - }, - PatchPacket::FileInfoReply(pkt) => { - client.patch_file_info.push(pkt); - }, - PatchPacket::FileInfoListEnd(_pkt) => { - send_file_data(client)?; + fn next(&mut self) -> Option { + if self.done { + return None; } - } - Ok(()) -} - -fn client_loop(mut client: Client) { - let poll = mio::Poll::new().unwrap(); - poll.register(&client.socket, Token(0), Ready::readable(), PollOpt::edge()).unwrap(); - - let mut events = Events::with_capacity(1024); - loop { - poll.poll(&mut events, None).unwrap(); - - for event in &events{ - println!("event! {:?}", event); - if event.token() == Token(0) { - loop { - let pkt = recv_packet(&mut client.socket, &mut *client.cipher_in) - .and_then(|pkt| { - PatchPacket::from_bytes(&pkt) - .map_err(|err| err.into()) - }); - - match pkt { - Ok(pkt) => { - handle_packet(&mut client, pkt).expect("could not handle packet"); - }, - Err(err) => { - println!("[patch] error recv-ing packet with {:?}: {:?}", client.socket, err); - break; + match self.current_file { + Some(ref mut file) => { + let mut buf = [0u8; PATCH_FILE_CHUNK_SIZE as usize]; + let len = file.read(&mut buf).unwrap(); + if len == 0 { + self.current_file = None; + self.chunk_num = 0; + Some(Box::new(EndFileSend::new())) + } + else { + let mut crc = crc32::Digest::new(crc32::IEEE); + crc.write(&buf[0..len]); + let pkt = FileSend { + chunk_num: self.chunk_num, + checksum: crc.sum32(), + chunk_size: len as u32, + buffer: buf, + }; + self.chunk_num += 1; + Some(Box::new(pkt)) + } + }, + None => { + match self.file_iter.next() { + Some(next_file) => { + match next_file { + PatchTreeIterItem::Directory(path) => { + Some(Box::new(ChangeDirectory::new(path.to_str().unwrap()))) + }, + PatchTreeIterItem::File(path, id) => { + if self.file_ids_to_update.contains(&id) { + let patch_file = self.patch_file_lookup.get(&id).unwrap(); + let file = fs::File::open(&patch_file.path).unwrap(); + let size = file.metadata().unwrap().len(); + self.current_file = Some(io::BufReader::new(file)); + Some(Box::new(StartFileSend::new(path.to_str().unwrap(), size as u32, id))) + } + else { + self.next() + } + }, + PatchTreeIterItem::UpDirectory => { + Some(Box::new(UpOneDirectory {})) + }, } + }, + None => { + self.current_file = None; + self.done = true; + Some(Box::new(FinalizePatching {})) } } } @@ -373,16 +344,16 @@ fn client_loop(mut client: Client) { } } + + fn new_client(socket: net::TcpStream, patch_file_tree: PatchFileTree, patch_file_lookup: HashMap) { - let mut client = Client::new(socket, patch_file_tree, patch_file_lookup); - let welcome_pkt = PatchWelcome::new(client.key_out, client.key_in); - client.send(&welcome_pkt); - client.cipher_in = Box::new(PSOPCCipher::new(client.key_in)); - client.cipher_out = Box::new(PSOPCCipher::new(client.key_out)); - client_loop(client); + let state = PatchServerState::new(patch_file_tree, patch_file_lookup); + let client = Client::new(socket, Box::new(state)); + client.io_loop(); } + fn main() { println!("[patch] starting server");