diff --git a/src/proto/server/request.rs b/src/proto/server/request.rs index bfc6624..e86c547 100644 --- a/src/proto/server/request.rs +++ b/src/proto/server/request.rs @@ -3,14 +3,25 @@ use std::io; use crypto::md5::Md5; use crypto::digest::Digest; -use super::constants::*; -use super::super::packet::{MutPacket, WriteToPacket}; +use proto::{DecodeError, ProtoDecode, ProtoDecoder, ProtoEncode, ProtoEncoder}; +use proto::packet::{MutPacket, WriteToPacket}; +use proto::server::constants::*; + +/* ------- * + * Helpers * + * ------- */ + +fn md5_str(string: &str) -> String { + let mut hasher = Md5::new(); + hasher.input_str(string); + hasher.result_str() +} /*================* * SERVER REQUEST * *================*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum ServerRequest { CannotConnectRequest(CannotConnectRequest), ConnectToPeerRequest(ConnectToPeerRequest), @@ -86,17 +97,11 @@ impl WriteToPacket for ServerRequest { } } -fn md5_str(string: &str) -> String { - let mut hasher = Md5::new(); - hasher.input_str(string); - hasher.result_str() -} - /*================* * CANNOT CONNECT * *================*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct CannotConnectRequest { pub token: u32, pub user_name: String, @@ -110,11 +115,29 @@ impl WriteToPacket for CannotConnectRequest { } } +impl ProtoEncode for CannotConnectRequest { + fn encode(&self, encoder: &mut ProtoEncoder) -> Result<(), io::Error> { + encoder.encode_u32(self.token)?; + encoder.encode_string(&self.user_name) + } +} + +impl ProtoDecode for CannotConnectRequest { + fn decode(decoder: &mut ProtoDecoder) -> Result { + let token = decoder.decode_u32()?; + let user_name = decoder.decode_string()?; + Ok(Self { + token: token, + user_name: user_name, + }) + } +} + /*=================* * CONNECT TO PEER * *=================*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct ConnectToPeerRequest { pub token: u32, pub user_name: String, @@ -130,11 +153,32 @@ impl WriteToPacket for ConnectToPeerRequest { } } +impl ProtoEncode for ConnectToPeerRequest { + fn encode(&self, encoder: &mut ProtoEncoder) -> Result<(), io::Error> { + encoder.encode_u32(self.token)?; + encoder.encode_string(&self.user_name)?; + encoder.encode_string(&self.connection_type) + } +} + +impl ProtoDecode for ConnectToPeerRequest { + fn decode(decoder: &mut ProtoDecoder) -> Result { + let token = decoder.decode_u32()?; + let user_name = decoder.decode_string()?; + let connection_type = decoder.decode_string()?; + Ok(Self { + token: token, + user_name: user_name, + connection_type: connection_type, + }) + } +} + /*=============* * FILE SEARCH * *=============*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct FileSearchRequest { pub ticket: u32, pub query: String, @@ -148,18 +192,42 @@ impl WriteToPacket for FileSearchRequest { } } +impl ProtoEncode for FileSearchRequest { + fn encode(&self, encoder: &mut ProtoEncoder) -> Result<(), io::Error> { + encoder.encode_u32(self.ticket)?; + encoder.encode_string(&self.query) + } +} + +impl ProtoDecode for FileSearchRequest { + fn decode(decoder: &mut ProtoDecoder) -> Result { + let ticket = decoder.decode_u32()?; + let query = decoder.decode_string()?; + Ok(Self { + ticket: ticket, + query: query, + }) + } +} + /*=======* * LOGIN * *=======*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct LoginRequest { username: String, password: String, + digest: String, major: u32, minor: u32, } +fn userpass_md5(username: &str, password: &str) -> String { + let userpass = String::new() + username + password; + md5_str(&userpass) +} + impl LoginRequest { pub fn new( username: &str, @@ -171,6 +239,7 @@ impl LoginRequest { Ok(LoginRequest { username: username.to_string(), password: password.to_string(), + digest: userpass_md5(username, password), major: major, minor: minor, }) @@ -178,28 +247,55 @@ impl LoginRequest { Err("Empty password") } } + + fn has_correct_digest(&self) -> bool { + self.digest == userpass_md5(&self.username, &self.password) + } } impl WriteToPacket for LoginRequest { fn write_to_packet(&self, packet: &mut MutPacket) -> io::Result<()> { - let userpass = String::new() + &self.username + &self.password; - let userpass_md5 = md5_str(&userpass); - try!(packet.write_value(&self.username)); try!(packet.write_value(&self.password)); try!(packet.write_value(&self.major)); - try!(packet.write_value(&userpass_md5)); + try!(packet.write_value(&self.digest)); try!(packet.write_value(&self.minor)); - Ok(()) } } +impl ProtoEncode for LoginRequest { + fn encode(&self, encoder: &mut ProtoEncoder) -> Result<(), io::Error> { + encoder.encode_string(&self.username)?; + encoder.encode_string(&self.password)?; + encoder.encode_u32(self.major)?; + encoder.encode_string(&self.digest)?; + encoder.encode_u32(self.minor) + } +} + +impl ProtoDecode for LoginRequest { + fn decode(decoder: &mut ProtoDecoder) -> Result { + let username = decoder.decode_string()?; + let password = decoder.decode_string()?; + let major = decoder.decode_u32()?; + let digest = decoder.decode_string()?; + let minor = decoder.decode_u32()?; + Ok(Self { + username: username, + password: password, + digest: digest, + major: major, + minor: minor, + }) + } +} + /*==============* * PEER ADDRESS * *==============*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct PeerAddressRequest { pub username: String, } @@ -215,7 +311,7 @@ impl WriteToPacket for PeerAddressRequest { * ROOM JOIN * *===========*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct RoomJoinRequest { pub room_name: String, } @@ -231,7 +327,7 @@ impl WriteToPacket for RoomJoinRequest { * ROOM LEAVE * *============*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct RoomLeaveRequest { pub room_name: String, } @@ -247,7 +343,7 @@ impl WriteToPacket for RoomLeaveRequest { * ROOM MESSAGE * *==============*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct RoomMessageRequest { pub room_name: String, pub message: String, @@ -265,7 +361,7 @@ impl WriteToPacket for RoomMessageRequest { * SET LISTEN PORT * *=================*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct SetListenPortRequest { pub port: u16, } @@ -281,7 +377,7 @@ impl WriteToPacket for SetListenPortRequest { * USER STATUS * *=============*/ -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct UserStatusRequest { pub user_name: String, } @@ -292,3 +388,71 @@ impl WriteToPacket for UserStatusRequest { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::fmt::Debug; + use std::io; + + use bytes::BytesMut; + + use proto::{DecodeError, ProtoDecode, ProtoDecoder, ProtoEncode, ProtoEncoder}; + + use super::*; + + fn roundtrip(input: &T) -> T { + let mut bytes = BytesMut::new(); + input.encode(&mut ProtoEncoder::new(&mut bytes)).unwrap(); + + let mut cursor = io::Cursor::new(bytes); + T::decode(&mut ProtoDecoder::new(&mut cursor)).unwrap() + } + + #[test] + fn roundtrip_cannot_connect_request() { + let input = CannotConnectRequest { + token: 1337, + user_name: "alice".to_string(), + }; + assert_eq!(roundtrip(&input), input); + } + + #[test] + fn roundtrip_connect_to_peer_request() { + let input = ConnectToPeerRequest { + token: 1337, + user_name: "alice".to_string(), + connection_type: "P".to_string(), + }; + assert_eq!(roundtrip(&input), input); + } + + #[test] + fn roundtrip_file_search_request() { + let input = FileSearchRequest { + ticket: 1337, + query: "foo.txt".to_string(), + }; + assert_eq!(roundtrip(&input), input); + } + + #[test] + #[should_panic] + fn new_login_request_with_empty_password() { + LoginRequest::new("alice", "", 1337, 42).unwrap(); + } + + #[test] + fn new_login_request_has_correct_digest() { + let request = LoginRequest::new("alice", "password1234", 1337, 42).unwrap(); + assert!(request.has_correct_digest()); + } + + #[test] + fn roundtrip_login_request() { + let input = LoginRequest::new("alice", "password1234", 1337, 42).unwrap(); + let output = roundtrip(&input); + assert_eq!(output, input); + assert!(output.has_correct_digest()); + } +}