diff --git a/src/client.rs b/src/client.rs index a9fd505..1731aaa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use prompted::input; -use rust_irc::{clear, codes, one_op_buf, one_param_buf, two_param_buf, DEFAULT_PORT}; +use rust_irc::buf_helpers::{one_op_buf, one_param_buf, two_param_buf}; +use rust_irc::{clear, codes, DEFAULT_PORT}; use std::io::{Read, Write}; use std::net::TcpStream; use std::sync::{Arc, Mutex}; @@ -43,7 +44,7 @@ fn process_message(msg_bytes: &[u8], nick: &str) { eprintln!("Server is full. Try again later"); } codes::error::NOT_IN_ROOM => { - eprintln!("Cannot send a message before joining room. Use /join [room].") + eprintln!("Cannot interact with a room you have not joined. Use /join [room].") } codes::error::EMPTY_ROOM => { eprintln!("Room is Empty"); @@ -200,7 +201,7 @@ pub fn start() { }, "/msg" => match param.split_once(' ') { Some((room, msg)) => { - let out_buf = two_param_buf(codes::MESSAGE_ROOM, room, msg); + let out_buf: Vec = two_param_buf(codes::MESSAGE_ROOM, room, msg); stream.write_all(&out_buf).unwrap(); } _ => { diff --git a/src/lib.rs b/src/lib.rs index facd2b1..6278352 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,50 +29,50 @@ pub mod codes { } pub const DEFAULT_PORT: u16 = 6667; - pub fn clear() { print!("\x1B[2J"); } +pub mod buf_helpers { + pub const SPACE_BYTES: &[u8] = &[0x20]; -pub const SPACE_BYTES: &[u8] = &[0x20]; + pub fn one_op_buf(opcode: u8) -> [u8; 1] { + [opcode] + } -pub fn one_op_buf(opcode: u8) -> [u8; 1] { - [opcode] -} - -pub fn two_op_buf(opcode0: u8, opcode1: u8) -> [u8; 2] { - [opcode0, opcode1] -} - -pub fn one_param_buf(opcode: u8, param: &str) -> Vec { - let opcode_buf: &[u8; 1] = &[opcode]; - let param_buf: &[u8] = param.as_bytes(); - let out_buf: Vec = [opcode_buf, param_buf].concat(); - out_buf -} - -pub fn two_param_buf(opcode: u8, param0: &str, param1: &str) -> Vec { - let opcode_buf: &[u8; 1] = &[opcode]; - let param0_buf: &[u8] = param0.as_bytes(); - let param1_buf: &[u8] = param1.as_bytes(); - let out_buf: Vec = [opcode_buf, param0_buf, SPACE_BYTES, param1_buf].concat(); - out_buf -} - -pub fn three_param_buf(opcode: u8, param0: &str, param1: &str, param2: &str) -> Vec { - let opcode_buf: &[u8; 1] = &[opcode]; - let param0_buf: &[u8] = param0.as_bytes(); - let param1_buf: &[u8] = param1.as_bytes(); - let param2_buf: &[u8] = param2.as_bytes(); - - let out_buf: Vec = [ - opcode_buf, - param0_buf, - SPACE_BYTES, - param1_buf, - SPACE_BYTES, - param2_buf, - ] - .concat(); - out_buf + pub fn two_op_buf(opcode0: u8, opcode1: u8) -> [u8; 2] { + [opcode0, opcode1] + } + + pub fn one_param_buf(opcode: u8, param: &str) -> Vec { + let opcode_buf: &[u8; 1] = &[opcode]; + let param_buf: &[u8] = param.as_bytes(); + let out_buf: Vec = [opcode_buf, param_buf].concat(); + out_buf + } + + pub fn two_param_buf(opcode: u8, param0: &str, param1: &str) -> Vec { + let opcode_buf: &[u8; 1] = &[opcode]; + let param0_buf: &[u8] = param0.as_bytes(); + let param1_buf: &[u8] = param1.as_bytes(); + let out_buf: Vec = [opcode_buf, param0_buf, SPACE_BYTES, param1_buf].concat(); + out_buf + } + + pub fn three_param_buf(opcode: u8, param0: &str, param1: &str, param2: &str) -> Vec { + let opcode_buf: &[u8; 1] = &[opcode]; + let param0_buf: &[u8] = param0.as_bytes(); + let param1_buf: &[u8] = param1.as_bytes(); + let param2_buf: &[u8] = param2.as_bytes(); + + let out_buf: Vec = [ + opcode_buf, + param0_buf, + SPACE_BYTES, + param1_buf, + SPACE_BYTES, + param2_buf, + ] + .concat(); + out_buf + } } diff --git a/src/server.rs b/src/server.rs index 2472673..8a36125 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,9 +8,8 @@ use std::{ }; use prompted::input; -use rust_irc::{ - clear, codes, one_op_buf, one_param_buf, three_param_buf, two_op_buf, DEFAULT_PORT, -}; +use rust_irc::buf_helpers::{one_op_buf, one_param_buf, three_param_buf, two_op_buf}; +use rust_irc::{clear, codes, DEFAULT_PORT}; #[derive(Debug)] struct Server { @@ -43,10 +42,8 @@ fn message_room(room: &str, msg: &str, sender: &str, server: &Arc> .unwrap() .try_clone() .expect("Clone issue"); - //1 match room_users { Some(users) => { - //2 let mut is_member = false; for user in users { if user.eq(sender) { @@ -57,12 +54,10 @@ fn message_room(room: &str, msg: &str, sender: &str, server: &Arc> if is_member { for user in users { if user.eq(sender) { - //4 sender_stream .write_all(&one_op_buf(codes::RESPONSE_OK)) .unwrap(); } else { - //3 let recipient_stream: Option<&mut TcpStream> = server.users.get_mut(user); match recipient_stream { Some(str) => { @@ -305,18 +300,15 @@ fn leave_room(server: &Arc>, user: &str, room: &str, stream: &mut Some(l) => { let before_len: usize = l.len(); l.retain(|item: &String| item != user); - if l.is_empty() { - unlocked_server.rooms.remove(room); - drop(unlocked_server); - let rooms: Vec = get_rooms_of_user(server, user); - let rooms_expanded: String = rooms.join(","); - let response: String = format!("Left {}. Current rooms: {}", room, rooms_expanded); - let out_buf: Vec = one_param_buf(codes::RESPONSE, &response); - stream.write_all(&out_buf).unwrap(); - } else if l.len() == before_len { - let err_buf: [u8; 2] = two_op_buf(codes::ERROR, codes::error::INVALID_ROOM); + + // case when the user was not found to be in the room. + if l.len() == before_len { + let err_buf: [u8; 2] = two_op_buf(codes::ERROR, codes::error::NOT_IN_ROOM); stream.write_all(&err_buf).unwrap(); } else { + if l.is_empty() { + unlocked_server.rooms.remove(room); //drop the room if this was the last member + } drop(unlocked_server); let rooms: Vec = get_rooms_of_user(server, user); let rooms_expanded: String = rooms.join(","); @@ -350,6 +342,11 @@ fn get_rooms_of_user(server: &Arc>, user: &str) -> Vec { result } +/// Entrypoint for the server +/// Main thread -> Main Menu +/// We spawn one thread to manage the entire TCP incoming process (seperate from main thread) +/// Each connected IP gets a spawned thread in the `for` loop +/// Before looping to handle generic client input, we handle the special case of the nickname registration requirnment pub fn start() { let mut host: String = "0.0.0.0:".to_string(); host.push_str(&DEFAULT_PORT.to_string()); diff --git a/tests/test_lib.rs b/tests/test_lib.rs new file mode 100644 index 0000000..fd433f7 --- /dev/null +++ b/tests/test_lib.rs @@ -0,0 +1,58 @@ +use rust_irc::{ + buf_helpers::{ + one_op_buf, one_param_buf, three_param_buf, two_op_buf, two_param_buf, SPACE_BYTES, + }, + codes, +}; + +#[test] +pub fn test_one_op_buf() { + let buf_in: [u8; 1] = one_op_buf(codes::RESPONSE_OK); + assert_eq!(buf_in, [codes::RESPONSE_OK]) +} + +#[test] +pub fn test_two_op_buf() { + let buf_in: [u8; 2] = two_op_buf(codes::ERROR, codes::error::ALREADY_IN_ROOM); + assert_eq!(buf_in, [codes::ERROR, codes::error::ALREADY_IN_ROOM]); +} + +#[test] +pub fn test_one_param_buf() { + let opcode_buf: &[u8; 1] = &[codes::MESSAGE]; + let string_buf: &[u8] = "hello world".as_bytes(); + let checker_buf: Vec = [opcode_buf, string_buf].concat(); + let result: Vec = one_param_buf(codes::MESSAGE, "hello world"); + assert_eq!(result, checker_buf); +} + +#[test] +pub fn test_two_param_buf() { + let opcode_buf: &[u8; 1] = &[codes::MESSAGE]; + let string0_buf: &[u8] = "cat".as_bytes(); + let string1_buf: &[u8] = "dog".as_bytes(); + + let checker_buf: Vec = [opcode_buf, string0_buf, SPACE_BYTES, string1_buf].concat(); + let result: Vec = two_param_buf(codes::MESSAGE, "cat", "dog"); + assert_eq!(result, checker_buf); +} + +#[test] +pub fn test_three_param_buf() { + let opcode_buf: &[u8; 1] = &[codes::MESSAGE]; + let string0_buf: &[u8] = "cat".as_bytes(); + let string1_buf: &[u8] = "dog".as_bytes(); + let string2_buf: &[u8] = "frog".as_bytes(); + + let checker_buf: Vec = [ + opcode_buf, + string0_buf, + SPACE_BYTES, + string1_buf, + SPACE_BYTES, + string2_buf, + ] + .concat(); + let result: Vec = three_param_buf(codes::MESSAGE, "cat", "dog", "frog"); + assert_eq!(result, checker_buf); +}