diff --git a/Cargo.lock b/Cargo.lock index 7533ce1..3ed4f92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,16 @@ dependencies = [ "byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "advapi32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -27,6 +37,15 @@ name = "cfg-if" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "gcc" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.1" @@ -103,6 +122,33 @@ dependencies = [ "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust-crypto" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "slab" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index 2b2b97e..993320b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["letitz"] [dependencies] -mio = "0.5" -log = "0.3.5" -byteorder = "0.4.2" +mio = "^0.5" +log = "^0.3.5" +byteorder = "^0.4.2" +rust-crypto = "^0.2.34" diff --git a/src/main.rs b/src/main.rs index 643f9df..1d2d353 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,18 @@ mod server; -mod message; +mod proto; #[macro_use] extern crate log; extern crate mio; extern crate byteorder; +extern crate crypto; + +use std::io; +use std::net::ToSocketAddrs; use mio::{EventLoop, EventSet, Handler, PollOpt, Token}; +use mio::tcp::TcpStream; +use proto::Connection; use server::ServerConnection; const SERVER_HOST : &'static str = "server.slsknet.org"; @@ -16,12 +22,17 @@ const SERVER_TOKEN : Token = Token(0); #[derive(Debug)] struct ConnectionHandler { - server: ServerConnection, + server_conn: Connection, + server_stream: TcpStream, } impl ConnectionHandler { - fn new(server: ServerConnection) -> Self { - ConnectionHandler{ server: server } + fn new(server_conn: Connection, server_stream: TcpStream) + -> Self { + ConnectionHandler{ + server_conn: server_conn, + server_stream: server_stream, + } } } @@ -33,25 +44,42 @@ impl Handler for ConnectionHandler { token: Token, event_set: EventSet) { match token { - SERVER_TOKEN => self.server.ready_to_read(), + SERVER_TOKEN => + if event_set.is_readable() { + self.server_conn.ready_to_read(&mut self.server_stream) + } else { + self.server_conn.ready_to_write(&mut self.server_stream) + }, _ => unreachable!("Unknown token"), } } } -fn main() { - let server = ServerConnection::new(SERVER_HOST, SERVER_PORT).unwrap(); +fn connect(hostname: &str, port: u16) -> io::Result { + for sock_addr in try!((hostname, port).to_socket_addrs()) { + if let Ok(stream) = TcpStream::connect(&sock_addr) { + return Ok(stream) + } + } + Err(io::Error::new(io::ErrorKind::Other, + format!("Cannot connect to {}:{}", hostname, port))) +} - println!("Connected to {:?}", &server); +fn main() { + let stream = connect(SERVER_HOST, SERVER_PORT).unwrap(); + println!("Connected to {:?}", &stream); let mut event_loop = EventLoop::new().unwrap(); event_loop.register( - server.stream(), + &stream, SERVER_TOKEN, - EventSet::readable(), + EventSet::readable() | EventSet::writable(), PollOpt::edge()).unwrap(); - event_loop.run(&mut ConnectionHandler::new(server)).unwrap(); + let server_conn = Connection::new(ServerConnection::new()); + let mut handler = ConnectionHandler::new(server_conn, stream); + + event_loop.run(&mut handler).unwrap(); } diff --git a/src/message.rs b/src/message.rs deleted file mode 100644 index c021c71..0000000 --- a/src/message.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::io; -use std::io::Write; - -use byteorder::{LittleEndian, WriteBytesExt}; - -const MAX_MESSAGE_SIZE : usize = 2048; - -pub struct Message { - bytes: Vec, -} - -impl Message { - pub fn new(msg_code: u32) -> Message { - let mut bytes = Vec::with_capacity(MAX_MESSAGE_SIZE); - bytes.write_u32::(msg_code); - Message{ bytes: bytes } - } - - pub fn write_str(&mut self, string: &str) -> io::Result { - try!(self.write_u32::(string.len() as u32)); - let n = try!(self.write(string.as_bytes())); - Ok(n + 4) - } -} - -impl io::Write for Message { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.bytes.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.bytes.flush() - } -} - diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..e153cf3 --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,188 @@ +use std::iter::repeat; +use std::io; +use std::io::{Cursor, Read, Write}; +use std::mem; + +use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; + +use mio::tcp::TcpStream; + +const MAX_PACKET_SIZE: usize = 1 << 20; // 1 MiB +const U32_SIZE: usize = 4; +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, + bytes: Vec, +} + +impl Message { + pub fn new(code: MessageCode) -> Message { + let mut bytes = Vec::new(); + bytes.write_u32::(0).unwrap(); + bytes.write_u32::(code.to_u32()).unwrap(); + Message { + code: code, + 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), + bytes: bytes, + } + } + + pub fn code(&self) -> MessageCode { + self.code + } + + 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())); + Ok(n + U32_SIZE) + } + + pub fn write_u32(&mut self, n: u32) -> io::Result { + match self.bytes.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]) + } + + pub fn finalize(mut self) -> Vec { + let bytes_len = (self.bytes.len() - U32_SIZE) as u32; + { + let mut first_word = &mut self.bytes[..4]; + first_word.write_u32::(bytes_len).unwrap(); + } + self.bytes + } +} + +impl io::Write for Message { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.bytes.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.bytes.flush() + } +} + +/*======* + * PEER * + *======*/ + +pub trait Peer { + fn read_message(&mut self) -> Option; + fn write_message(&mut self, message: Message); +} + +#[derive(Debug, Clone, Copy)] +enum State { + ReadingLength, + ReadingMessage, +} + +#[derive(Debug)] +pub struct Connection { + state: State, + num_bytes_left: usize, + buffer: Vec, + peer: T, +} + +impl Connection { + + pub fn new(peer: T) -> Self { + Connection { + state: State::ReadingLength, + num_bytes_left: U32_SIZE, + buffer: vec![0; U32_SIZE], + peer: peer, + } + } + + pub fn ready_to_read(&mut self, stream: &mut TcpStream) { + let offset = self.buffer.len() - self.num_bytes_left; + match stream.read(&mut self.buffer[offset..]) { + Ok(num_bytes_read) => { + assert!(num_bytes_read <= self.num_bytes_left); + self.num_bytes_left -= num_bytes_read; + }, + + Err(e) => error!("Could not read stream: {:?}", e), + } + + if self.num_bytes_left > 0 { + return; + } + + match self.state { + State::ReadingLength => { + let message_len = + LittleEndian::read_u32(&mut self.buffer) as usize; + if message_len > MAX_MESSAGE_SIZE { + unimplemented!(); + }; + self.state = State::ReadingMessage; + self.num_bytes_left = message_len; + self.buffer.extend(repeat(0).take(message_len)); + }, + + State::ReadingMessage => { + 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)); + } + } + } + + pub fn ready_to_write(&mut self, stream: &mut TcpStream) { + match self.peer.read_message() { + Some(message) => { + stream.write(&message.finalize()).unwrap(); + () + }, + None => (), + } + } +} diff --git a/src/server.rs b/src/server.rs index 4a54f58..c440045 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,12 +1,18 @@ -use std::io; -use std::net::ToSocketAddrs; +use crypto::md5::Md5; +use crypto::digest::Digest; -use mio::tcp::TcpStream; +use proto::{Message, MessageCode, Peer}; -use message::Message; +const VER_MAJOR : u32 = 181; +const VER_MINOR : u32 = 100; -#[derive(Debug)] -enum ServerState { +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"; + +#[derive(Debug, Clone, Copy)] +enum State { NotLoggedIn, LoggingIn, LoggedIn, @@ -14,36 +20,56 @@ enum ServerState { #[derive(Debug)] pub struct ServerConnection { - stream: TcpStream, - state: ServerState, + state: State, } impl ServerConnection { - pub fn new(hostname: &str, port: u16) -> io::Result { - for sock_addr in try!((hostname, port).to_socket_addrs()) { - if let Ok(stream) = TcpStream::connect(&sock_addr) { - return Ok(ServerConnection { - stream: stream, - state: ServerState::NotLoggedIn, - }) - } + pub fn new() -> Self { + ServerConnection { + state: State::NotLoggedIn, } - Err(io::Error::new(io::ErrorKind::Other, - format!("Cannot connect to {}:{}", hostname, port))) } - pub fn stream(&self) -> &TcpStream { - &self.stream + pub fn make_login_message(&mut self) -> Message { + let mut msg = Message::new(MessageCode::Login); + + msg.write_str(USERNAME).unwrap(); + msg.write_str(PASSWORD).unwrap(); + msg.write_u32(VER_MAJOR).unwrap(); + + let userpass = USERNAME.to_string() + PASSWORD; + msg.write_str(&Self::md5_str(&userpass)).unwrap(); + + msg.write_u32(VER_MINOR).unwrap(); + + msg } - pub fn ready_to_read(&mut self) { + fn md5_str(string: &str) -> String { + let mut hasher = Md5::new(); + hasher.input_str(string); + hasher.result_str() + } +} + +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()) + }, + _ => None } } - pub fn login(&mut self) { - let msg = Message::new(1); + fn write_message(&mut self, message: Message) { + println!("write_message: {:?}", message); + match self.state { + State::LoggingIn => (), + _ => () + } } } -