From b07fd93219001f332e4e1cd5347d5f198fe4bad3 Mon Sep 17 00:00:00 2001 From: Titouan Rigoudy Date: Sun, 14 Feb 2016 18:29:12 +0100 Subject: [PATCH] Refactor packet parsing/creating code. --- src/config.rs | 10 ++ src/main.rs | 12 +-- src/{proto.rs => proto/connection.rs} | 126 +++++++++++++++----------- src/proto/message.rs | 118 ++++++++++++++++++++++++ src/proto/mod.rs | 2 + src/server.rs | 117 ++++++++++++++++-------- 6 files changed, 287 insertions(+), 98 deletions(-) create mode 100644 src/config.rs rename src/{proto.rs => proto/connection.rs} (57%) create mode 100644 src/proto/message.rs create mode 100644 src/proto/mod.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..e121e31 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,10 @@ +pub const VER_MAJOR: u32 = 181; +pub const VER_MINOR: u32 = 100; + +pub const USERNAME: &'static str = "abcdefgh"; +// The password is not used for much, and sent unencrypted over the wire, so +// why not even check it in to git +pub const PASSWORD: &'static str = "ijklmnop"; + +pub const SERVER_HOST : &'static str = "server.slsknet.org"; +pub const SERVER_PORT : u16 = 2242; diff --git a/src/main.rs b/src/main.rs index 1d2d353..d5139cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod server; mod proto; +mod config; #[macro_use] extern crate log; extern crate mio; @@ -12,12 +13,9 @@ use std::net::ToSocketAddrs; use mio::{EventLoop, EventSet, Handler, PollOpt, Token}; use mio::tcp::TcpStream; -use proto::Connection; +use proto::connection::Connection; use server::ServerConnection; -const SERVER_HOST : &'static str = "server.slsknet.org"; -const SERVER_PORT : u16 = 2242; - const SERVER_TOKEN : Token = Token(0); #[derive(Debug)] @@ -67,8 +65,10 @@ fn connect(hostname: &str, port: u16) -> io::Result { } fn main() { - let stream = connect(SERVER_HOST, SERVER_PORT).unwrap(); - println!("Connected to {:?}", &stream); + let host = config::SERVER_HOST; + let port = config::SERVER_PORT; + let stream = connect(host, port).unwrap(); + println!("Connected to {}:{}", host, port); let mut event_loop = EventLoop::new().unwrap(); diff --git a/src/proto.rs b/src/proto/connection.rs similarity index 57% rename from src/proto.rs rename to src/proto/connection.rs index e153cf3..a7794bb 100644 --- a/src/proto.rs +++ b/src/proto/connection.rs @@ -13,78 +13,81 @@ const MAX_MESSAGE_SIZE: usize = MAX_PACKET_SIZE - U32_SIZE; const CODE_LOGIN: u32 = 1; -/*=========* - * MESSAGE * - *=========*/ - -#[derive(Debug, Clone, Copy)] -pub enum MessageCode { - Login, - Unknown(u32), -} - -impl MessageCode { - fn to_u32(&self) -> u32 { - match *self { - MessageCode::Login => CODE_LOGIN, - MessageCode::Unknown(code) => code, - } - } - - fn from_u32(code: u32) -> MessageCode { - match code { - CODE_LOGIN => MessageCode::Login, - _ => MessageCode::Unknown(code), - } - } -} - #[derive(Debug)] -pub struct Message { - code: MessageCode, +pub struct Packet { + cursor: usize, bytes: Vec, } -impl Message { - pub fn new(code: MessageCode) -> Message { +impl Packet { + pub fn new(code: u32) -> Self { let mut bytes = Vec::new(); bytes.write_u32::(0).unwrap(); - bytes.write_u32::(code.to_u32()).unwrap(); - Message { - code: code, + bytes.write_u32::(code).unwrap(); + Packet { + cursor: 2*U32_SIZE, bytes: bytes, } } - fn from_raw_parts(bytes: Vec) -> Message { - let code_u32 = LittleEndian::read_u32(&bytes[U32_SIZE..2*U32_SIZE]); - Message { - code: MessageCode::from_u32(code_u32), + fn from_raw_parts(bytes: Vec) -> Self { + let size = LittleEndian::read_u32(&bytes[..U32_SIZE]) as usize; + assert!(size + U32_SIZE == bytes.len()); + Packet { + cursor: U32_SIZE, bytes: bytes, } } - pub fn code(&self) -> MessageCode { - self.code - } + // Writing convenience pub fn write_str(&mut self, string: &str) -> io::Result { - try!(self.write_u32(string.len() as u32)); - let n = try!(self.bytes.write(string.as_bytes())); + try!(self.write_uint(string.len() as u32)); + let n = try!(self.write(string.as_bytes())); Ok(n + U32_SIZE) } - pub fn write_u32(&mut self, n: u32) -> io::Result { - match self.bytes.write_u32::(n) { + pub fn write_uint(&mut self, n: u32) -> io::Result { + match self.write_u32::(n) { Ok(()) => Ok(U32_SIZE), Err(e) => Err(io::Error::from(e)) } } pub fn write_bool(&mut self, b: bool) -> io::Result { - self.bytes.write(&[b as u8]) + self.write(&[b as u8]) + } + + // Reading convenience + + pub fn read_uint(&mut self) -> io::Result { + self.read_u32::().map_err(io::Error::from) + } + + pub fn read_str(&mut self) -> io::Result { + let len = try!(self.read_uint()) as usize; + let mut buffer = vec![0; len]; + try!(self.read(&mut buffer)); + let result = String::from_utf8(buffer); + match result { + Ok(string) => Ok(string), + Err(e) => Err(io::Error::new(io::ErrorKind::Other, e.to_string())), + } + } + + pub fn read_bool(&mut self) -> io::Result { + let mut buffer = vec![0; 1]; + try!(self.read(&mut buffer)); + match buffer[0] { + 0 => Ok(false), + 1 => Ok(true), + n => Err(io::Error::new(io::ErrorKind::InvalidInput, + format!("{} is not a boolean", n))) + + } } + pub fn finalize(mut self) -> Vec { let bytes_len = (self.bytes.len() - U32_SIZE) as u32; { @@ -95,7 +98,7 @@ impl Message { } } -impl io::Write for Message { +impl io::Write for Packet { fn write(&mut self, buf: &[u8]) -> io::Result { self.bytes.write(buf) } @@ -105,19 +108,32 @@ impl io::Write for Message { } } +impl io::Read for Packet { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut slice = &self.bytes[self.cursor..]; + let result = slice.read(buf); + match result { + Ok(num_bytes_read) => self.cursor += num_bytes_read, + Err(_) => () + } + result + } +} + + /*======* * PEER * *======*/ pub trait Peer { - fn read_message(&mut self) -> Option; - fn write_message(&mut self, message: Message); + fn read_packet(&mut self) -> Option; + fn write_packet(&mut self, packet: Packet); } #[derive(Debug, Clone, Copy)] enum State { ReadingLength, - ReadingMessage, + ReadingPacket, } #[derive(Debug)] @@ -161,25 +177,25 @@ impl Connection { if message_len > MAX_MESSAGE_SIZE { unimplemented!(); }; - self.state = State::ReadingMessage; + self.state = State::ReadingPacket; self.num_bytes_left = message_len; self.buffer.extend(repeat(0).take(message_len)); }, - State::ReadingMessage => { + State::ReadingPacket => { self.state = State::ReadingLength; self.num_bytes_left = U32_SIZE; let new_buffer = vec![0;U32_SIZE]; let old_buffer = mem::replace(&mut self.buffer, new_buffer); - self.peer.write_message(Message::from_raw_parts(old_buffer)); + self.peer.write_packet(Packet::from_raw_parts(old_buffer)); } } } pub fn ready_to_write(&mut self, stream: &mut TcpStream) { - match self.peer.read_message() { - Some(message) => { - stream.write(&message.finalize()).unwrap(); + match self.peer.read_packet() { + Some(packet) => { + stream.write(&packet.finalize()).unwrap(); () }, None => (), diff --git a/src/proto/message.rs b/src/proto/message.rs new file mode 100644 index 0000000..b0d43c4 --- /dev/null +++ b/src/proto/message.rs @@ -0,0 +1,118 @@ +use std::io; +use std::net; + +use crypto::md5::Md5; +use crypto::digest::Digest; + +use proto::connection::Packet; + +const VERSION_MAJOR: u32 = 181; +const VERSION_MINOR: u32 = 0; + +const CODE_LOGIN: u32 = 1; + +pub enum ServerRequest { + LoginRequest(LoginRequest), +} + +impl ServerRequest { + pub fn to_packet(&self) -> io::Result { + match *self { + ServerRequest::LoginRequest(ref request) => { + let mut packet = Packet::new(CODE_LOGIN); + try!(request.write_to_packet(&mut packet)); + Ok(packet) + }, + } + } +} + +pub enum ServerResponse { + LoginResponse(LoginResponse), + UnknownResponse(u32, Packet), +} + +impl ServerResponse { + pub fn from_packet(mut packet: Packet) -> io::Result { + let resp = match try!(packet.read_uint()) { + CODE_LOGIN => ServerResponse::LoginResponse( + try!(LoginResponse::from_packet(packet))), + + code => ServerResponse::UnknownResponse(code, packet), + }; + Ok(resp) + } +} + +fn md5_str(string: &str) -> String { + let mut hasher = Md5::new(); + hasher.input_str(string); + hasher.result_str() +} + +pub struct LoginRequest { + username: String, + password: String, + major: u32, + minor: u32, +} + +impl LoginRequest { + pub fn new(username: &str, password: &str, major: u32, minor: u32) + -> Result { + if password.len() > 0 { + Ok(LoginRequest { + username: username.to_string(), + password: password.to_string(), + major: major, + minor: minor, + }) + } else { + Err("Empty password") + } + } + + pub fn write_to_packet(&self, packet: &mut Packet) -> io::Result<()> { + let userpass = String::new() + &self.username + &self.password; + let userpass_md5 = md5_str(&userpass); + + try!(packet.write_str(&self.username)); + try!(packet.write_str(&self.password)); + try!(packet.write_uint(self.major)); + try!(packet.write_str(&md5_str(&userpass))); + try!(packet.write_uint(self.minor)); + + Ok(()) + } +} + +pub enum LoginResponse { + LoginOk { + motd: String, + ip: net::Ipv4Addr, + password_md5_opt: Option + }, + LoginFail { + reason: String + }, +} + +impl LoginResponse { + pub fn from_packet(mut packet: Packet) -> io::Result { + let ok = try!(packet.read_bool()); + let resp = if ok { + let motd = try!(packet.read_str()).to_string(); + let ip = net::Ipv4Addr::from(try!(packet.read_uint())); + LoginResponse::LoginOk { + motd: motd, + ip: ip, + password_md5_opt: None + } + } else { + LoginResponse::LoginFail { + reason: try!(packet.read_str()).to_string() + } + }; + Ok(resp) + } +} diff --git a/src/proto/mod.rs b/src/proto/mod.rs new file mode 100644 index 0000000..ea305ac --- /dev/null +++ b/src/proto/mod.rs @@ -0,0 +1,2 @@ +pub mod connection; +pub mod message; diff --git a/src/server.rs b/src/server.rs index c440045..77322dc 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,15 +1,15 @@ -use crypto::md5::Md5; -use crypto::digest::Digest; +use std::io; +use std::net::Ipv4Addr; -use proto::{Message, MessageCode, Peer}; +use proto::connection::{Packet, Peer}; +use proto::message::{ + LoginRequest, + LoginResponse, + ServerRequest, + ServerResponse, +}; -const VER_MAJOR : u32 = 181; -const VER_MINOR : u32 = 100; - -const USERNAME : &'static str = "abcdefgh"; -// The password is not used for much, and sent unencrypted over the wire, so -// why not even check it in to git -const PASSWORD : &'static str = "ijklmnop"; +use config; #[derive(Debug, Clone, Copy)] enum State { @@ -30,46 +30,89 @@ impl ServerConnection { } } - pub fn make_login_message(&mut self) -> Message { - let mut msg = Message::new(MessageCode::Login); + fn read_request(&mut self) -> Option { + match self.state { + State::NotLoggedIn => { + println!("Logging in..."); + self.state = State::LoggingIn; + Some(ServerRequest::LoginRequest(LoginRequest::new( + config::USERNAME, + config::PASSWORD, + config::VER_MAJOR, + config::VER_MINOR, + ).unwrap())) + }, + + _ => None + } + } + + fn write_response(&mut self, response: ServerResponse) { + match response { + ServerResponse::LoginResponse(login) => { + self.handle_login(login); + }, + ServerResponse::UnknownResponse(code, packet) => { + println!("Unknown packet code {}", code); + }, + } + } - msg.write_str(USERNAME).unwrap(); - msg.write_str(PASSWORD).unwrap(); - msg.write_u32(VER_MAJOR).unwrap(); + fn handle_login(&mut self, login: LoginResponse) -> io::Result<()> { + match self.state { + State::LoggingIn => { + match login { + LoginResponse::LoginOk { motd, ip, password_md5_opt } => { + self.state = State::LoggedIn; - let userpass = USERNAME.to_string() + PASSWORD; - msg.write_str(&Self::md5_str(&userpass)).unwrap(); + println!("Login successful!"); + println!("MOTD: \"{}\"", motd); + println!("IP address: {}", ip); - msg.write_u32(VER_MINOR).unwrap(); + match password_md5_opt { + Some(password_md5) => { + println!("Password MD5: \"{}\"", password_md5); + println!(concat!( + "Connected to official server ", + "as official client")); + }, + None => println!(concat!( + "Connected to official server ", + "as unofficial client")), + } + }, - msg - } + LoginResponse::LoginFail { reason } => { + self.state = State::NotLoggedIn; + println!("Login failed!"); + println!("Reason: {}", reason); + } + } + Ok(()) + }, - fn md5_str(string: &str) -> String { - let mut hasher = Md5::new(); - hasher.input_str(string); - hasher.result_str() + _ => unimplemented!(), + } } } impl Peer for ServerConnection { - - fn read_message(&mut self) -> Option { - match self.state { - State::NotLoggedIn => { - println!("Logging in..."); - self.state = State::LoggingIn; - Some(self.make_login_message()) + fn read_packet(&mut self) -> Option { + match self.read_request() { + Some(request) => { + match request.to_packet() { + Ok(packet) => Some(packet), + Err(e) => unimplemented!(), + } }, - _ => None + None => None } } - fn write_message(&mut self, message: Message) { - println!("write_message: {:?}", message); - match self.state { - State::LoggingIn => (), - _ => () + fn write_packet(&mut self, mut packet: Packet) { + match ServerResponse::from_packet(packet) { + Ok(response) => self.write_response(response), + Err(e) => unimplemented!(), } } }