From d05df7979f33b4d0e20897fc620036c419aaa62d Mon Sep 17 00:00:00 2001 From: David Westgate Date: Thu, 30 Nov 2023 20:19:41 -0800 Subject: [PATCH] better support messaging and parsing on client and server --- src/client.rs | 128 ++++++++++++++++++++------------------------------ src/lib.rs | 2 + src/server.rs | 108 ++++++++++++++++++++++++++---------------- 3 files changed, 120 insertions(+), 118 deletions(-) diff --git a/src/client.rs b/src/client.rs index b7b3e7f..e922387 100644 --- a/src/client.rs +++ b/src/client.rs @@ -145,14 +145,12 @@ fn help() { println!( "/leave [room-name] <- Leave the given room. Error if the you are not already in the room" ); - println!("/show [room-name] <- Switch your focus to the given room. It is suggested to join the room first"); } pub fn start() { clear(); println!("Starting the IRC client. No spaces allowed in nicknames or room names. /help to see available commands"); let mut nick: String; - let mut active_room: String = String::new(); loop { nick = input!("Enter your nickname : "); if nick.contains(" ") { @@ -200,88 +198,62 @@ pub fn start() { one_param_op(codes::client::REGISTER_NICK, &mut stream, &nick); loop { - let inp; - if active_room.is_empty() { - inp = input!(""); - } else { - inp = input!("\n[{}]:", active_room); - } - let mut args: std::str::SplitWhitespace<'_> = inp.split_whitespace(); - let command: Option<&str> = args.next(); + let inp = input!(""); - match command { - Some(cmd) => { - let param: Option<&str> = args.next(); - match cmd { - "/quit" => { - disconnect(&mut stream); - break; - } - "/rooms" => no_param_op(codes::client::LIST_ROOMS, &mut stream), - "/users" => no_param_op(codes::client::LIST_USERS, &mut stream), - "/list" => match param { - Some(room) => { - one_param_op(codes::client::LIST_USERS_IN_ROOM, &mut stream, room); - } - None => { - println!("Room name expected, but not provided"); - } - }, - "/join" => match param { - Some(room) => { - one_param_op(codes::client::JOIN_ROOM, &mut stream, room); - } - None => { - println!("Room name expected, but not provided"); - } - }, - "/show" => match param { - Some(room) => { - clear(); - active_room = room.to_string(); - } - None => { - println!("Room name expected, but not provided") - } - }, - "/leave" => match param { - Some(room) => { - one_param_op(codes::client::LEAVE_ROOM, &mut stream, room); - } - None => { - println!("Room name expected, but not provided"); - } - }, - "/msg" => match inp.split_once(" ") { - Some((room, msg)) => { - two_param_op(codes::client::MESSAGE_ROOM, &mut stream, room, msg); - } - None => { - println!("Usage: /msg [room] [message]"); - } - }, - "/help" => { - help(); - } - "/" => { - println!("Invalid command"); + match inp.split_once(" ") { + Some((cmd, param)) => match cmd { + "/quit" => { + disconnect(&mut stream); + break; + } + "/rooms" => no_param_op(codes::client::LIST_ROOMS, &mut stream), + "/users" => no_param_op(codes::client::LIST_USERS, &mut stream), + "/list" => match param.split_once(" ") { + Some((room, _)) => { + one_param_op(codes::client::LIST_USERS_IN_ROOM, &mut stream, room); } _ => { - if active_room.is_empty() { - println!("use '/show [room]' to set an active room before sending a message"); - } else { - let message: String = inp; - two_param_op( - codes::client::MESSAGE_ROOM, - &mut stream, - &active_room, - &message, - ); - } + println!("Malformaed. Try /list [room-name]"); } + }, + "/join" => match param.split_once(" ") { + Some((_, _)) => { + println!("Malformed. Try /join [room-name]"); + } + _ => { + one_param_op(codes::client::JOIN_ROOM, &mut stream, param); + } + }, + + "/leave" => match param.split_once(" ") { + Some((_, _)) => { + println!("Malformed. Try /leave [room-name]"); + } + _ => { + one_param_op(codes::client::LEAVE_ROOM, &mut stream, param); + } + }, + "/msg" => match param.split_once(" ") { + Some((room, msg)) => { + two_param_op(codes::client::MESSAGE_ROOM, &mut stream, room, msg); + } + _ => { + println!("Ufsage: /msg [room] [message]"); + } + }, + "/help" => { + help(); } + "/" => { + println!("Invalid command"); + } + _ => { + one_param_op(codes::client::MESSAGE, &mut stream, &inp); + } + }, + _ => { + one_param_op(codes::client::MESSAGE, &mut stream, &inp); } - None => {} } } } else { diff --git a/src/lib.rs b/src/lib.rs index 9f15807..cdf5340 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,3 +33,5 @@ pub mod codes { pub fn clear() { print!("\x1B[2J"); } + +pub const SPACE_BYTES: &[u8] = &[0x20]; diff --git a/src/server.rs b/src/server.rs index e2bf4fe..8ac2541 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,7 +8,7 @@ use std::{ }; use prompted::input; -use rust_irc::{clear, codes}; +use rust_irc::{clear, codes, SPACE_BYTES}; const SERVER_ADDRESS: &str = "0.0.0.0:6667"; const MAX_USERS: usize = 20; @@ -28,34 +28,21 @@ impl Server { } } -fn message(room: &str, msg: &str, sender: &str, server: &Arc>) { - let size = room.len() + msg.len() + sender.len() + 3; - let mut out_buf: Vec = vec![0; size]; - - let mut byte: usize = 0; - out_buf[byte] = codes::client::MESSAGE_ROOM; - byte += 1; - - for i in 0..room.len() { - out_buf[byte] = *room.as_bytes().get(i).unwrap(); - byte += 1; - } - - out_buf[byte] = 0x20; - byte += 1; - - for i in 0..sender.len() { - out_buf[byte] = *sender.as_bytes().get(i).unwrap(); - byte += 1; - } - - out_buf[byte] = 0x20; - byte += 1; - - for i in 0..msg.len() { - out_buf[byte] = *msg.as_bytes().get(i).unwrap(); - byte += 1; - } +fn message_room(room: &str, msg: &str, sender: &str, server: &Arc>) { + let code_bytes = &[codes::client::MESSAGE_ROOM]; + let room_bytes = room.as_bytes(); + let msg_bytes = msg.as_bytes(); + let sender_bytes = sender.as_bytes(); + let out_buf: &Vec = &[ + code_bytes, + room_bytes, + SPACE_BYTES, + sender_bytes, + SPACE_BYTES, + msg_bytes, + ] + .concat(); + println!("out buf {:?} ", out_buf.to_ascii_lowercase()); let mut guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap(); let server: &mut Server = guard.deref_mut(); @@ -64,6 +51,7 @@ fn message(room: &str, msg: &str, sender: &str, server: &Arc>) { //2: Make sure sender is a member of the room, ifn error //3: Message all non-sender users in the room the message, ifnone error empty room //4: Message the sender RESPONSE_OK + println!("checking room {} ", room); let room_users: Option<&Vec> = server.rooms.get(room); let mut sender_stream = server .users @@ -213,9 +201,11 @@ fn handle_client( leave_room(server, &nickname, room, stream); } + //Generic message sent to all users of all rooms the clients nickname is in, except the client nickname codes::client::MESSAGE => { - #[cfg(debug_assertions)] - println!("MESSAGE"); + let p: String = String::from_utf8_lossy(param_bytes).to_string(); + + message_all_senders_rooms(server, &nickname, &p, stream); stream.write_all(&[codes::RESPONSE_OK]).unwrap(); } @@ -223,13 +213,13 @@ fn handle_client( stream.write_all(&[codes::RESPONSE_OK]).unwrap(); } + //A message sent just to the users of the room passed in, except the client nickname codes::client::MESSAGE_ROOM => { let p: String = String::from_utf8_lossy(param_bytes).to_string(); let params: Option<(&str, &str)> = p.split_once(" "); - match params { Some((room, msg)) => { - message(room, msg, nickname, server); + message_room(room, msg, nickname, server); } _ => { stream @@ -250,6 +240,42 @@ fn handle_client( // } } +fn message_all_senders_rooms( + server: &Arc>, + sender: &str, + message: &str, + stream: &mut TcpStream, +) { + let rooms = get_rooms_of_user(server, sender); + let mut guard = server.lock().unwrap(); + let sender_bytes: &[u8] = sender.as_bytes(); + let code_bytes: &[u8] = &[codes::client::MESSAGE_ROOM]; + let message_bytes: &[u8] = message.as_bytes(); + let space_bytes: &[u8] = &[0x20]; + for room in rooms { + let room_bytes: &[u8] = room.as_bytes(); + let users = guard.rooms.get(&room).unwrap().clone(); + let out_buf: &Vec = &[ + code_bytes, + room_bytes, + space_bytes, + sender_bytes, + space_bytes, + message_bytes, + ] + .concat(); + + for user in users { + if !user.eq(sender) { + let stream = guard.users.get_mut(&user); + stream.unwrap().write_all(out_buf).unwrap(); + } + } + stream.write_all(&[codes::RESPONSE_OK]).unwrap(); + } +} + +/// Remove a user from any rooms they may be in, then drop the user fn remove_user(server: &Arc>, nickname: &str, stream: &mut TcpStream) { let mut guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap(); let server: &mut Server = guard.deref_mut(); @@ -261,8 +287,8 @@ fn remove_user(server: &Arc>, nickname: &str, stream: &mut TcpStre users.remove(nickname); } +/// Add a nickname to the Server, being careful to handle a possible collision. fn register_nick(server: &Arc>, nickname: &str, stream: &mut TcpStream) { - // Check for nickname collision let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap(); if unlocked_server.users.contains_key(nickname) { #[cfg(debug_assertions)] @@ -271,18 +297,17 @@ fn register_nick(server: &Arc>, nickname: &str, stream: &mut TcpSt .write_all(&[codes::ERROR, codes::error::NICKNAME_COLLISION]) .unwrap(); } else { - // Add the user to the user list let clone: TcpStream = stream.try_clone().expect("fail to clone"); let addr: String = clone.peer_addr().unwrap().to_string(); unlocked_server.users.insert(nickname.to_string(), clone); - // Send response ok stream.write_all(&[codes::RESPONSE_OK]).unwrap(); - println!("{} has registered nickname {}", addr, nickname); } } +/// Add user to a room, creating the room if necessary +/// Provide feedback about what room was just joined, and which rooms the user may be in fn join_room(server: &Arc>, user: &str, room: &str, stream: &mut TcpStream) { let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap(); @@ -313,6 +338,8 @@ fn join_room(server: &Arc>, user: &str, room: &str, stream: &mut T stream.write_all(out).unwrap(); } +/// Remove a user from a room, handling possible error cases. +/// Provide feedback about what room was just left, and which rooms the user may still be in fn leave_room(server: &Arc>, user: &str, room: &str, stream: &mut TcpStream) { let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap(); match unlocked_server.rooms.get_mut(room) { @@ -352,8 +379,8 @@ fn leave_room(server: &Arc>, user: &str, room: &str, stream: &mut } } -// Iterate on all rooms, capture each room name which has the user -// return a vec of strings of room names +/// Iterate on all rooms, capture each room name which has the user +/// return a vec of strings of room names fn get_rooms_of_user(server: &Arc>, user: &str) -> Vec { let mut result: Vec = vec![]; let guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap(); @@ -452,13 +479,14 @@ pub fn start() { } }); + // Main Menu Loop on the main thread. loop { println!("0: Quit Server"); println!("1: list connected users"); println!("2: list rooms"); println!("3: Broadcast message to all"); println!("4: Freeze server via double lock (for testing)"); - let inp: String = input!(":"); + let inp: String = input!(""); match inp.parse::() { Ok(num) => match num { 0 => {