From e7ccea3c8e832fc1c43d454e79a319713fc1d2f6 Mon Sep 17 00:00:00 2001 From: Adrian Hedqvist Date: Thu, 21 Jun 2018 16:33:13 +0200 Subject: [PATCH] More progress --- src/context.rs | 70 ++++++++++++++------------- src/main.rs | 126 +++++++++++++++++++++++++++---------------------- src/models.rs | 48 +++++++++++++++---- src/user.rs | 49 ++++++++++++++----- 4 files changed, 182 insertions(+), 111 deletions(-) diff --git a/src/context.rs b/src/context.rs index 004fabd..06bcdb3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -10,66 +10,68 @@ use models::IrcCommand; #[derive(Clone)] pub struct Context { - pub connections: Arc>>>, - pub unregistered_users: Arc>>, - pub users: Arc>>, + pub hostname: Arc>, + pub users: Arc>>, pub channels: Arc>>, } impl Context { pub fn new() -> Context { Context { - connections: Arc::new(Mutex::new(HashMap::new())), - unregistered_users: Arc::new(Mutex::new(HashMap::new())), + hostname: Arc::new(Mutex::new("plankircd".to_string())), users: Arc::new(Mutex::new(HashMap::new())), channels: Arc::new(Mutex::new(HashMap::new())), } } pub fn register_user(&mut self, addr: &SocketAddr) -> Result<(), ()> { - let mut users = self.users.lock().unwrap(); - let mut unreg = self.unregistered_users.lock().unwrap(); - let remove = unreg.remove(addr); - match remove { - Some(user) => { - if users.contains_key(&user.nickname) { - self.send_string_to(&addr, format!(":plankircd 433 {} :Nickname is already in use", user.nickname)); - unreg.insert(*addr, user); + let mut user; + { + user = self.users.lock().unwrap().get_mut(addr).cloned(); + } + match user { + Some(mut user) => { + if !user.is_valid() || user.is_registered() { return Err(()); } - let mut msg = format!(":plankircd 001 {} :Welcome to IRC!\r\n", user.nickname); - msg += &format!(":plankircd 002 {} :Your host is plankircd, running version 0.0.1\r\n", user.nickname); - msg += &format!(":plankircd 003 {} :This server was created tomorrow\r\n", user.nickname); - msg += &format!(":plankircd 004 {} plankircd plankircd-0.0.1 o o\r\n", user.nickname); - msg += &format!(":plankircd 422 {} :MOTD File is missing\r\n", user.nickname); - let res = self.send_string_to(&addr, msg); - println!("{:?}",res); - users.insert(user.nickname.clone(), user); + if self.users.lock().unwrap().iter().any(|(k, v)| k != addr && v.nickname == user.nickname) { + return Err(()); + } + + user.register(); // TODO: This is done on the cloned user -- won't save Ok(()) }, None => Err(()) } } - pub fn disconnect(&mut self, addr: &SocketAddr) { - self.send_message_to(addr, IrcMessage::new(Some("plankircd"), IrcCommand::ERROR, vec!["Connection terminated."])); - self.connections.lock().unwrap().remove(addr); - self.unregistered_users.lock().unwrap().remove(addr); - self.users.lock().unwrap().retain(|_, u| u.address != *addr); + pub fn nick_exists(&self, nick: &str) -> bool { + self.users.lock().unwrap().values().any(|user| user.nickname == nick) } - pub fn is_unreg(&self, addr: &SocketAddr) -> bool { - self.unregistered_users.lock().unwrap().contains_key(addr) + pub fn send_message_to(&self, addr: &SocketAddr, msg: IrcMessage) -> Result<(), SendError> { + if let Some(user) = self.users.lock().unwrap().get(addr) { + user.send(msg) + } + else { Ok(()) } } - pub fn send_string_to(&self, to: &SocketAddr, string: String) -> Result<(),SendError> { - match self.connections.lock().unwrap().get(to) { - Some(tx) => tx.unbounded_send(string), - None => Ok(()), // TODO: proper error + pub fn is_registered(&self, addr: &SocketAddr) -> bool { + if let Some(user) = self.users.lock().unwrap().get(addr) { + user.is_registered() + } + else { + false } } - pub fn send_message_to(&self, to: &SocketAddr, message: IrcMessage) -> Result<(),SendError> { - self.send_string_to(to, message.to_string()) + pub fn connect(&mut self, addr: SocketAddr, send: UnboundedSender) { + self.users.lock().unwrap().insert(addr, User::new(send)); + } + + pub fn disconnect(&mut self, addr: &SocketAddr) { + if let Some(user) = self.users.lock().unwrap().remove(addr) { + user.send(IrcMessage::new(Some(&self.hostname.lock().unwrap()), IrcCommand::ERROR, vec!["Connection terminated."])); + } } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index deabc06..82026ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,10 +34,7 @@ fn main() { let (reader, writer) = sock.split(); let (tx, rx) = futures::sync::mpsc::unbounded(); - { - context.connections.lock().unwrap().insert(addr, tx); - context.unregistered_users.lock().unwrap().insert(addr, user::User::new(addr)); - } + context.connect(addr, tx); let context_inner = context.clone(); @@ -65,70 +62,85 @@ fn main() { }) .map(move |(reader, line)| { use models::IrcMessage; - let msg = IrcMessage::from_str(&line); - let res = match msg { - Some(msg) => { - use models::IrcCommand::*; - match msg.command { - NICK => { - let mut shouldreg = false; - if msg.params.len() < 1 { - } - else { - if context.is_unreg(&addr) { - let mut lock = context.unregistered_users.lock().unwrap(); - let mut unreg = lock.get_mut(&addr).unwrap(); - let wasreg = unreg.is_registered(); - unreg.set_nick(&msg.params[0]); - if !wasreg && unreg.is_registered() { - shouldreg = true; - } + for l in line.lines() { + println!("{} -> {}", addr, l.trim()); + } + let res = IrcMessage::from_str(&line); + if let Err(e) = res { + eprintln!("error parsing command: {:?}", e); + return reader; + } + let msg = res.unwrap(); + use models::IrcCommand::*; + match msg.command { + NICK => { + if msg.params.len() < 1 { + context.send_message_to(&addr, IrcMessage::new(Some("plankircd"), Num(431), vec!["*", "No nickname given"])); + } + else { + let nick = &msg.params[0]; + if context.nick_exists(nick) { + context.send_message_to(&addr, IrcMessage::new(Some("plankircd"), Num(443), vec!["*", nick, "Nickname is already in use"])); + } + else { + let (valid, old) = { + let mut lock = context.users.lock().unwrap(); + let mut user = lock.get_mut(&addr).unwrap(); + let old = user.nickname.clone(); + user.set_nick(nick); + (user.is_valid(), old) + }; + if valid { + if !context.is_registered(&addr) { + context.register_user(&addr); + } + else { + context.send_message_to(&addr, IrcMessage::new(Some(&old), NICK, vec![nick])); } } - if shouldreg { - context.register_user(&addr); - } - Ok(()) - }, - USER => { - let mut shouldreg = false; - if msg.params.len() < 4 { - } - else { - if context.is_unreg(&addr) { - let mut lock = context.unregistered_users.lock().unwrap(); - let mut unreg = lock.get_mut(&addr).unwrap(); - let wasreg = unreg.is_registered(); - unreg.set_user(&msg.params[0], &msg.params[2], &msg.params[3]); - if !wasreg && unreg.is_registered() { - shouldreg = true; - } - } - } - if shouldreg { - context.register_user(&addr); - } - Ok(()) - }, - PING => context.send_message_to(&addr, IrcMessage::new(Some("plankircd"), PONG, vec!["plankircd", &msg.params[0]])), - QUIT => Ok(context.disconnect(&addr)), - _ => Ok(println!("Unhandled command: {} -> {}", addr, line.trim())), + } + } }, - None => Ok(eprintln!("Unrecognized command: {} -> {}", addr, line.trim())), - }; - - if let Err(e) = res { - eprintln!("error handling command {:?}", e); + USER => { + if msg.params.len() < 4 { + } + else { + let valid = { + let mut lock = context.users.lock().unwrap(); + let mut user = lock.get_mut(&addr).unwrap(); + user.set_user(&msg.params[0], &msg.params[2], &msg.params[3]); + user.is_valid() + }; + if valid && !context.is_registered(&addr) { + context.register_user(&addr); + } + } + }, + PING => {context.send_message_to(&addr, IrcMessage::new(Some("plankircd"), PONG, vec!["plankircd", &msg.params[0]]));}, + PRIVMSG => { + if msg.params[0].starts_with('#') { + } + else { + let users = context.users.lock().unwrap(); + let from = &users[&addr]; + if let Some(user) = users.values().find(|u| u.nickname == msg.params[0]) { + user.privmsg(from, &msg.params[1]); + } + } + } + QUIT => context.disconnect(&addr), + _ => println!("Unhandled command: {}", msg.command), } - reader }) }) .map_err(|_|()); let socket_writer = rx.fold(writer, move |writer, msg| { - println!("{} <- {}", addr, msg); + for l in msg.lines() { + println!("{} <- {}", addr, l.trim()); + } io::write_all(writer, msg.into_bytes()) .map(|(writer, _)| writer) .map_err(|_| ()) diff --git a/src/models.rs b/src/models.rs index b208ea3..888419e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,3 +1,5 @@ +use std::fmt; + #[derive(Debug, PartialEq, Eq)] pub enum IrcCommand { USER, @@ -14,6 +16,7 @@ pub enum IrcCommand { PING, PONG, ERROR, + Num(u16), } impl IrcCommand { @@ -34,7 +37,19 @@ impl IrcCommand { "PING" => Some(PING), "PONG" => Some(PONG), "ERROR" => Some(ERROR), - _ => None, + s => if let Ok(i) = s.parse::() { + Some(Num(i)) + } else { None }, + } + } +} + +impl fmt::Display for IrcCommand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::IrcCommand::*; + match self { + Num(i) => write!(f, "{:03}", i), + cmd => write!(f, "{:?}", cmd), } } } @@ -55,7 +70,7 @@ impl IrcMessage { } } - pub fn from_str(msg: &str) -> Option { // TODO: Wrap in Result<> + pub fn from_str(msg: &str) -> Result { // TODO: Wrap in Result<> let mut args = msg.trim().splitn(2, " :"); let mut left = args.next().unwrap().split(' '); @@ -67,11 +82,17 @@ impl IrcMessage { prefix = Some(&s[1..]); if let Some(c) = left.next() { - cmd = IrcCommand::from_str(c); + cmd = match IrcCommand::from_str(c) { + Some(c) => Some(c), + None => return Err(format!("Unknown command: {}", s)), + }; }; } else { - cmd = IrcCommand::from_str(s); + cmd = match IrcCommand::from_str(s) { + Some(c) => Some(c), + None => return Err(format!("Unknown command: {}", s)), + }; } } @@ -82,8 +103,8 @@ impl IrcMessage { }; match cmd { - Some(c) => Some(IrcMessage::new(prefix, c, params)), - None => None, + Some(c) => Ok(IrcMessage::new(prefix, c, params)), + None => Err("Not enough arguments".to_string()), } } @@ -96,8 +117,8 @@ impl IrcMessage { // doesn't have any spaces match self.prefix { - Some(ref prefix) => format!(":{} {:?} {}\r\n", prefix, self.command, param.join(" ")), - None => format!("{:?} {}\r\n", self.command, param.join(" ")) + Some(ref prefix) => format!(":{} {} {}\r\n", prefix, self.command, param.join(" ")), + None => format!("{} {}\r\n", self.command, param.join(" ")) } } } @@ -160,4 +181,15 @@ mod tests { assert_eq!(&msg.to_string(), "PRIVMSG supes :u suk lol\r\n"); } + + #[test] + fn message_to_string_numeric() { + let msg = IrcMessage { + prefix: Some("plankircd".to_string()), + command: IrcCommand::Num(4), + params: vec!["supes".to_string(), "u suk lol".to_string()] + }; + + assert_eq!(&msg.to_string(), ":plankircd 004 supes :u suk lol\r\n"); + } } \ No newline at end of file diff --git a/src/user.rs b/src/user.rs index 2f913ef..432823e 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,4 +1,6 @@ -use std::net::SocketAddr; +use models::{IrcMessage, IrcCommand}; +use futures::sync::mpsc::UnboundedSender; +use futures::sync::mpsc::SendError; #[derive(Debug, Clone)] pub struct User { @@ -6,41 +8,64 @@ pub struct User { pub username: String, pub hostname: String, pub realname: String, - pub address: SocketAddr, - nick_set: bool, - user_set: bool, + sendchannel: UnboundedSender, + registered: bool, } impl User { - pub fn new(addr: SocketAddr) -> User { + pub fn new(send: UnboundedSender) -> User { User { nickname: String::new(), username: String::new(), hostname: String::new(), realname: String::new(), - address: addr, - nick_set: false, - user_set: false, + sendchannel: send, + registered: false, } } + pub fn register(&mut self) { + if self.registered { return; } + self.registered = true; + + let mut msg = format!(":{0} 001 {1} :Welcome to IRC!\r\n:{0} 002 {1} :Your host is plankircd, running version 0.0.1\r\n:{0} 003 {1} :This server was created tomorrow\r\n:{0} 004 {1} plankircd plankircd-0.0.1 o o\r\n:{0} 422 {1} :MOTD File is missing\r\n", self.hostname, self.nickname); + + let res = self.send_raw(msg); + println!("{:?}",res); + } + + pub fn is_registered(&self) -> bool { + self.registered + } + pub fn set_nick(&mut self, nick: &str) { self.nickname = nick.to_string(); - self.nick_set = true; } pub fn set_user(&mut self, user: &str, host: &str, real: &str) { + self.username = user.to_string(); self.hostname = host.to_string(); self.realname = real.to_string(); - self.user_set = true; } pub fn to_string(&self) -> String { format!("{}!{}@{}", self.nickname, self.username, self.hostname) } - pub fn is_registered(&self) -> bool { - self.nick_set && self.user_set + pub fn is_valid(&self) -> bool { + !self.nickname.is_empty() && !self.username.is_empty() && !self.hostname.is_empty() + } + + pub fn send_raw(&self, msg: String) -> Result<(),SendError> { + self.sendchannel.unbounded_send(msg) + } + + pub fn send(&self, msg: IrcMessage) -> Result<(),SendError> { + self.sendchannel.unbounded_send(msg.to_string()) + } + + pub fn privmsg(&self, from: &User, msg: &str) -> Result<(),SendError> { + self.send(IrcMessage::new(Some(&from.nickname), IrcCommand::PRIVMSG, vec![&self.nickname, msg])) } }